Compare commits

...

77 Commits

Author SHA1 Message Date
John Preston
f09b91ebb5 Beta version 2.8.5: Fix build on non-Windows. 2021-07-02 04:45:58 +03:00
John Preston
57b147e0c8 Beta version 2.8.5.
- Use ANGLE for OpenGL over DirectX 9 / DirectX 11 (Windows).
- Use GTK from a child process (Linux).
2021-07-02 00:59:51 +03:00
Ilya Fedin
551ea7d879 Move GTK integration out of process with D-Bus 2021-07-02 00:59:36 +03:00
John Preston
d3c9bb0bc6 Try disabling native child OpenGL workaround. 2021-07-02 00:37:27 +03:00
John Preston
c711b1f1df Fix build on non-Linux systems. 2021-07-02 00:27:25 +03:00
23rd
af7ea90246 Added floating panel of playing playlist for scheduled audio files. 2021-07-01 23:53:45 +03:00
23rd
348cf4829c Added ability to scroll media in section of scheduled messages.
Fixed #8388.
2021-07-01 23:53:45 +03:00
23rd
4753a57091 Added ability to validate playlists in section of scheduled messages. 2021-07-01 23:53:45 +03:00
23rd
1a93f4fa4c Added ability to sparse id slices of scheduled media. 2021-07-01 23:53:45 +03:00
23rd
118fd187e3 Added abstract class for sparse ids slices. 2021-07-01 23:53:45 +03:00
23rd
baa47bde7f Removed unused MsgRange from SparseIdsSlice. 2021-07-01 23:53:45 +03:00
John Preston
18b48df9ce Allow to choose ANGLE backend. 2021-07-01 23:48:18 +03:00
John Preston
e71fc60d22 Use exact revision of ANGLE. 2021-07-01 23:48:16 +03:00
John Preston
148af59615 Don't check dll-s if "SetDefaultDllDirectories" is available. 2021-07-01 23:47:12 +03:00
John Preston
5b2db4112f Don't allow any .dll-s near Telegram.exe 2021-07-01 23:47:12 +03:00
John Preston
7cedc1f7a5 Add dynamic DirectX loading helper. 2021-07-01 23:47:08 +03:00
John Preston
6cea7d4a52 Fix YUV->RGB on D3D9 ANGLE backend. 2021-07-01 23:46:52 +03:00
John Preston
bd93aed393 Test build with statically linked ANGLE. 2021-07-01 23:46:52 +03:00
John Preston
348666de6d Use media viewer size hack only when required. 2021-07-01 23:46:52 +03:00
Ilya Fedin
47e32bebe4 Remove not really needed gtk scale factor query 2021-07-01 22:13:50 +03:00
Ilya Fedin
0b21c04489 Remove the copy of gtk file dialog 2021-07-01 22:13:50 +03:00
Ilya Fedin
85f013ebdb Revert "Avoid removing portal platformtheme plugin in snap"
This reverts commit 12db51fe75.
2021-07-01 22:13:50 +03:00
Ilya Fedin
832cc6ac69 Build Qt with gtk integration 2021-07-01 22:13:50 +03:00
Ilya Fedin
30ce049f51 Update submodules 2021-07-01 22:13:20 +03:00
Ilya Fedin
d42fb6d1b9 Switch from mallocng to jemalloc
Now it's known how to make it free the memory in an expected manner and it's better maintained
2021-07-01 22:13:20 +03:00
John Preston
cade53aa0a Version 2.8.4.
- Crash fixes in WebView on Windows.
2021-07-01 11:05:21 +03:00
GitHub Action
2fdcda7536 Update User-Agent for DNS to Chrome 91.0.4472.114. 2021-07-01 10:48:17 +03:00
Ilya Fedin
7e6439e4f8 Fix counting screen bottom point when restoring geometry 2021-06-30 00:27:39 +03:00
Ilya Fedin
f07ee7f590 Update lib_base and cmake_helpers 2021-06-29 17:35:39 +03:00
Ilya Fedin
02db4e01fa Get rid of qt5ct 2021-06-29 17:35:39 +03:00
Ilya Fedin
8d75078a42 Use Glib::MainLoop instead of QEventLoop in glib code 2021-06-29 15:10:08 +03:00
John Preston
0e25ef7524 Handle WinRT exceptions in WebView. 2021-06-29 14:08:50 +03:00
John Preston
8608d8aa4d Crash Fix: Destroy WebView before the container. 2021-06-29 11:07:47 +03:00
sammiee5311
c1a7332a5e Shorten if statement
Shorten if statement
2021-06-29 10:38:35 +03:00
Ilya Fedin
c3fb392906 Clean dbus-specific code in main_window_linux.h 2021-06-29 10:30:48 +03:00
Ilya Fedin
a59bfdb2f8 Fix handleNativeSurfaceChanged when dbus integration is disabled 2021-06-29 10:30:48 +03:00
Ilya Fedin
79f96480c2 Use QMenuBar instead of own global menu implementation on Linux 2021-06-29 10:30:48 +03:00
John Preston
60cbd96d91 Version 2.8.3.
- Fix crashes in OpenSSL on macOS.
2021-06-28 13:51:06 +03:00
John Preston
ee0400f1ac Version 2.8.2.
- Attempt to fix random crashes on macOS.

Fixes #16504.
2021-06-28 08:57:40 +03:00
John Preston
b8a3746558 Version 2.8.1: Fix NuGet WinRT header generation. 2021-06-26 13:26:39 +03:00
John Preston
14f25fc997 Version 2.8.1.
- Fix crash in audio player volume slider.
2021-06-26 13:03:40 +03:00
John Preston
27da6ee9eb Update patches revision in instructions. 2021-06-26 13:00:16 +03:00
John Preston
48d482006a Fix crash fix. 2021-06-26 12:33:18 +03:00
John Preston
9afee2620a Fix crash in vertical sliders.
Regression was introduced in 90ff8ecd0f.
2021-06-26 08:20:37 +03:00
John Preston
baca3047d4 Version 2.8: Fix build on Windows x64. 2021-06-24 18:39:41 +04:00
John Preston
43a5265e0c Version 2.8.
- Start video conferences from Voice Chats in any group.
- Share your screen or video from your camera
with up to 30 participants (limit to be increased soon).
- Talk without video with an unlimited number of participants.
- Create voice chats from the info page
of any group where you are an admin.
- Group video calls are supported natively on all devices,
including iPads and laptops.
2021-06-24 17:57:09 +04:00
John Preston
5519bb3523 Allow reporting private groups as well.
Fixes #7451.
2021-06-24 17:44:55 +04:00
John Preston
ff213d1386 Enable /LARGEADDRESSAWARE for 32 bit Windows build. 2021-06-24 17:44:55 +04:00
John Preston
55b3f99653 Fix new formatting mixing with emoji. 2021-06-24 17:44:55 +04:00
John Preston
8a6ff3f414 Add separator above volume control in voice chats. 2021-06-24 17:44:55 +04:00
John Preston
feb8624d05 Don't use private Hunspell headers. 2021-06-24 17:44:55 +04:00
John Preston
a2c33545d4 Improve some paddings. 2021-06-24 17:44:28 +04:00
23rd
7decf68122 Fixed possible crash in OverlayWidget when video continues from PiP. 2021-06-24 17:44:28 +04:00
John Preston
6b62ec97c6 Fix possible crash in export panel management. 2021-06-24 11:57:27 +04:00
Ilya Fedin
ea3dab4a06 Update lib_base 2021-06-24 11:26:24 +04:00
Ilya Fedin
5c8f08fc92 Move preview support from QGtkDialog to GtkFileDialog 2021-06-24 11:26:24 +04:00
Ilya Fedin
00a0b2c8b6 Get rid of GTK cast templates 2021-06-24 11:26:24 +04:00
Ilya Fedin
007218cc13 Use C++ wrappers in GtkOpenWithDialog 2021-06-24 11:26:24 +04:00
Ilya Fedin
8afe495a4f Avoid using g_unix_fd_list_new_from_array 2021-06-24 11:26:24 +04:00
Ilya Fedin
257f2086d1 Get rid of gtk2 header compatibility 2021-06-24 11:26:24 +04:00
Ilya Fedin
f011c84ce8 Make Linux file dialog API better 2021-06-24 11:26:24 +04:00
Ilya Fedin
8b839f46b2 Fix crash report window scale 2021-06-24 11:26:08 +04:00
23rd
c1067d8fe1 Fixed possible crash in notifications manager. 2021-06-24 11:25:36 +04:00
23rd
bb76818cc8 Split adaptive changed rpl::producer into two. 2021-06-24 11:25:35 +04:00
John Preston
5dcc219f1c For large video tile always request full quality. 2021-06-24 10:57:23 +04:00
John Preston
4ff9e90153 Add some assertions and logging for a crash debugging. 2021-06-24 10:49:01 +04:00
John Preston
28fe98af80 Add some assertions for a strange crash debugging. 2021-06-24 10:24:52 +04:00
John Preston
5eba65aaa0 Remove unused legacy protocol code. 2021-06-24 09:55:57 +04:00
John Preston
d1e3e7d240 Don't show pinned tooltips if only one video. 2021-06-23 20:14:49 +04:00
John Preston
90ff8ecd0f Fix volume slider in voice chats. 2021-06-23 20:14:22 +04:00
John Preston
468e75a572 Update submodules. 2021-06-23 20:13:19 +04:00
John Preston
ae3e5487d7 Fix editing messages with monospace parts. 2021-06-23 15:55:29 +04:00
John Preston
03147a5426 Fix possible crash in case of API error. 2021-06-23 14:29:38 +04:00
John Preston
14a2b10989 Show error if camera could not be enabled. 2021-06-23 12:04:05 +04:00
John Preston
b29f8aa1e6 Remove background over highlight in volume change item. 2021-06-23 11:07:23 +04:00
John Preston
f9bb932cd8 Fix voice chat window expanding near the screen edges. 2021-06-23 10:52:04 +04:00
John Preston
a38cbbf7e8 Fix disappearing icons after popup menu display. 2021-06-23 10:43:51 +04:00
119 changed files with 1957 additions and 2271 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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)

View File

@@ -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.";

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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());
}

View File

@@ -61,6 +61,7 @@ enum class VideoQuality {
enum class Error {
NoCamera,
CameraFailed,
ScreenFailed,
MutedNoCamera,
MutedNoScreen,

View File

@@ -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)) {

View File

@@ -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());
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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);

View File

@@ -744,7 +744,7 @@ FieldAutocomplete::Inner::Inner(
update();
}, lifetime());
controller->adaptive().changed(
controller->adaptive().value(
) | rpl::start_with_next([=] {
_isOneColumn = controller->adaptive().isOneColumn();
update();

View File

@@ -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();
};

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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;

View 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;
};

View File

@@ -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);
}
};

View File

@@ -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);

View File

@@ -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(

View File

@@ -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;
};

View File

@@ -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)));
});
};
}
}

View File

@@ -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;

View File

@@ -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);
});
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -266,7 +266,7 @@ Widget::Widget(
}, lifetime());
}
controller->adaptive().changed(
controller->adaptive().changes(
) | rpl::start_with_next([=] {
updateForwardBar();
}, lifetime());

View File

@@ -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() {

View File

@@ -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());

View File

@@ -413,7 +413,7 @@ HistoryWidget::HistoryWidget(
Window::ActivateWindow(controller);
});
controller->adaptive().changed(
controller->adaptive().changes(
) | rpl::start_with_next([=] {
if (_history) {
_history->forceFullResize();

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());

View File

@@ -117,7 +117,7 @@ TopBarWidget::TopBarWidget(
_search->setForceRippled(searchInActiveChat, animated);
}, lifetime());
controller->adaptive().changed(
controller->adaptive().changes(
) | rpl::start_with_next([=] {
updateAdaptiveLayout();
}, lifetime());

View File

@@ -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(

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);
}
};

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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,

View File

@@ -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

View File

@@ -92,9 +92,6 @@ public:
[[nodiscard]] virtual bool needHttpWait() {
return false;
}
[[nodiscard]] virtual bool requiresExtendedPadding() const {
return false;
}
[[nodiscard]] virtual int32 debugState() const = 0;

View File

@@ -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;

View File

@@ -35,7 +35,6 @@ public:
int16 protocolDcId,
bool protocolForFiles) override;
bool isConnected() const override;
bool requiresExtendedPadding() const override;
int32 debugState() const override;

View File

@@ -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);

View File

@@ -36,7 +36,6 @@ public:
bool protocolForFiles) override;
void timedOut() override;
bool isConnected() const override;
bool requiresExtendedPadding() const override;
int32 debugState() const override;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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]));

View File

@@ -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

View File

@@ -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;

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 &parameters,
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 &parameters,
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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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();

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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

View File

@@ -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 &parameters) {
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

View File

@@ -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 &parameters) {
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 &parameters) {
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
}

View File

@@ -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
};

View File

@@ -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

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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(

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -76,7 +76,7 @@ Manager::Manager(System *system)
system->settingsChanged(
) | rpl::start_with_next([=](ChangeType change) {
settingsChanged(change);
}, system->lifetime());
}, _lifetime);
}
Manager::QueuedNotification::QueuedNotification(

View File

@@ -126,6 +126,8 @@ private:
mutable QPixmap _hiddenUserpicPlaceholder;
rpl::lifetime _lifetime;
};
namespace internal {

View File

@@ -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([=] {

View File

@@ -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