Compare commits
77 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 | ||
|
|
60cbd96d91 | ||
|
|
ee0400f1ac | ||
|
|
b8a3746558 | ||
|
|
14f25fc997 | ||
|
|
27da6ee9eb | ||
|
|
48d482006a | ||
|
|
9afee2620a | ||
|
|
baca3047d4 | ||
|
|
43a5265e0c | ||
|
|
5519bb3523 | ||
|
|
ff213d1386 | ||
|
|
55b3f99653 | ||
|
|
8a6ff3f414 | ||
|
|
feb8624d05 | ||
|
|
a2c33545d4 | ||
|
|
7decf68122 | ||
|
|
6b62ec97c6 | ||
|
|
ea3dab4a06 | ||
|
|
5c8f08fc92 | ||
|
|
00a0b2c8b6 | ||
|
|
007218cc13 | ||
|
|
8afe495a4f | ||
|
|
257f2086d1 | ||
|
|
f011c84ce8 | ||
|
|
8b839f46b2 | ||
|
|
c1067d8fe1 | ||
|
|
bb76818cc8 | ||
|
|
5dcc219f1c | ||
|
|
4ff9e90153 | ||
|
|
28fe98af80 | ||
|
|
5eba65aaa0 | ||
|
|
d1e3e7d240 | ||
|
|
90ff8ecd0f | ||
|
|
468e75a572 | ||
|
|
ae3e5487d7 | ||
|
|
03147a5426 | ||
|
|
14a2b10989 | ||
|
|
b29f8aa1e6 | ||
|
|
f9bb932cd8 | ||
|
|
a38cbbf7e8 |
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)
|
||||
@@ -125,7 +124,7 @@ if (LINUX)
|
||||
target_link_libraries(Telegram PRIVATE PkgConfig::X11)
|
||||
endif()
|
||||
else()
|
||||
pkg_search_module(GTK REQUIRED gtk+-3.0 gtk+-2.0)
|
||||
pkg_check_modules(GTK REQUIRED gtk+-3.0)
|
||||
target_include_directories(Telegram PRIVATE ${GTK_INCLUDE_DIRS})
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
|
||||
@@ -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.";
|
||||
@@ -1007,6 +1014,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_report_group_title" = "Report group";
|
||||
"lng_report_bot_title" = "Report bot";
|
||||
"lng_report_message_title" = "Report message";
|
||||
"lng_report_please_select_messages" = "Please select messages to report.";
|
||||
"lng_report_select_messages" = "Select messages";
|
||||
"lng_report_messages_none" = "Select Messages";
|
||||
"lng_report_messages_count#one" = "Report {count} Message";
|
||||
@@ -2030,6 +2038,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_chat_no_camera" = "You can't turn on video in this chat.";
|
||||
"lng_group_call_chat_no_screen" = "You can't share your screen in this chat.";
|
||||
"lng_group_call_failed_screen" = "An error occured. Screencast has stopped.";
|
||||
"lng_group_call_failed_camera" = "Could not enable camera. Perhaps another app is using the camera already. Try closing other apps.";
|
||||
"lng_group_call_tooltip_screen" = "Share screen";
|
||||
"lng_group_call_tooltip_camera" = "Your camera is off. Click here to enable camera.";
|
||||
"lng_group_call_tooltip_microphone" = "You are on mute. Click here to speak.";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.7.10.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,7,10,0
|
||||
PRODUCTVERSION 2,7,10,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.7.10.0"
|
||||
VALUE "FileVersion", "2.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.10.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,7,10,0
|
||||
PRODUCTVERSION 2,7,10,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.7.10.0"
|
||||
VALUE "FileVersion", "2.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.10.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);
|
||||
|
||||
@@ -520,6 +520,10 @@ groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) {
|
||||
widthMin: 210px;
|
||||
}
|
||||
}
|
||||
groupCallPopupVolumeMenu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
itemBgOver: groupCallMenuBg;
|
||||
}
|
||||
|
||||
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
|
||||
groupCallRecordingTimerFont: font(12px);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1989,6 +1989,10 @@ bool GroupCall::emitShareCameraError() {
|
||||
|
||||
void GroupCall::emitShareCameraError(Error error) {
|
||||
_cameraState = Webrtc::VideoState::Inactive;
|
||||
if (error == Error::CameraFailed
|
||||
&& Webrtc::GetVideoInputList().empty()) {
|
||||
error = Error::NoCamera;
|
||||
}
|
||||
_errors.fire_copy(error);
|
||||
}
|
||||
|
||||
@@ -2042,8 +2046,14 @@ void GroupCall::setupOutgoingVideo() {
|
||||
_cameraCapture = _delegate->groupCallGetVideoCapture(
|
||||
_cameraInputId);
|
||||
if (!_cameraCapture) {
|
||||
return emitShareCameraError(Error::NoCamera);
|
||||
return emitShareCameraError(Error::CameraFailed);
|
||||
}
|
||||
const auto weak = base::make_weak(this);
|
||||
_cameraCapture->setOnFatalError([=] {
|
||||
crl::on_main(weak, [=] {
|
||||
emitShareCameraError(Error::CameraFailed);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_cameraCapture->switchToDevice(_cameraInputId.toStdString());
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ enum class VideoQuality {
|
||||
|
||||
enum class Error {
|
||||
NoCamera,
|
||||
CameraFailed,
|
||||
ScreenFailed,
|
||||
MutedNoCamera,
|
||||
MutedNoScreen,
|
||||
|
||||
@@ -1455,7 +1455,7 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
|
||||
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
|
||||
menu->menu(),
|
||||
st::groupCallPopupMenuWithVolume.menu,
|
||||
st::groupCallPopupVolumeMenu,
|
||||
otherParticipantStateValue,
|
||||
row->volume(),
|
||||
Group::kMaxVolume,
|
||||
@@ -1494,6 +1494,10 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
}
|
||||
}, volumeItem->lifetime());
|
||||
|
||||
if (!menu->empty()) {
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
menu->addAction(std::move(volumeItem));
|
||||
|
||||
if (!isMe(participantPeer)) {
|
||||
|
||||
@@ -841,19 +841,19 @@ void Panel::enlargeVideo() {
|
||||
std::min(available.height(), st::groupCallWideModeSize.height()));
|
||||
auto geometry = QRect(window()->pos(), QSize(width, height));
|
||||
if (geometry.x() < available.x()) {
|
||||
geometry.setX(std::min(available.x(), window()->x()));
|
||||
geometry.moveLeft(std::min(available.x(), window()->x()));
|
||||
}
|
||||
if (geometry.x() + geometry.width()
|
||||
> available.x() + available.width()) {
|
||||
geometry.setX(std::max(
|
||||
geometry.moveLeft(std::max(
|
||||
available.x() + available.width(),
|
||||
window()->x() + window()->width()) - geometry.width());
|
||||
}
|
||||
if (geometry.y() < available.y()) {
|
||||
geometry.setY(std::min(available.y(), window()->y()));
|
||||
geometry.moveTop(std::min(available.y(), window()->y()));
|
||||
}
|
||||
if (geometry.y() + geometry.height() > available.y() + available.height()) {
|
||||
geometry.setY(std::max(
|
||||
geometry.moveTop(std::max(
|
||||
available.y() + available.height(),
|
||||
window()->y() + window()->height()) - geometry.height());
|
||||
}
|
||||
|
||||
@@ -105,7 +105,9 @@ void Toasts::setupPinnedVideo() {
|
||||
? _call->videoEndpointLargeValue()
|
||||
: rpl::single(_call->videoEndpointLarge());
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||
) | rpl::filter([=] {
|
||||
return (_call->shownVideoTracks().size() > 1);
|
||||
}) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||
const auto pinned = _call->videoEndpointPinned();
|
||||
const auto peer = endpoint.peer;
|
||||
if (!peer) {
|
||||
@@ -155,6 +157,8 @@ void Toasts::setupError() {
|
||||
const auto key = [&] {
|
||||
switch (error) {
|
||||
case Error::NoCamera: return tr::lng_call_error_no_camera;
|
||||
case Error::CameraFailed:
|
||||
return tr::lng_group_call_failed_camera;
|
||||
case Error::ScreenFailed:
|
||||
return tr::lng_group_call_failed_screen;
|
||||
case Error::MutedNoCamera:
|
||||
|
||||
@@ -765,9 +765,10 @@ void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
|
||||
const auto &endpoint = tile->endpoint();
|
||||
const auto forceThumbnailQuality = !wide()
|
||||
&& (ranges::count(_tiles, false, &VideoTile::hidden) > 1);
|
||||
const auto forceFullQuality = wide() && (tile.get() == _large);
|
||||
const auto quality = forceThumbnailQuality
|
||||
? VideoQuality::Thumbnail
|
||||
: (min >= kMedium)
|
||||
: (forceFullQuality || min >= kMedium)
|
||||
? VideoQuality::Full
|
||||
: (min >= kSmall)
|
||||
? VideoQuality::Medium
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -744,7 +744,7 @@ FieldAutocomplete::Inner::Inner(
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
_isOneColumn = controller->adaptive().isOneColumn();
|
||||
update();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -95,6 +95,9 @@ public:
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideValue() const {
|
||||
return _adaptiveForWide.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideChanges() const {
|
||||
return _adaptiveForWide.changes();
|
||||
}
|
||||
void setAdaptiveForWide(bool value) {
|
||||
_adaptiveForWide = value;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -183,8 +183,6 @@ void Sandbox::launchApplication() {
|
||||
}
|
||||
|
||||
void Sandbox::setupScreenScale() {
|
||||
Ui::DisableCustomScaling();
|
||||
|
||||
const auto dpi = Sandbox::primaryScreen()->logicalDotsPerInch();
|
||||
LOG(("Primary screen DPI: %1").arg(dpi));
|
||||
if (dpi <= 108) {
|
||||
@@ -315,6 +313,7 @@ void Sandbox::singleInstanceChecked() {
|
||||
Logs::multipleInstances();
|
||||
}
|
||||
|
||||
Ui::DisableCustomScaling();
|
||||
refreshGlobalProxy();
|
||||
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
|
||||
new NotStartedWindow();
|
||||
|
||||
@@ -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 = 2007010;
|
||||
constexpr auto AppVersionStr = "2.7.10";
|
||||
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;
|
||||
|
||||
};
|
||||
@@ -209,7 +209,7 @@ base::binary_guard ReadImageAsync(
|
||||
}
|
||||
|
||||
void ResolveDocument(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item) {
|
||||
if (!document->date) {
|
||||
@@ -222,7 +222,7 @@ void ResolveDocument(
|
||||
&& document->isVideoFile()
|
||||
&& !document->filepath().isEmpty()) {
|
||||
File::Launch(document->location(false).fname);
|
||||
} else {
|
||||
} else if (controller) {
|
||||
controller->openDocument(document, msgId, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ base::binary_guard ReadImageAsync(
|
||||
FnMut<void(QImage&&)> done);
|
||||
|
||||
void ResolveDocument(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -2138,7 +2138,7 @@ void InnerWidget::peerSearchReceived(
|
||||
} else {
|
||||
LOG(("API Error: "
|
||||
"user %1 was not loaded in InnerWidget::peopleReceived()"
|
||||
).arg(peer->id.value));
|
||||
).arg(peerFromMTP(mtpPeer).value));
|
||||
}
|
||||
}
|
||||
for (const auto &mtpPeer : result) {
|
||||
@@ -2153,7 +2153,7 @@ void InnerWidget::peerSearchReceived(
|
||||
} else {
|
||||
LOG(("API Error: "
|
||||
"user %1 was not loaded in InnerWidget::peopleReceived()"
|
||||
).arg(peer->id.value));
|
||||
).arg(peerFromMTP(mtpPeer).value));
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
|
||||
@@ -266,7 +266,7 @@ Widget::Widget(
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateForwardBar();
|
||||
}, lifetime());
|
||||
|
||||
@@ -153,11 +153,15 @@ PanelController::~PanelController() {
|
||||
if (_saveSettingsTimer.isActive()) {
|
||||
saveSettings();
|
||||
}
|
||||
_panel->destroyLayer();
|
||||
if (_panel) {
|
||||
_panel->destroyLayer();
|
||||
}
|
||||
}
|
||||
|
||||
void PanelController::activatePanel() {
|
||||
_panel->showAndActivate();
|
||||
if (_panel) {
|
||||
_panel->showAndActivate();
|
||||
}
|
||||
}
|
||||
|
||||
void PanelController::createPanel() {
|
||||
|
||||
@@ -298,10 +298,7 @@ Widget::Widget(
|
||||
|
||||
_fixedBarShadow->raise();
|
||||
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -413,7 +413,7 @@ HistoryWidget::HistoryWidget(
|
||||
Window::ActivateWindow(controller);
|
||||
});
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_history) {
|
||||
_history->forceFullResize();
|
||||
|
||||
@@ -128,10 +128,7 @@ PinnedWidget::PinnedWidget(
|
||||
}, _topBar->lifetime());
|
||||
|
||||
_topBarShadow->raise();
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -193,10 +193,7 @@ RepliesWidget::RepliesWidget(
|
||||
_rootView->raise();
|
||||
_topBarShadow->raise();
|
||||
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -126,10 +126,7 @@ ScheduledWidget::ScheduledWidget(
|
||||
}, _topBar->lifetime());
|
||||
|
||||
_topBarShadow->raise();
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -117,7 +117,7 @@ TopBarWidget::TopBarWidget(
|
||||
_search->setForceRippled(searchInActiveChat, animated);
|
||||
}, lifetime());
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -35,7 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/focus_persister.h"
|
||||
@@ -380,7 +380,7 @@ MainWidget::MainWidget(
|
||||
}
|
||||
});
|
||||
|
||||
_controller->adaptive().changed(
|
||||
_controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
handleAdaptiveLayoutUpdate();
|
||||
}, lifetime());
|
||||
@@ -1423,6 +1423,9 @@ void MainWidget::showChooseReportMessages(
|
||||
peer->id,
|
||||
SectionShow::Way::Forward,
|
||||
ShowForChooseMessagesMsgId);
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { tr::lng_report_please_select_messages(tr::now) },
|
||||
});
|
||||
}
|
||||
|
||||
void MainWidget::clearChooseReportMessages() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ffmpeg/ffmpeg_utility.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "base/concurrent_timer.h"
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
namespace Media {
|
||||
namespace Streaming {
|
||||
@@ -288,6 +289,7 @@ void VideoTrackObject::readFrames() {
|
||||
}
|
||||
}, [&](Shared::PrepareNextCheck delay) {
|
||||
Expects(delay == kTimeUnknown || delay > 0);
|
||||
|
||||
if (delay != kTimeUnknown) {
|
||||
queueReadFrames(delay);
|
||||
}
|
||||
@@ -303,7 +305,8 @@ auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
|
||||
-> ReadEnoughState {
|
||||
const auto dropStaleFrames = !_options.waitForMarkAsShown;
|
||||
const auto state = _shared->prepareState(trackTime, dropStaleFrames);
|
||||
return v::match(state, [&](Shared::PrepareFrame frame) -> ReadEnoughState {
|
||||
return v::match(state, [&](Shared::PrepareFrame frame)
|
||||
-> ReadEnoughState {
|
||||
while (true) {
|
||||
const auto result = readFrame(frame);
|
||||
if (result != FrameResult::Done) {
|
||||
@@ -314,6 +317,8 @@ auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
|
||||
}
|
||||
}
|
||||
}, [&](Shared::PrepareNextCheck delay) -> ReadEnoughState {
|
||||
Expects(delay == kTimeUnknown || delay > 0); // Debugging crash.
|
||||
|
||||
return delay;
|
||||
}, [&](v::null_t) -> ReadEnoughState {
|
||||
return FrameResult::Done;
|
||||
@@ -752,6 +757,16 @@ auto VideoTrack::Shared::prepareState(
|
||||
next->displayed = kDisplaySkipped;
|
||||
return next;
|
||||
} else {
|
||||
if (frame->position - trackTime + 1 <= 0) { // Debugging crash.
|
||||
CrashReports::SetAnnotation(
|
||||
"DelayValues",
|
||||
(QString::number(frame->position)
|
||||
+ " + 1 <= "
|
||||
+ QString::number(trackTime)));
|
||||
}
|
||||
Assert(frame->position >= trackTime);
|
||||
Assert(frame->position - trackTime + 1 > 0);
|
||||
|
||||
return PrepareNextCheck(frame->position - trackTime + 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
@@ -1427,12 +1435,15 @@ void OverlayWidget::subscribeToScreenGeometry() {
|
||||
}
|
||||
|
||||
void OverlayWidget::toMessage() {
|
||||
if (!_session || !_controller) {
|
||||
if (!_session) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto item = _session->data().message(_msgid)) {
|
||||
close();
|
||||
_controller->showPeerHistoryAtItem(item);
|
||||
if (const auto window = findWindow()) {
|
||||
window->showPeerHistoryAtItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1556,11 +1567,8 @@ void OverlayWidget::handleDocumentClick() {
|
||||
if (_document->loading()) {
|
||||
saveCancel();
|
||||
} else {
|
||||
if (!_controller) {
|
||||
return;
|
||||
}
|
||||
Data::ResolveDocument(
|
||||
_controller,
|
||||
findWindow(),
|
||||
_document,
|
||||
_document->owner().message(_msgid));
|
||||
if (_document->loading() && !_radial.animating()) {
|
||||
@@ -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) {
|
||||
@@ -2251,10 +2272,6 @@ void OverlayWidget::activate() {
|
||||
}
|
||||
|
||||
void OverlayWidget::show(OpenRequest request) {
|
||||
if (!request.controller()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto document = request.document();
|
||||
const auto photo = request.photo();
|
||||
const auto contextItem = request.item();
|
||||
@@ -2264,8 +2281,6 @@ void OverlayWidget::show(OpenRequest request) {
|
||||
return;
|
||||
}
|
||||
setSession(&photo->session());
|
||||
_controller = request.controller();
|
||||
Assert(_session == (&_controller->session()));
|
||||
|
||||
if (contextPeer) {
|
||||
setContext(contextPeer);
|
||||
@@ -2284,8 +2299,6 @@ void OverlayWidget::show(OpenRequest request) {
|
||||
activateControls();
|
||||
} else if (document) {
|
||||
setSession(&document->session());
|
||||
_controller = request.controller();
|
||||
Assert(_session == (&_controller->session()));
|
||||
|
||||
if (contextItem) {
|
||||
setContext(contextItem);
|
||||
@@ -2308,6 +2321,9 @@ void OverlayWidget::show(OpenRequest request) {
|
||||
activateControls();
|
||||
}
|
||||
}
|
||||
if (const auto controller = request.controller()) {
|
||||
_window = base::make_weak(&controller->window());
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
|
||||
@@ -3097,14 +3113,13 @@ float64 OverlayWidget::playbackControlsCurrentSpeed() {
|
||||
void OverlayWidget::switchToPip() {
|
||||
Expects(_streamed != nullptr);
|
||||
Expects(_document != nullptr);
|
||||
Expects(_controller != nullptr);
|
||||
|
||||
const auto document = _document;
|
||||
const auto msgId = _msgid;
|
||||
const auto closeAndContinue = [=] {
|
||||
_showAsPip = false;
|
||||
show(OpenRequest(
|
||||
_controller,
|
||||
findWindow(),
|
||||
document,
|
||||
document->owner().message(msgId),
|
||||
true));
|
||||
@@ -4538,6 +4553,36 @@ void OverlayWidget::applyHideWindowWorkaround() {
|
||||
}
|
||||
}
|
||||
|
||||
Window::SessionController *OverlayWidget::findWindow() const {
|
||||
if (!_session) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto window = _window.get();
|
||||
if (window) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
if (&controller->session() == _session) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto &active = _session->windows();
|
||||
if (!active.empty()) {
|
||||
return active.front();
|
||||
} else if (window) {
|
||||
Window::SessionController *controllerPtr = nullptr;
|
||||
window->invokeForSessionController(
|
||||
&_session->account(),
|
||||
[&](not_null<Window::SessionController*> newController) {
|
||||
controllerPtr = newController;
|
||||
});
|
||||
return controllerPtr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// #TODO unite and check
|
||||
void OverlayWidget::clearBeforeHide() {
|
||||
_sharedMedia = nullptr;
|
||||
|
||||
@@ -411,11 +411,13 @@ private:
|
||||
|
||||
void applyHideWindowWorkaround();
|
||||
|
||||
Window::SessionController *findWindow() const;
|
||||
|
||||
bool _opengl = false;
|
||||
const std::unique_ptr<Ui::RpWidgetWrap> _surface;
|
||||
const not_null<QWidget*> _widget;
|
||||
|
||||
Window::SessionController *_controller = nullptr;
|
||||
base::weak_ptr<Window::Controller> _window;
|
||||
Main::Session *_session = nullptr;
|
||||
rpl::lifetime _sessionLifetime;
|
||||
PhotoData *_photo = nullptr;
|
||||
|
||||
@@ -1009,6 +1009,9 @@ void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
|
||||
}
|
||||
|
||||
void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
|
||||
Expects(1 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
const auto weak = Ui::MakeWeak(_panel.widget());
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (weak) {
|
||||
@@ -1019,10 +1022,22 @@ void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
|
||||
return;
|
||||
}
|
||||
seekUpdate(position);
|
||||
|
||||
Assert(2 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
volumeControllerUpdate(position);
|
||||
|
||||
Assert(3 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
const auto pressed = base::take(_pressed);
|
||||
if (pressed && *pressed == OverState::Playback) {
|
||||
_panel.setDragDisabled(false);
|
||||
|
||||
Assert(4 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
seekFinish(_playbackProgress->value());
|
||||
} else if (pressed && *pressed == OverState::VolumeController) {
|
||||
_panel.setDragDisabled(false);
|
||||
@@ -1083,6 +1098,9 @@ void Pip::seekProgress(float64 value) {
|
||||
}
|
||||
|
||||
void Pip::seekFinish(float64 value) {
|
||||
Expects(5 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
if (!_lastDurationMs) {
|
||||
return;
|
||||
}
|
||||
@@ -1556,15 +1574,30 @@ void Pip::playbackPauseResume() {
|
||||
}
|
||||
|
||||
void Pip::restartAtSeekPosition(crl::time position) {
|
||||
Expects(6 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
if (!_instance.info().video.cover.isNull()) {
|
||||
_preparedCoverStorage = QImage();
|
||||
_preparedCoverState = ThumbState::Empty;
|
||||
_instance.saveFrameToCover();
|
||||
}
|
||||
|
||||
Assert(7 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
auto options = Streaming::PlaybackOptions();
|
||||
options.position = position;
|
||||
options.audioId = _instance.player().prepareLegacyState().id;
|
||||
|
||||
Assert(8 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
options.speed = _delegate->pipPlaybackSpeed();
|
||||
|
||||
Assert(9 && options.speed >= 0.5
|
||||
&& options.speed <= 2.); // Debugging strange crash.
|
||||
|
||||
_instance.play(options);
|
||||
if (_startPaused) {
|
||||
_instance.pause();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -189,9 +189,7 @@ ConnectionPointer AbstractConnection::Create(
|
||||
}
|
||||
|
||||
uint32 AbstractConnection::extendedNotSecurePadding() const {
|
||||
return requiresExtendedPadding()
|
||||
? uint32(openssl::RandomValue<uchar>() & 0x3F)
|
||||
: 0;
|
||||
return uint32(openssl::RandomValue<uchar>() & 0x3F);
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
@@ -92,9 +92,6 @@ public:
|
||||
[[nodiscard]] virtual bool needHttpWait() {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual bool requiresExtendedPadding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual int32 debugState() const = 0;
|
||||
|
||||
|
||||
@@ -198,12 +198,6 @@ void ResolvingConnection::sendData(mtpBuffer &&buffer) {
|
||||
_child->sendData(std::move(buffer));
|
||||
}
|
||||
|
||||
bool ResolvingConnection::requiresExtendedPadding() const {
|
||||
Expects(_child != nullptr);
|
||||
|
||||
return _child->requiresExtendedPadding();
|
||||
}
|
||||
|
||||
void ResolvingConnection::disconnectFromServer() {
|
||||
_address = QString();
|
||||
_port = 0;
|
||||
|
||||
@@ -35,7 +35,6 @@ public:
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) override;
|
||||
bool isConnected() const override;
|
||||
bool requiresExtendedPadding() const override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ public:
|
||||
virtual uint32 id() const = 0;
|
||||
virtual bool supportsArbitraryLength() const = 0;
|
||||
|
||||
virtual bool requiresExtendedPadding() const = 0;
|
||||
virtual void prepareKey(bytes::span key, bytes::const_span source) = 0;
|
||||
virtual bytes::span finalizePacket(mtpBuffer &buffer) = 0;
|
||||
|
||||
@@ -58,7 +57,6 @@ public:
|
||||
uint32 id() const override;
|
||||
bool supportsArbitraryLength() const override;
|
||||
|
||||
bool requiresExtendedPadding() const override;
|
||||
void prepareKey(bytes::span key, bytes::const_span source) override;
|
||||
bytes::span finalizePacket(mtpBuffer &buffer) override;
|
||||
|
||||
@@ -75,10 +73,6 @@ bool TcpConnection::Protocol::Version0::supportsArbitraryLength() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TcpConnection::Protocol::Version0::requiresExtendedPadding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void TcpConnection::Protocol::Version0::prepareKey(
|
||||
bytes::span key,
|
||||
bytes::const_span source) {
|
||||
@@ -142,7 +136,6 @@ class TcpConnection::Protocol::Version1 : public Version0 {
|
||||
public:
|
||||
explicit Version1(bytes::vector &&secret);
|
||||
|
||||
bool requiresExtendedPadding() const override;
|
||||
void prepareKey(bytes::span key, bytes::const_span source) override;
|
||||
|
||||
private:
|
||||
@@ -154,10 +147,6 @@ TcpConnection::Protocol::Version1::Version1(bytes::vector &&secret)
|
||||
: _secret(std::move(secret)) {
|
||||
}
|
||||
|
||||
bool TcpConnection::Protocol::Version1::requiresExtendedPadding() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TcpConnection::Protocol::Version1::prepareKey(
|
||||
bytes::span key,
|
||||
bytes::const_span source) {
|
||||
@@ -425,12 +414,6 @@ void TcpConnection::socketDisconnected() {
|
||||
}
|
||||
}
|
||||
|
||||
bool TcpConnection::requiresExtendedPadding() const {
|
||||
Expects(_protocol != nullptr);
|
||||
|
||||
return _protocol->requiresExtendedPadding();
|
||||
}
|
||||
|
||||
void TcpConnection::sendData(mtpBuffer &&buffer) {
|
||||
Expects(buffer.size() > 2);
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ public:
|
||||
bool protocolForFiles) override;
|
||||
void timedOut() override;
|
||||
bool isConnected() const override;
|
||||
bool requiresExtendedPadding() const override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace {
|
||||
auto serialized = SerializedRequest::Serialize(data);
|
||||
serialized.setMsgId(realMsgId);
|
||||
serialized.setSeqNo(0);
|
||||
serialized.addPadding(false, true);
|
||||
serialized.addPadding(true);
|
||||
|
||||
constexpr auto kMsgIdPosition = SerializedRequest::kMessageIdPosition;
|
||||
constexpr auto kMinMessageSize = 5;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
uint32 CountPaddingPrimesCount(uint32 requestSize, bool extended, bool old) {
|
||||
if (old) {
|
||||
uint32 CountPaddingPrimesCount(
|
||||
uint32 requestSize,
|
||||
bool forAuthKeyInner) {
|
||||
if (forAuthKeyInner) {
|
||||
return ((8 + requestSize) & 0x03)
|
||||
? (4 - ((8 + requestSize) & 0x03))
|
||||
: 0;
|
||||
@@ -27,12 +29,8 @@ uint32 CountPaddingPrimesCount(uint32 requestSize, bool extended, bool old) {
|
||||
result += 4;
|
||||
}
|
||||
|
||||
if (extended) {
|
||||
// Some more random padding.
|
||||
result += ((openssl::RandomValue<uchar>() & 0x0F) << 2);
|
||||
}
|
||||
|
||||
return result;
|
||||
// Some more random padding.
|
||||
return result + ((openssl::RandomValue<uchar>() & 0x0F) << 2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -100,12 +98,14 @@ uint32 SerializedRequest::getSeqNo() const {
|
||||
return uint32((*_data)[kSeqNoPosition]);
|
||||
}
|
||||
|
||||
void SerializedRequest::addPadding(bool extended, bool old) {
|
||||
void SerializedRequest::addPadding(bool forAuthKeyInner) {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
const auto requestSize = (tl::count_length(*this) >> 2);
|
||||
const auto padding = CountPaddingPrimesCount(requestSize, extended, old);
|
||||
const auto padding = CountPaddingPrimesCount(
|
||||
requestSize,
|
||||
forAuthKeyInner);
|
||||
const auto fullSize = kMessageBodyPosition + requestSize + padding;
|
||||
if (uint32(_data->size()) != fullSize) {
|
||||
_data->resize(fullSize);
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
void setSeqNo(uint32 seqNo);
|
||||
[[nodiscard]] uint32 getSeqNo() const;
|
||||
|
||||
void addPadding(bool extended, bool old);
|
||||
void addPadding(bool forAuthKeyInner);
|
||||
[[nodiscard]] uint32 messageSize() const;
|
||||
|
||||
[[nodiscard]] bool needAck() const;
|
||||
|
||||
@@ -1263,17 +1263,16 @@ void SessionPrivate::handleReceived() {
|
||||
return restart();
|
||||
}
|
||||
|
||||
constexpr auto kMinPaddingSize = 12U;
|
||||
constexpr auto kMaxPaddingSize = 1024U;
|
||||
|
||||
auto encryptedInts = ints + kExternalHeaderIntsCount;
|
||||
auto encryptedIntsCount = (intsCount - kExternalHeaderIntsCount) & ~0x03U;
|
||||
auto encryptedBytesCount = encryptedIntsCount * kIntSize;
|
||||
auto decryptedBuffer = QByteArray(encryptedBytesCount, Qt::Uninitialized);
|
||||
auto msgKey = *(MTPint128*)(ints + 2);
|
||||
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
aesIgeDecrypt_oldmtp(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _encryptionKey, msgKey);
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
aesIgeDecrypt(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _encryptionKey, msgKey);
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
|
||||
auto decryptedInts = reinterpret_cast<const mtpPrime*>(decryptedBuffer.constData());
|
||||
auto serverSalt = *(uint64*)&decryptedInts[0];
|
||||
@@ -1283,31 +1282,10 @@ void SessionPrivate::handleReceived() {
|
||||
auto needAck = ((seqNo & 0x01) != 0);
|
||||
auto messageLength = *(uint32*)&decryptedInts[7];
|
||||
auto fullDataLength = kEncryptedHeaderIntsCount * kIntSize + messageLength; // Without padding.
|
||||
auto badMessageLength = (messageLength > kMaxMessageLength);
|
||||
|
||||
// Can underflow, but it is an unsigned type, so we just check the range later.
|
||||
auto paddingSize = static_cast<uint32>(encryptedBytesCount) - static_cast<uint32>(fullDataLength);
|
||||
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
constexpr auto kMinPaddingSize_oldmtp = 0U;
|
||||
constexpr auto kMaxPaddingSize_oldmtp = 15U;
|
||||
badMessageLength |= (/*paddingSize < kMinPaddingSize_oldmtp || */paddingSize > kMaxPaddingSize_oldmtp);
|
||||
|
||||
auto hashedDataLength = badMessageLength ? encryptedBytesCount : fullDataLength;
|
||||
auto sha1ForMsgKeyCheck = hashSha1(decryptedInts, hashedDataLength);
|
||||
|
||||
constexpr auto kMsgKeyShift_oldmtp = 4U;
|
||||
if (ConstTimeIsDifferent(&msgKey, sha1ForMsgKeyCheck.data() + kMsgKeyShift_oldmtp, sizeof(msgKey))) {
|
||||
LOG(("TCP Error: bad SHA1 hash after aesDecrypt in message."));
|
||||
TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str()));
|
||||
|
||||
return restart();
|
||||
}
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
constexpr auto kMinPaddingSize = 12U;
|
||||
constexpr auto kMaxPaddingSize = 1024U;
|
||||
badMessageLength |= (paddingSize < kMinPaddingSize || paddingSize > kMaxPaddingSize);
|
||||
|
||||
std::array<uchar, 32> sha256Buffer = { { 0 } };
|
||||
|
||||
SHA256_CTX msgKeyLargeContext;
|
||||
@@ -1323,9 +1301,11 @@ void SessionPrivate::handleReceived() {
|
||||
|
||||
return restart();
|
||||
}
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
|
||||
if (badMessageLength || (messageLength & 0x03)) {
|
||||
if ((messageLength > kMaxMessageLength)
|
||||
|| (messageLength & 0x03)
|
||||
|| (paddingSize < kMinPaddingSize)
|
||||
|| (paddingSize > kMaxPaddingSize)) {
|
||||
LOG(("TCP Error: bad msg_len received %1, data size: %2").arg(messageLength).arg(encryptedBytesCount));
|
||||
TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str()));
|
||||
|
||||
@@ -2616,12 +2596,7 @@ void SessionPrivate::destroyTemporaryKey() {
|
||||
bool SessionPrivate::sendSecureRequest(
|
||||
SerializedRequest &&request,
|
||||
bool needAnyResponse) {
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
const auto oldPadding = true;
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
const auto oldPadding = false;
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
request.addPadding(_connection->requiresExtendedPadding(), oldPadding);
|
||||
request.addPadding(false);
|
||||
|
||||
uint32 fullSize = request->size();
|
||||
if (fullSize < 9) {
|
||||
@@ -2643,27 +2618,6 @@ bool SessionPrivate::sendSecureRequest(
|
||||
).arg(getProtocolDcId()
|
||||
).arg(_encryptionKey->keyId()));
|
||||
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
uint32 padding = fullSize - 4 - messageSize;
|
||||
|
||||
uchar encryptedSHA[20];
|
||||
MTPint128 &msgKey(*(MTPint128*)(encryptedSHA + 4));
|
||||
hashSha1(
|
||||
request->constData(),
|
||||
(fullSize - padding) * sizeof(mtpPrime),
|
||||
encryptedSHA);
|
||||
|
||||
auto packet = _connection->prepareSecurePacket(_keyId, msgKey, fullSize);
|
||||
const auto prefix = packet.size();
|
||||
packet.resize(prefix + fullSize);
|
||||
|
||||
aesIgeEncrypt_oldmtp(
|
||||
request->constData(),
|
||||
&packet[prefix],
|
||||
fullSize * sizeof(mtpPrime),
|
||||
_encryptionKey,
|
||||
msgKey);
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
uchar encryptedSHA256[32];
|
||||
MTPint128 &msgKey(*(MTPint128*)(encryptedSHA256 + 8));
|
||||
|
||||
@@ -2683,7 +2637,6 @@ bool SessionPrivate::sendSecureRequest(
|
||||
fullSize * sizeof(mtpPrime),
|
||||
_encryptionKey,
|
||||
msgKey);
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
|
||||
DEBUG_LOG(("MTP Info: sending request, size: %1, num: %2, time: %3").arg(fullSize + 6).arg((*request)[4]).arg((*request)[5]));
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -171,7 +102,7 @@ bool Get(
|
||||
parent = parent->window();
|
||||
}
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (XDP::Use(type)) {
|
||||
{
|
||||
const auto result = XDP::Get(
|
||||
parent,
|
||||
files,
|
||||
@@ -186,30 +117,6 @@ bool Get(
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
if (integration->useFileDialog(type)) {
|
||||
return integration->getFileDialog(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
}
|
||||
}
|
||||
// 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" {
|
||||
@@ -20,13 +17,11 @@ extern "C" {
|
||||
} // extern "C"
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
|
||||
// CentOS 7 seem to be too old for needed definitions,
|
||||
// so don't include until we link to gtk directly.
|
||||
#if !defined DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION && defined LINK_TO_GTK
|
||||
#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
extern "C" {
|
||||
#include <gdk/gdkwayland.h>
|
||||
} // extern "C"
|
||||
#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION && LINK_TO_GTK
|
||||
#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
@@ -36,7 +31,7 @@ using base::Platform::GtkIntegration;
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
// To be able to compile with gtk-3.0 headers as well
|
||||
// To be able to compile with gtk-3.0 headers
|
||||
#define GdkDrawable GdkWindow
|
||||
|
||||
// Gtk 2
|
||||
@@ -50,12 +45,6 @@ f_gdk_x11_drawable_get_xid gdk_x11_drawable_get_xid = nullptr;
|
||||
using f_gdk_x11_window_get_type = GType (*)(void);
|
||||
f_gdk_x11_window_get_type gdk_x11_window_get_type = nullptr;
|
||||
|
||||
// To be able to compile with gtk-2.0 headers as well
|
||||
template <typename Object>
|
||||
inline bool gdk_is_x11_window_check(Object *obj) {
|
||||
return g_type_cit_helper(obj, gdk_x11_window_get_type());
|
||||
}
|
||||
|
||||
using f_gdk_window_get_display = GdkDisplay*(*)(GdkWindow *window);
|
||||
f_gdk_window_get_display gdk_window_get_display = nullptr;
|
||||
|
||||
@@ -70,11 +59,6 @@ f_gdk_x11_window_get_xid gdk_x11_window_get_xid = nullptr;
|
||||
using f_gdk_wayland_window_get_type = GType (*)(void);
|
||||
f_gdk_wayland_window_get_type gdk_wayland_window_get_type = nullptr;
|
||||
|
||||
template <typename Object>
|
||||
inline bool gdk_is_wayland_window_check(Object *obj) {
|
||||
return g_type_cit_helper(obj, gdk_wayland_window_get_type());
|
||||
}
|
||||
|
||||
using f_gdk_wayland_window_set_transient_for_exported = gboolean(*)(GdkWindow *window, char *parent_handle_str);
|
||||
f_gdk_wayland_window_set_transient_for_exported gdk_wayland_window_set_transient_for_exported = nullptr;
|
||||
#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
@@ -111,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_check(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
|
||||
|
||||
@@ -134,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_check(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,710 +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 {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
namespace {
|
||||
|
||||
// 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);
|
||||
static void onUpdatePreview(QGtkDialog *dialog);
|
||||
|
||||
private:
|
||||
void onParentWindowDestroyed();
|
||||
|
||||
GtkWidget *gtkWidget = nullptr;
|
||||
GtkWidget *_preview = 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);
|
||||
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;
|
||||
|
||||
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);
|
||||
if (PreviewSupported()) {
|
||||
_preview = gtk_image_new();
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "update-preview", G_CALLBACK(onUpdatePreview), this);
|
||||
gtk_file_chooser_set_preview_widget(gtk_file_chooser_cast(gtkWidget), _preview);
|
||||
}
|
||||
}
|
||||
|
||||
QGtkDialog::~QGtkDialog() {
|
||||
gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
GtkDialog *QGtkDialog::gtkDialog() const {
|
||||
return gtk_dialog_cast(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::onUpdatePreview(QGtkDialog* dialog) {
|
||||
auto filename = gtk_file_chooser_get_preview_filename(gtk_file_chooser_cast(dialog->gtkWidget));
|
||||
if (!filename) {
|
||||
gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), 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_cast(dialog->gtkWidget), 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_cast(dialog->_preview), pixbuf);
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false);
|
||||
}
|
||||
|
||||
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_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this);
|
||||
g_signal_connect_swapped(gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this);
|
||||
}
|
||||
|
||||
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_cast(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_cast(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_cast(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_cast(gtkDialog), gtkFilter);
|
||||
}
|
||||
}
|
||||
|
||||
QString GtkFileDialog::selectedNameFilter() const {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GtkFileFilter *gtkFilter = gtk_file_chooser_get_filter(gtk_file_chooser_cast(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());
|
||||
}
|
||||
|
||||
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_cast(gtkDialog), _windowTitle.toUtf8().constData());
|
||||
gtk_file_chooser_set_local_only(gtk_file_chooser_cast(gtkDialog), true);
|
||||
|
||||
const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode);
|
||||
gtk_file_chooser_set_action(gtk_file_chooser_cast(gtkDialog), action);
|
||||
|
||||
const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles);
|
||||
gtk_file_chooser_set_select_multiple(gtk_file_chooser_cast(gtkDialog), selectMultiple);
|
||||
|
||||
const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(gtk_file_chooser_cast(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_cast(gtkDialog), fi.path().toUtf8().constData());
|
||||
gtk_file_chooser_set_current_name(gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8().constData());
|
||||
} else if (filename.endsWith('/')) {
|
||||
gtk_file_chooser_set_current_folder(gtk_file_chooser_cast(gtkDialog), filename.toUtf8().constData());
|
||||
} else {
|
||||
gtk_file_chooser_select_filename(gtk_file_chooser_cast(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_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8().constData());
|
||||
else*/ if (_acceptMode == QFileDialog::AcceptOpen)
|
||||
gtk_button_set_label(gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8().constData());
|
||||
else
|
||||
gtk_button_set_label(gtk_button_cast(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_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8().constData());
|
||||
else*/
|
||||
gtk_button_set_label(gtk_button_cast(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_cast(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_cast(gtkDialog), gtkFilter);
|
||||
|
||||
_filters.insert(filter, gtkFilter);
|
||||
_filterNames.insert(gtkFilter, filter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Use(Type type) {
|
||||
if (!Supported()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return qEnvironmentVariableIsSet("TDESKTOP_USE_GTK_FILE_DIALOG")
|
||||
|| DesktopEnvironment::IsGtkBased();
|
||||
}
|
||||
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
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,30 +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 {
|
||||
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
bool Use(Type type = Type::ReadFile);
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
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,62 +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);
|
||||
}
|
||||
|
||||
bool GtkIntegration::useFileDialog(FileDialogType type) const {
|
||||
return FileDialog::Gtk::Use(type);
|
||||
}
|
||||
|
||||
bool GtkIntegration::getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
FileDialogType 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;
|
||||
}
|
||||
@@ -224,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,37 +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;
|
||||
|
||||
using FileDialogType = ::FileDialog::internal::Type;
|
||||
[[nodiscard]] bool useFileDialog(
|
||||
FileDialogType type = FileDialogType::ReadFile) const;
|
||||
[[nodiscard]] bool getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
FileDialogType 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,33 +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;
|
||||
}
|
||||
|
||||
bool GtkIntegration::useFileDialog(FileDialogType type) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GtkIntegration::getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
FileDialogType type,
|
||||
QString startFile) const {
|
||||
return false;
|
||||
int GtkIntegration::exec(const QString &parentDBusName, int ppid) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool GtkIntegration::showOpenWithDialog(const QString &filepath) const {
|
||||
@@ -47,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
|
||||
|
||||
@@ -12,114 +12,22 @@ extern "C" {
|
||||
#include <gdk/gdk.h>
|
||||
} // extern "C"
|
||||
|
||||
// To be able to compile with gtk-2.0 headers as well
|
||||
typedef struct _GdkMonitor GdkMonitor;
|
||||
typedef struct _GtkAppChooser GtkAppChooser;
|
||||
|
||||
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 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 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 GtkWidget* (*gtk_dialog_get_widget_for_response)(GtkDialog *dialog, gint response_id) = 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 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;
|
||||
|
||||
template <typename Result, typename Object>
|
||||
inline Result *g_type_cic_helper(Object *instance, GType iface_type) {
|
||||
return reinterpret_cast<Result*>(g_type_check_instance_cast(reinterpret_cast<GTypeInstance*>(instance), iface_type));
|
||||
}
|
||||
|
||||
inline GType (*gtk_dialog_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkDialog *gtk_dialog_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkDialog, Object>(obj, gtk_dialog_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_file_chooser_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkFileChooser, Object>(obj, gtk_file_chooser_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_image_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkImage *gtk_image_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkImage, Object>(obj, gtk_image_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_button_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkButton *gtk_button_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkButton, Object>(obj, gtk_button_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_window_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkWindow *gtk_window_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkWindow, Object>(obj, gtk_window_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_app_chooser_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkAppChooser, Object>(obj, gtk_app_chooser_get_type());
|
||||
}
|
||||
|
||||
template <typename Object>
|
||||
inline bool g_type_cit_helper(Object *instance, GType iface_type) {
|
||||
if (!instance) return false;
|
||||
|
||||
auto ginstance = reinterpret_cast<GTypeInstance*>(instance);
|
||||
if (ginstance->g_class && ginstance->g_class->g_type == iface_type) {
|
||||
return true;
|
||||
}
|
||||
return g_type_check_instance_is_a(ginstance, iface_type);
|
||||
}
|
||||
|
||||
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,8 @@ 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 {
|
||||
namespace File {
|
||||
@@ -21,6 +19,14 @@ namespace {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
rpl::event_stream<bool> GtkOpenWithDialogResponseStream;
|
||||
|
||||
struct GtkWidgetDeleter {
|
||||
void operator()(GtkWidget *widget) {
|
||||
gtk_widget_destroy(widget);
|
||||
}
|
||||
};
|
||||
|
||||
bool Supported() {
|
||||
return (gtk_app_chooser_dialog_new != nullptr)
|
||||
&& (gtk_app_chooser_get_app_info != nullptr)
|
||||
@@ -31,75 +37,57 @@ bool Supported() {
|
||||
&& (gtk_widget_destroy != nullptr);
|
||||
}
|
||||
|
||||
class GtkOpenWithDialog : public QWindow {
|
||||
class GtkOpenWithDialog {
|
||||
public:
|
||||
GtkOpenWithDialog(const QString &filepath);
|
||||
~GtkOpenWithDialog();
|
||||
|
||||
bool exec();
|
||||
GtkOpenWithDialog(
|
||||
const QString &parent,
|
||||
const QString &filepath);
|
||||
|
||||
private:
|
||||
static void handleResponse(GtkOpenWithDialog *dialog, int responseId);
|
||||
|
||||
GFile *_gfileInstance = nullptr;
|
||||
GtkWidget *_gtkWidget = nullptr;
|
||||
QEventLoop _loop;
|
||||
std::optional<bool> _result;
|
||||
const Glib::RefPtr<Gio::File> _file;
|
||||
const std::unique_ptr<GtkWidget, GtkWidgetDeleter> _gtkWidget;
|
||||
};
|
||||
|
||||
GtkOpenWithDialog::GtkOpenWithDialog(const QString &filepath)
|
||||
: _gfileInstance(g_file_new_for_path(filepath.toUtf8().constData()))
|
||||
GtkOpenWithDialog::GtkOpenWithDialog(
|
||||
const QString &parent,
|
||||
const QString &filepath)
|
||||
: _file(Gio::File::create_for_path(filepath.toStdString()))
|
||||
, _gtkWidget(gtk_app_chooser_dialog_new(
|
||||
nullptr,
|
||||
GTK_DIALOG_MODAL,
|
||||
_gfileInstance)) {
|
||||
_file->gobj())) {
|
||||
g_signal_connect_swapped(
|
||||
_gtkWidget,
|
||||
_gtkWidget.get(),
|
||||
"response",
|
||||
G_CALLBACK(handleResponse),
|
||||
this);
|
||||
}
|
||||
|
||||
GtkOpenWithDialog::~GtkOpenWithDialog() {
|
||||
gtk_widget_destroy(_gtkWidget);
|
||||
g_object_unref(_gfileInstance);
|
||||
}
|
||||
gtk_widget_realize(_gtkWidget.get());
|
||||
|
||||
bool GtkOpenWithDialog::exec() {
|
||||
gtk_widget_realize(_gtkWidget);
|
||||
Platform::internal::GdkSetTransientFor(
|
||||
gtk_widget_get_window(_gtkWidget.get()),
|
||||
parent);
|
||||
|
||||
if (const auto activeWindow = Core::App().activeWindow()) {
|
||||
Platform::internal::GdkSetTransientFor(
|
||||
gtk_widget_get_window(_gtkWidget),
|
||||
activeWindow->widget()->windowHandle());
|
||||
}
|
||||
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
gtk_widget_show(_gtkWidget);
|
||||
|
||||
if (!_result.has_value()) {
|
||||
_loop.exec();
|
||||
}
|
||||
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
return *_result;
|
||||
gtk_widget_show(_gtkWidget.get());
|
||||
}
|
||||
|
||||
void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId) {
|
||||
GAppInfo *chosenAppInfo = nullptr;
|
||||
dialog->_result = true;
|
||||
Glib::RefPtr<Gio::AppInfo> chosenAppInfo;
|
||||
bool result = true;
|
||||
|
||||
switch (responseId) {
|
||||
case GTK_RESPONSE_OK:
|
||||
chosenAppInfo = gtk_app_chooser_get_app_info(
|
||||
gtk_app_chooser_cast(dialog->_gtkWidget));
|
||||
chosenAppInfo = Glib::wrap(gtk_app_chooser_get_app_info(
|
||||
GTK_APP_CHOOSER(dialog->_gtkWidget.get())));
|
||||
|
||||
if (chosenAppInfo) {
|
||||
GList *uris = nullptr;
|
||||
uris = g_list_prepend(uris, g_file_get_uri(dialog->_gfileInstance));
|
||||
g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr);
|
||||
g_list_free(uris);
|
||||
g_object_unref(chosenAppInfo);
|
||||
try {
|
||||
chosenAppInfo->launch_uri(dialog->_file->get_uri());
|
||||
} catch (...) {
|
||||
}
|
||||
chosenAppInfo = {};
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -109,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
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace FileDialog {
|
||||
namespace XDP {
|
||||
namespace {
|
||||
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs;
|
||||
constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs;
|
||||
constexpr auto kXDGDesktopPortalFileChooserInterface = "org.freedesktop.portal.FileChooser"_cs;
|
||||
@@ -576,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;
|
||||
@@ -703,11 +708,6 @@ void Start() {
|
||||
ComputeFileChooserPortalVersion();
|
||||
}
|
||||
|
||||
bool Use(Type type) {
|
||||
return FileChooserPortalVersion.has_value()
|
||||
&& (type != Type::ReadFolder || *FileChooserPortalVersion >= 3);
|
||||
}
|
||||
|
||||
std::optional<bool> Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
@@ -716,6 +716,11 @@ std::optional<bool> Get(
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
if (!FileChooserPortalVersion.has_value()
|
||||
|| (type == Type::ReadFolder && *FileChooserPortalVersion < 3)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static const auto docRegExp = QRegularExpression("^/run/user/\\d+/doc");
|
||||
if (cDialogLastPath().isEmpty()
|
||||
|| cDialogLastPath().contains(docRegExp)) {
|
||||
|
||||
@@ -13,17 +13,14 @@ namespace Platform {
|
||||
namespace FileDialog {
|
||||
namespace XDP {
|
||||
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
void Start();
|
||||
bool Use(Type type = Type::ReadFile);
|
||||
std::optional<bool> Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile);
|
||||
|
||||
} // namespace XDP
|
||||
|
||||
@@ -17,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
#include <private/qguiapplication_p.h>
|
||||
@@ -34,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);
|
||||
@@ -70,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) {
|
||||
@@ -79,7 +70,6 @@ bool XDPOpenWithDialog::exec() {
|
||||
}
|
||||
|
||||
const auto fdGuard = gsl::finally([&] { ::close(fd); });
|
||||
auto outFdList = Glib::RefPtr<Gio::UnixFDList>();
|
||||
|
||||
const auto parentWindowId = [&]() -> Glib::ustring {
|
||||
std::stringstream result;
|
||||
@@ -115,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(
|
||||
[&](
|
||||
@@ -125,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",
|
||||
@@ -138,6 +130,10 @@ bool XDPOpenWithDialog::exec() {
|
||||
}
|
||||
});
|
||||
|
||||
const auto fdList = Gio::UnixFDList::create();
|
||||
fdList->append(fd);
|
||||
auto outFdList = Glib::RefPtr<Gio::UnixFDList>();
|
||||
|
||||
connection->call_sync(
|
||||
std::string(kXDGDesktopPortalObjectPath),
|
||||
std::string(kXDGDesktopPortalOpenURIInterface),
|
||||
@@ -159,14 +155,16 @@ bool XDPOpenWithDialog::exec() {
|
||||
},
|
||||
}),
|
||||
}),
|
||||
Glib::wrap(g_unix_fd_list_new_from_array(&fd, 1)),
|
||||
fdList,
|
||||
outFdList,
|
||||
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;
|
||||
@@ -176,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(
|
||||
|
||||
@@ -260,8 +260,17 @@ void MediaSlider::paintEvent(QPaintEvent *e) {
|
||||
: seekRect.height();
|
||||
const auto from = 0;
|
||||
const auto length = (horizontal ? width() : height());
|
||||
const auto mid = qRound(from + value * length);
|
||||
const auto till = std::max(mid, qRound(from + receivedTill * length));
|
||||
const auto alwaysSeekSize = horizontal
|
||||
? _st.seekSize.width()
|
||||
: _st.seekSize.height();
|
||||
const auto mid = _alwaysDisplayMarker
|
||||
? qRound(from
|
||||
+ (alwaysSeekSize / 2.)
|
||||
+ value * (length - alwaysSeekSize))
|
||||
: qRound(from + value * length);
|
||||
const auto till = horizontal
|
||||
? std::max(mid, qRound(from + receivedTill * length))
|
||||
: mid;
|
||||
const auto end = from + length;
|
||||
const auto activeFg = disabled
|
||||
? _st.activeFgDisabled
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ Manager::Manager(System *system)
|
||||
system->settingsChanged(
|
||||
) | rpl::start_with_next([=](ChangeType change) {
|
||||
settingsChanged(change);
|
||||
}, system->lifetime());
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
Manager::QueuedNotification::QueuedNotification(
|
||||
|
||||
@@ -126,6 +126,8 @@ private:
|
||||
|
||||
mutable QPixmap _hiddenUserpicPlaceholder;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
@@ -25,13 +25,20 @@ void Adaptive::setChatLayout(ChatLayout value) {
|
||||
_chatLayout = value;
|
||||
}
|
||||
|
||||
rpl::producer<> Adaptive::changed() const {
|
||||
rpl::producer<> Adaptive::value() const {
|
||||
return rpl::merge(
|
||||
Core::App().settings().adaptiveForWideValue() | rpl::to_empty,
|
||||
_chatLayout.changes() | rpl::to_empty,
|
||||
_layout.changes() | rpl::to_empty);
|
||||
}
|
||||
|
||||
rpl::producer<> Adaptive::changes() const {
|
||||
return rpl::merge(
|
||||
Core::App().settings().adaptiveForWideChanges() | rpl::to_empty,
|
||||
_chatLayout.changes() | rpl::to_empty,
|
||||
_layout.changes() | rpl::to_empty);
|
||||
}
|
||||
|
||||
rpl::producer<bool> Adaptive::oneColumnValue() const {
|
||||
return _layout.value(
|
||||
) | rpl::map([=] {
|
||||
|
||||
@@ -27,7 +27,8 @@ public:
|
||||
void setWindowLayout(WindowLayout value);
|
||||
void setChatLayout(ChatLayout value);
|
||||
|
||||
[[nodiscard]] rpl::producer<> changed() const;
|
||||
[[nodiscard]] rpl::producer<> value() const;
|
||||
[[nodiscard]] rpl::producer<> changes() const;
|
||||
[[nodiscard]] rpl::producer<bool> oneColumnValue() const;
|
||||
[[nodiscard]] rpl::producer<ChatLayout> chatLayoutValue() const;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user