Compare commits

..

68 Commits

Author SHA1 Message Date
John Preston
e206f42e4e Beta version 3.7.4: Update tg_owt in snap. 2022-05-04 12:34:22 +04:00
John Preston
3f60410190 Beta version 3.7.4: Fix build with GCC in Release. 2022-05-04 11:49:11 +04:00
John Preston
3cdd8558db Beta version 3.7.4: Fix build with GCC. 2022-05-04 11:42:54 +04:00
John Preston
f2b89445ae Allow playing single lottie icon repeatedly or once. 2022-05-04 11:39:53 +04:00
23rd
3ff4bf77e7 Removed repeated animation from some settings lottie icons. 2022-05-04 07:32:52 +03:00
John Preston
d16ccf0d9e Beta version 3.7.4: Fix build with Xcode. 2022-05-03 23:33:41 +04:00
John Preston
fc7d9b264f Add webview-debug-enabled option. 2022-05-03 23:30:54 +04:00
John Preston
b28d5a63d1 Handle alert/confirm/prompt with custom dialog on Linux. 2022-05-03 23:30:42 +04:00
John Preston
043ba4ff04 Handle alert/confirm/prompt with custom dialog on macOS. 2022-05-03 23:30:21 +04:00
John Preston
d6c3bf4168 Use EdgeChromium by default on Windows. 2022-05-03 23:29:04 +04:00
John Preston
7bf7a8feff Beta version 3.7.4.
- Improve some more sections design.
- Update the OpenAL library to 1.22.0.
2022-05-03 23:09:26 +04:00
John Preston
3413ad1d22 Add some more icons for folders. 2022-05-03 23:09:26 +04:00
John Preston
84af084a3b Update tg_owt to WebRTC M101. 2022-05-03 23:09:26 +04:00
23rd
cd50008429 Moved plus icon to settings style file. 2022-05-03 21:59:38 +03:00
23rd
767459ab57 Slightly improved style of button in settings of blocked peers. 2022-05-03 21:59:38 +03:00
23rd
3b45a120e6 Slightly improved style of buttons and icons in Folders settings. 2022-05-03 21:59:38 +03:00
23rd
b04aaba8d0 Slightly improved style of box for edit folder. 2022-05-03 21:59:38 +03:00
23rd
bfa3655c7b Removed unused code of box for filters choosing. 2022-05-03 21:59:38 +03:00
CrisMystik
8642eb23a7 Removed unneeded part of code 2022-05-03 16:35:54 +04:00
CrisMystik
ef8ecc546b Use ::Settings::Main::Id() as fallback 2022-05-03 16:35:54 +04:00
CrisMystik
fafbbb4996 Suggested code improvements 2022-05-03 16:35:54 +04:00
CrisMystik
3a021f4e49 Make type variable const 2022-05-03 16:35:54 +04:00
CrisMystik
2788c19c85 Add some missing includes 2022-05-03 16:35:54 +04:00
CrisMystik
300cc3dbca Support all Settings links 2022-05-03 16:35:54 +04:00
GitHub Action
62516e264d Update User-Agent for DNS to Chrome 100.0.4896.127. 2022-05-03 16:24:54 +04:00
John Preston
8b89cfc4cb Fix custom notification sounds for all chats. 2022-05-03 16:23:39 +04:00
Ilya Fedin
e3f65d2346 Convet font point size to pixel size more like Qt does in the crash reporter 2022-05-03 15:16:20 +04:00
Andrew Krasavin
2b383a4236 Explicitly specify QVector element type to fix build with clang13+rangev3+qt6
More info:
https://github.com/telegramdesktop/tdesktop/issues/24385
https://github.com/telegramdesktop/tdesktop/issues/24014
https://github.com/ericniebler/range-v3/issues/1691
2022-05-03 15:15:20 +04:00
John Preston
a8426bd6da Update submodules. 2022-05-03 14:58:13 +04:00
John Preston
39a02e649d Fix crash on invalid data in local cache. 2022-05-03 14:52:08 +04:00
John Preston
276fe2169a Use SeparatePanel / ShowMultilineToast from lib_ui. 2022-05-03 14:52:08 +04:00
23rd
8fae56bee8 Improved style of Folders settings. 2022-05-03 04:25:36 +03:00
23rd
0e16a50bbc Completely removed common and platform code for tray from main window. 2022-05-03 04:25:36 +03:00
23rd
9de372d715 Replaced macOS Qt tray with native implementation. 2022-05-03 04:25:36 +03:00
23rd
fbae5bdbcf Removed macOS tray implementation from main window. 2022-05-03 04:25:36 +03:00
23rd
aee1ef78da Moved out static job for Linux tray icon to separated class. 2022-05-03 04:25:36 +03:00
23rd
27c5c4b8f2 Removed Linux tray implementation from main window. 2022-05-03 04:25:36 +03:00
23rd
94e06c6846 Removed Windows tray implementation from main window. 2022-05-03 04:25:36 +03:00
23rd
7948d971e8 Added initial implementation of Linux tray. 2022-05-03 04:25:36 +03:00
23rd
70acc7a0e3 Added initial implementation of Windows tray. 2022-05-03 04:25:36 +03:00
23rd
56fdc7d39a Added common and macOS tray implementations. 2022-05-03 04:25:36 +03:00
23rd
f67c3bbf65 Added placeholders for platform dependent implementations of tray. 2022-05-03 04:25:35 +03:00
23rd
de194c4aa2 Initialized empty files for tray implementations. 2022-05-03 04:25:35 +03:00
23rd
511805199f Added ability to check are windows active for tray menu. 2022-05-03 04:25:35 +03:00
23rd
aa241a1f62 Removed duplicated icons. 2022-05-03 04:25:35 +03:00
23rd
4125a45503 Slightly improved format of mute time in menu. 2022-05-03 04:25:35 +03:00
23rd
1349989494 Moved settings of blocked peers to section. 2022-05-03 04:25:35 +03:00
23rd
639ed8b973 Added ability to append pinned to top content to settings sections. 2022-05-02 22:31:05 +03:00
23rd
2f5db08c9b Fixed double scroll in reactions settings when height is small. 2022-05-02 22:31:05 +03:00
Ilya Fedin
4c6814def6 Replace style sheet in PreLaunchInput 2022-05-02 16:01:36 +04:00
Ilya Fedin
387914be31 Replace style sheet in Editor::Paint 2022-05-02 16:01:36 +04:00
Ilya Fedin
2f2003c89b Fix media viewer on Unity
This could be a regression for tiling WMs, though...
2022-05-02 16:00:56 +04:00
Ilya Fedin
48589b721d Update openal to 1.22.0 2022-05-02 15:33:56 +04:00
23rd
3bdf1634a9 Added ability to copy phone number from main menu. 2022-04-28 17:41:34 +03:00
23rd
1878061c9a Fixed color of attention menu items for account buttons. 2022-04-28 17:16:58 +03:00
23rd
774c3b5ba0 Fixed tab order in EditNameBox. 2022-04-28 17:10:13 +03:00
23rd
a64b8d4181 Slightly improved style of section for group stickers. 2022-04-28 16:27:39 +03:00
23rd
e3e380124d Removed ttl menu from inaccessible groups and channels. 2022-04-28 15:13:58 +03:00
John Preston
823fc25fa8 Fix layer height updating in poll results. 2022-04-27 15:20:19 +04:00
23rd
4062912a98 Added missed icon for join requests in manage of groups / channels. 2022-04-26 22:16:41 +04:00
23rd
62b5192f24 Added missed icon for channel type in manage of channels. 2022-04-26 22:16:39 +04:00
23rd
058717532a Replaced title static reaction icon in manage of groups / channels. 2022-04-26 22:16:38 +04:00
Sergey A. Osokin
d117a72e6e Fix -Wunused-const-variable warnings by removing unused variables
Fixes #24432
2022-04-26 21:25:05 +04:00
Sergey A. Osokin
3ba5b825e5 Fix -Wunused-const-variable warnings by removing unused variables
Fixes #24432
2022-04-26 21:25:05 +04:00
John Preston
075ab20e5b Version 3.7.3: Don't copy text from a restricted post. 2022-04-26 14:13:16 +04:00
John Preston
deeea0aaed Version 3.7.3: Update lib_webview. 2022-04-26 13:06:42 +04:00
John Preston
8113117cc4 Version 3.7.3.
- Fix a crash in the pinned bar bot button refresh.
2022-04-26 10:38:58 +04:00
John Preston
7bfe096f3b Fix possible crashes in pinned bar button. 2022-04-26 10:24:36 +04:00
143 changed files with 2708 additions and 2170 deletions

View File

@@ -990,6 +990,8 @@ PRIVATE
platform/linux/notifications_manager_linux.h
platform/linux/specific_linux.cpp
platform/linux/specific_linux.h
platform/linux/tray_linux.cpp
platform/linux/tray_linux.h
platform/mac/file_utilities_mac.mm
platform/mac/file_utilities_mac.h
platform/mac/launcher_mac.mm
@@ -1005,6 +1007,8 @@ PRIVATE
platform/mac/specific_mac.h
platform/mac/specific_mac_p.mm
platform/mac/specific_mac_p.h
platform/mac/tray_mac.mm
platform/mac/tray_mac.h
platform/mac/window_title_mac.mm
platform/mac/touchbar/items/mac_formatter_item.h
platform/mac/touchbar/items/mac_formatter_item.mm
@@ -1038,6 +1042,8 @@ PRIVATE
platform/win/notifications_manager_win.h
platform/win/specific_win.cpp
platform/win/specific_win.h
platform/win/tray_win.cpp
platform/win/tray_win.h
platform/win/windows_app_user_model_id.cpp
platform/win/windows_app_user_model_id.h
platform/win/windows_dlls.cpp
@@ -1054,6 +1060,7 @@ PRIVATE
platform/platform_main_window.h
platform/platform_notifications_manager.h
platform/platform_specific.h
platform/platform_tray.h
platform/platform_window_title.h
profile/profile_back_button.cpp
profile/profile_back_button.h
@@ -1067,6 +1074,8 @@ PRIVATE
profile/profile_cover_drop_area.h
settings/settings_advanced.cpp
settings/settings_advanced.h
settings/settings_blocked_peers.cpp
settings/settings_blocked_peers.h
settings/settings_chat.cpp
settings/settings_chat.h
settings/settings_calls.cpp
@@ -1258,6 +1267,8 @@ PRIVATE
settings.cpp
settings.h
stdafx.h
tray.cpp
tray.h
)
if (NOT build_winstore)

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -799,6 +799,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_blocked_list_confirm_text" = "Do you want to block {name} from messaging and calling you on Telegram?";
"lng_blocked_list_confirm_clear" = "Delete this chat";
"lng_blocked_list_confirm_ok" = "Block";
"lng_blocked_list_empty_title" = "No blocked users";
"lng_blocked_list_empty_description" = "You haven't blocked anyone yet.";
"lng_blocked_list_subtitle#one" = "{count} blocked user";
"lng_blocked_list_subtitle#other" = "{count} blocked users";
"lng_edit_privacy_everyone" = "Everybody";
"lng_edit_privacy_contacts" = "My contacts";

View File

@@ -2,4 +2,10 @@
<qresource prefix="/animations">
<file alias="change_number.tgs">../../animations/change_number.tgs</file>
</qresource>
<qresource prefix="/animations">
<file alias="blocked_peers_empty.tgs">../../animations/blocked_peers_empty.tgs</file>
</qresource>
<qresource prefix="/animations">
<file alias="filters.tgs">../../animations/filters.tgs</file>
</qresource>
</RCC>

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="3.7.2.0" />
Version="3.7.4.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 3,7,2,0
PRODUCTVERSION 3,7,2,0
FILEVERSION 3,7,4,0
PRODUCTVERSION 3,7,4,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "3.7.2.0"
VALUE "FileVersion", "3.7.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.7.2.0"
VALUE "ProductVersion", "3.7.4.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,7,2,0
PRODUCTVERSION 3,7,2,0
FILEVERSION 3,7,4,0
PRODUCTVERSION 3,7,4,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", "3.7.2.0"
VALUE "FileVersion", "3.7.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.7.2.0"
VALUE "ProductVersion", "3.7.4.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -83,7 +83,7 @@ MTPInputMedia PrepareUploadedPhoto(RemoteFileInfo info) {
MTP_flags(flags),
info.file,
MTP_vector<MTPInputDocument>(
ranges::to<QVector>(info.attachedStickers)),
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
MTP_int(0));
}
@@ -107,7 +107,7 @@ MTPInputMedia PrepareUploadedDocument(
MTP_string(document->mimeString()),
ComposeSendingDocumentAttributes(document),
MTP_vector<MTPInputDocument>(
ranges::to<QVector>(info.attachedStickers)),
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
MTP_int(0));
}

View File

@@ -1393,6 +1393,18 @@ void EditNameBox::prepare() {
connect(_first, &Ui::InputField::submitted, [=] { submit(); });
connect(_last, &Ui::InputField::submitted, [=] { submit(); });
_first->customTab(true);
_last->customTab(true);
QObject::connect(
_first,
&Ui::InputField::tabbed,
[=] { _last->setFocus(); });
QObject::connect(
_last,
&Ui::InputField::tabbed,
[=] { _first->setFocus(); });
}
void EditNameBox::setInnerFocus() {

View File

@@ -602,6 +602,9 @@ changePhoneError: FlatLabel(changePhoneLabel) {
textFg: boxTextFgError;
}
blockedUsersListSubtitleAddPadding: margins(0px, 1px, 0px, -14px);
blockedUsersListIconPadding: margins(0px, 34px, 0px, 5px);
adminLogFilterUserpicLeft: 15px;
adminLogFilterLittleSkip: 16px;
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {

View File

@@ -510,7 +510,7 @@ void ChangePhone::setupContent() {
}
void ChangePhone::showFinished() {
_animate();
_animate(anim::repeat::loop);
}
} // namespace Settings

View File

@@ -36,7 +36,7 @@ private:
void setupContent();
const not_null<Window::SessionController*> _controller;
Fn<void()> _animate;
Fn<void(anim::repeat)> _animate;
};

View File

@@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/filter_icons.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" // Ui::Text::Bold
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
@@ -27,46 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
class FolderButton : public Ui::SettingsButton {
public:
FolderButton(
not_null<Ui::RpWidget*> parent,
const Data::ChatFilter &filter);
protected:
void paintEvent(QPaintEvent *e) override;
private:
const Ui::FilterIcon _icon;
};
FolderButton::FolderButton(
not_null<Ui::RpWidget*> parent,
const Data::ChatFilter &filter)
: SettingsButton(
parent,
rpl::single(filter.title()),
st::paymentsSectionButton)
, _icon(Ui::ComputeFilterIcon(filter)) {
}
void FolderButton::paintEvent(QPaintEvent *e) {
SettingsButton::paintEvent(e);
Painter p(this);
const auto over = isOver() || isDown();
const auto icon = Ui::LookupFilterIcon(_icon).normal;
icon->paint(
p,
st::settingsFilterIconLeft,
(height() - icon->height()) / 2,
width(),
(over
? st::dialogsUnreadBgMutedOver
: st::dialogsUnreadBgMuted)->c);
}
Data::ChatFilter ChangedFilter(
const Data::ChatFilter &filter,
not_null<History*> history,
@@ -165,47 +124,6 @@ void ChooseFilterValidator::remove(FilterId filterId) const {
ChangeFilterById(filterId, _history, false);
}
void ChooseFilterBox(
not_null<Ui::GenericBox*> box,
not_null<History*> history) {
box->setTitle(tr::lng_filters_add_box_title());
const auto validator = ChooseFilterValidator(history);
const auto container = box->verticalLayout()->add(
object_ptr<Ui::VerticalLayout>(box->verticalLayout()));
const auto rebuild = [=] {
while (container->count()) {
delete container->widgetAt(0);
}
for (const auto &filter : history->owner().chatsFilters().list()) {
if (filter.contains(history)) {
continue;
}
container->add(
object_ptr<FolderButton>(box, filter),
style::margins()
)->setClickedCallback([=, id = filter.id()] {
validator.add(id);
box->closeBox();
});
}
container->resizeToWidth(box->verticalLayout()->width());
if (!container->count()) {
box->closeBox();
}
};
history->owner().chatsFilters().changed(
) | rpl::start_with_next([=] {
rebuild();
}, box->lifetime());
rebuild();
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
}
void FillChooseFilterMenu(
not_null<Ui::PopupMenu*> menu,
not_null<History*> history) {

View File

@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
namespace Ui {
class GenericBox;
class PopupMenu;
} // namespace Ui
@@ -29,10 +28,6 @@ private:
};
void ChooseFilterBox(
not_null<Ui::GenericBox*> box,
not_null<History*> history);
void FillChooseFilterMenu(
not_null<Ui::PopupMenu*> menu,
not_null<History*> history);

View File

@@ -500,6 +500,7 @@ void EditFilterBox(
const Data::ChatFilter &filter,
Fn<void(const Data::ChatFilter &)> doneCallback) {
const auto creating = filter.title().isEmpty();
box->setWidth(st::boxWideWidth);
box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit());
box->setCloseByOutsideClick(false);
@@ -571,8 +572,9 @@ void EditFilterBox(
const auto includeAdd = AddButton(
content,
tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
st::settingsUpdate);
tr::lng_filters_add_chats(),
st::settingsButtonActive,
{ &st::settingsIconAdd, 0, IconType::Round, &st::windowBgActive });
const auto include = SetupChatsPreview(
content,
@@ -582,21 +584,16 @@ void EditFilterBox(
&Data::ChatFilter::always);
AddSkip(content);
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_filters_include_about(),
st::boxDividerLabel),
st::windowFilterAboutPadding);
AddDivider(content);
AddDividerText(content, tr::lng_filters_include_about());
AddSkip(content);
AddSubsectionTitle(content, tr::lng_filters_exclude());
const auto excludeAdd = AddButton(
content,
tr::lng_filters_remove_chats() | Ui::Text::ToUpper(),
st::settingsUpdate);
tr::lng_filters_remove_chats(),
st::settingsButtonActive,
{ &st::settingsIconRemove, 0, IconType::Round, &st::windowBgActive });
const auto exclude = SetupChatsPreview(
content,
@@ -606,12 +603,7 @@ void EditFilterBox(
&Data::ChatFilter::never);
AddSkip(content);
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_filters_exclude_about(),
st::boxDividerLabel),
st::windowFilterAboutPadding);
AddDividerText(content, tr::lng_filters_exclude_about());
const auto refreshPreviews = [=] {
include->updateData(

View File

@@ -544,38 +544,36 @@ object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
Expects(_wrap != nullptr);
const auto channel = _peer->asChannel();
const auto bottomSkip = st::editPeerTopButtonsLayoutSkipCustomBottom;
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInvitesMargins);
object_ptr<Ui::VerticalLayout>(_wrap));
const auto container = result->entity();
container->add(object_ptr<Ui::FlatLabel>(
Settings::AddSubsectionTitle(
container,
tr::lng_group_stickers(),
st::editPeerSectionLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
{ 0, st::settingsSubsectionTitlePadding.top() - bottomSkip, 0, 0 });
container->add(object_ptr<Ui::FlatLabel>(
AddButtonWithCount(
container,
tr::lng_group_stickers_description(),
st::editPeerPrivacyLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
tr::lng_group_stickers_add(),
rpl::single(QString()), //Empty count.
[=, controller = _navigation->parentController()] {
controller->show(
Box<StickersBox>(controller, channel),
Ui::LayerOption::KeepOther);
},
{ &st::settingsIconStickers, Settings::kIconLightOrange });
container->add(object_ptr<Ui::LinkButton>(
_wrap,
tr::lng_group_stickers_add(tr::now),
st::editPeerInviteLinkButton)
)->addClickHandler([=] {
_navigation->parentController()->show(
Box<StickersBox>(_navigation->parentController(), channel),
Ui::LayerOption::KeepOther);
});
Settings::AddSkip(container, bottomSkip);
Settings::AddDividerText(
container,
tr::lng_group_stickers_description());
Settings::AddSkip(container, bottomSkip);
return result;
}
@@ -707,6 +705,9 @@ void Controller::fillPrivacyTypeButton() {
_noForwardsSavedValue = !_peer->allowsForwarding();
const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
const auto icon = isGroup
? &st::settingsIconGroup
: &st::settingsIconChannel;
AddButtonWithText(
_controls.buttonsLayout,
(hasLocation
@@ -729,7 +730,7 @@ void Controller::fillPrivacyTypeButton() {
: tr::lng_manage_private_peer_title)();
}) | rpl::flatten_latest(),
[=] { showEditPeerTypeBox(); },
{ &st::infoIconGroupType, Settings::kIconLightBlue });
{ icon, Settings::kIconLightBlue });
_privacyTypeUpdates.fire_copy(*_privacySavedValue);
}

View File

@@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h"
#include "info/profile/info_profile_icon.h"
#include "settings/settings_common.h"
#include "styles/style_settings.h"
#include "styles/style_info.h"
@@ -29,6 +28,7 @@ void EditAllowedReactionsBox(
const std::vector<Data::Reaction> &list,
const base::flat_set<QString> &selected,
Fn<void(const std::vector<QString> &)> callback) {
const auto iconHeight = st::editPeerReactionsPreview;
box->setTitle(tr::lng_manage_peer_reactions());
struct State {
@@ -57,10 +57,21 @@ void EditAllowedReactionsBox(
container,
tr::lng_manage_peer_reactions_enable(),
st::manageGroupButton.button);
Ui::CreateChild<Info::Profile::FloatingIcon>(
enabled.get(),
st::infoIconReactions,
st::manageGroupButton.iconPosition);
if (!list.empty()) {
AddReactionLottieIcon(
enabled,
enabled->sizeValue(
) | rpl::map([=](const QSize &size) {
return QPoint(
st::manageGroupButton.iconPosition.x(),
(size.height() - iconHeight) / 2);
}),
iconHeight,
list.front(),
rpl::never<>(),
rpl::never<>(),
&enabled->lifetime());
}
enabled->toggleOn(state->anyToggled.value());
enabled->toggledChanges(
) | rpl::filter([=](bool value) {
@@ -87,7 +98,6 @@ void EditAllowedReactionsBox(
container,
rpl::single(entry.title),
st::manageGroupButton.button);
const auto iconHeight = st::editPeerReactionsPreview;
AddReactionLottieIcon(
button,
button->sizeValue(
@@ -138,7 +148,7 @@ void SaveAllowedReactions(
const std::vector<QString> &allowed) {
auto ids = allowed | ranges::views::transform([=](QString value) {
return MTP_string(value);
}) | ranges::to<QVector>;
}) | ranges::to<QVector<MTPstring>>;
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
peer->input,

View File

@@ -89,12 +89,13 @@ AdminLog::OwnedItem GenerateItem(
}
void AddMessage(
not_null<Ui::GenericBox*> box,
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
rpl::producer<QString> &&emojiValue) {
rpl::producer<QString> &&emojiValue,
int width) {
const auto widget = box->addRow(
object_ptr<Ui::RpWidget>(box),
const auto widget = container->add(
object_ptr<Ui::RpWidget>(container),
style::margins(
0,
st::settingsSectionSkip,
@@ -121,7 +122,7 @@ void AddMessage(
bool flag = false;
} icons;
};
const auto state = box->lifetime().make_state<State>();
const auto state = container->lifetime().make_state<State>();
state->delegate = std::make_unique<Delegate>(
controller,
crl::guard(widget, [=] { widget->update(); }));
@@ -150,16 +151,18 @@ void AddMessage(
const auto padding = st::settingsForwardPrivacyPadding;
widget->widthValue(
) | rpl::filter(
rpl::mappers::_1 >= (st::historyMinimalWidth / 2)
) | rpl::start_with_next([=](int width) {
const auto updateWidgetSize = [=](int width) {
const auto height = view->resizeGetHeight(width);
const auto top = view->marginTop();
const auto bottom = view->marginBottom();
const auto full = padding + top + height + bottom + padding;
widget->resize(width, full);
}, widget->lifetime());
};
widget->widthValue(
) | rpl::filter(
rpl::mappers::_1 >= (st::historyMinimalWidth / 2)
) | rpl::start_with_next(updateWidgetSize, widget->lifetime());
updateWidgetSize(width);
const auto rightSize = st::settingsReactionCornerSize;
const auto rightRect = [=] {
@@ -224,7 +227,7 @@ void AddMessage(
const auto index = state->icons.flag ? 1 : 0;
state->icons.lifetimes[index] = rpl::lifetime();
AddReactionLottieIcon(
box->verticalLayout(),
container,
widget->geometryValue(
) | rpl::map([=](const QRect &r) {
return widget->pos()
@@ -388,34 +391,19 @@ void ReactionsSettingsBox(
const auto state = box->lifetime().make_state<State>();
state->selectedEmoji = reactions.favorite();
AddMessage(box, controller, state->selectedEmoji.value());
const auto pinnedToTop = box->setPinnedToTopContent(
object_ptr<Ui::VerticalLayout>(box));
auto emojiValue = state->selectedEmoji.value();
AddMessage(pinnedToTop, controller, std::move(emojiValue), box->width());
const auto container = box->verticalLayout();
Settings::AddSubsectionTitle(
container,
pinnedToTop,
tr::lng_settings_chat_reactions_subtitle());
const auto &stButton = st::settingsButton;
const auto scrollContainer = box->addRow(
object_ptr<Ui::FixedHeightWidget>(
box,
kVisibleButtonsCount
* (stButton.height
+ stButton.padding.top()
+ stButton.padding.bottom())),
style::margins());
const auto scroll = Ui::CreateChild<Ui::ScrollArea>(
scrollContainer,
st::boxScroll);
const auto buttonsContainer = scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(scroll));
scrollContainer->sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
scroll->resize(s.width(), s.height());
buttonsContainer->resizeToWidth(s.width());
}, scroll->lifetime());
const auto container = box->verticalLayout();
const auto check = Ui::CreateChild<Ui::RpWidget>(buttonsContainer.data());
const auto check = Ui::CreateChild<Ui::RpWidget>(container.get());
check->resize(st::settingsReactionCornerSize);
check->setAttribute(Qt::WA_TransparentForMouseEvents);
check->paintRequest(
@@ -432,9 +420,9 @@ void ReactionsSettingsBox(
auto firstCheckedButton = (Ui::RpWidget*)(nullptr);
for (const auto &r : reactions.list(Data::Reactions::Type::Active)) {
const auto button = Settings::AddButton(
buttonsContainer,
container,
rpl::single<QString>(base::duplicate(r.title)),
stButton);
st::settingsButton);
const auto iconSize = st::settingsReactionSize;
AddReactionLottieIcon(
@@ -472,11 +460,6 @@ void ReactionsSettingsBox(
}
check->raise();
Ui::SetupShadowsToScrollContent(
scrollContainer,
scroll,
buttonsContainer->heightValue());
box->setTitle(tr::lng_settings_chat_reactions_title());
box->setWidth(st::boxWideWidth);
box->addButton(tr::lng_settings_save(), [=] {

View File

@@ -39,7 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_menu_icons.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
#include "styles/style_settings.h"
namespace {
@@ -276,7 +276,7 @@ void RingtonesBox(
tr::lng_ringtones_box_upload_button(),
st::ringtonesBoxButton,
{
&st::mainMenuAddAccount,
&st::settingsIconAdd,
0,
Settings::IconType::Round,
&st::windowBgActive

View File

@@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace tgcalls {
class InstanceImpl;
class InstanceV2Impl;
class InstanceV2ReferenceImpl;
class InstanceV2_4_0_0Impl;
class InstanceImplLegacy;
void SetLegacyGlobalServerConfig(const std::string &serverConfig);
@@ -53,6 +54,7 @@ const auto kDefaultVersion = "2.4.4"_q;
const auto Register = tgcalls::Register<tgcalls::InstanceImpl>();
const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>();
const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();

View File

@@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_instance.h"
#include "inline_bots/bot_attach_web_view.h"
#include "mainwidget.h"
#include "tray.h"
#include "core/file_utilities.h"
#include "core/click_handler_types.h" // ClickHandlerContext.
#include "core/crash_reports.h"
@@ -149,6 +150,7 @@ Application::Application(not_null<Launcher*> launcher)
, _langpack(std::make_unique<Lang::Instance>())
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
, _tray(std::make_unique<Tray>())
, _autoLockTimer([=] { checkAutoLock(); }) {
Ui::Integration::Set(&_private->uiIntegration);
@@ -300,6 +302,8 @@ void Application::run() {
startShortcuts();
startDomain();
startTray();
_primaryWindow->widget()->show();
const auto currentGeometry = _primaryWindow->widget()->geometry();
@@ -409,6 +413,38 @@ void Application::startSystemDarkModeViewer() {
}, _lifetime);
}
void Application::startTray() {
using WindowRaw = not_null<Window::Controller*>;
const auto enumerate = [=](Fn<void(WindowRaw)> c) {
if (_primaryWindow) {
c(_primaryWindow.get());
}
for (const auto &window : ranges::views::values(_secondaryWindows)) {
c(window.get());
}
};
_tray->create();
_tray->aboutToShowRequests(
) | rpl::start_with_next([=] {
enumerate([&](WindowRaw w) { w->updateIsActive(); });
_tray->updateMenuText();
}, _primaryWindow->widget()->lifetime());
_tray->showFromTrayRequests(
) | rpl::start_with_next([=] {
const auto last = _lastActiveWindow;
enumerate([&](WindowRaw w) { w->widget()->showFromTray(); });
if (last) {
last->widget()->showFromTray();
}
}, _primaryWindow->widget()->lifetime());
_tray->hideToTrayRequests(
) | rpl::start_with_next([=] {
enumerate([&](WindowRaw w) { w->widget()->minimizeToTray(); });
}, _primaryWindow->widget()->lifetime());
}
auto Application::prepareEmojiSourceImages()
-> std::shared_ptr<Ui::Emoji::UniversalImages> {
const auto &images = Ui::Emoji::SourceImages();
@@ -427,6 +463,16 @@ void Application::clearEmojiSourceImages() {
});
}
bool Application::isActiveForTrayMenu() const {
if (_primaryWindow) {
return _primaryWindow->widget()->isActiveForTrayMenu();
}
return ranges::any_of(ranges::views::values(_secondaryWindows), [=](
const std::unique_ptr<Window::Controller> &controller) {
return controller->widget()->isActiveForTrayMenu();
});
}
bool Application::hideMediaView() {
if (_mediaView && !_mediaView->isHidden()) {
_mediaView->hide();

View File

@@ -103,6 +103,7 @@ namespace Core {
class Launcher;
struct LocalUrlHandler;
class Tray;
enum class LaunchState {
Running,
@@ -147,6 +148,9 @@ public:
[[nodiscard]] Data::DownloadManager &downloadManager() const {
return *_downloadManager;
}
[[nodiscard]] Tray &tray() const {
return *_tray;
}
// Windows interface.
bool hasActiveWindow(not_null<Main::Session*> session) const;
@@ -163,6 +167,7 @@ public:
[[nodiscard]] QWidget *getFileDialogParent();
void notifyFileDialogShown(bool shown);
void checkSystemDarkMode();
[[nodiscard]] bool isActiveForTrayMenu() const;
// Media view interface.
void checkMediaViewActivation();
@@ -316,6 +321,7 @@ private:
void startDomain();
void startEmojiImageLoader();
void startSystemDarkModeViewer();
void startTray();
friend void QuitAttempt();
void quitDelayed();
@@ -376,6 +382,8 @@ private:
std::unique_ptr<Lang::Translator> _translator;
QPointer<Ui::BoxContent> _badProxyDisableBox;
const std::unique_ptr<Tray> _tray;
std::unique_ptr<Media::Player::FloatController> _floatPlayers;
Media::Player::FloatDelegate *_defaultFloatPlayerDelegate = nullptr;
Media::Player::FloatDelegate *_replacementFloatPlayerDelegate = nullptr;

View File

@@ -121,6 +121,14 @@ std::map<int, const char*> BetaLogs() {
"- Fix group and channel photo upload.\n"
"- Test hardware video decoding.\n"
},
{
3007004,
"- More icons for chat folders.\n"
"- Improve some more sections design.\n"
"- Update the OpenAL library to 1.22.0.\n"
}
};
};

View File

@@ -44,17 +44,9 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
p.setColor(QPalette::Window, QColor(255, 255, 255));
setPalette(p);
constexpr auto processDpi = [](const QDpi &dpi) {
return (dpi.first + dpi.second) * 0.5;
};
const auto screen = QGuiApplication::primaryScreen();
const auto scale = processDpi(screen->handle()->logicalDpi())
/ processDpi(screen->handle()->logicalBaseDpi());
auto font = QGuiApplication::font();
font.setPixelSize(base::SafeRound(font.pointSize() * scale));
const auto dpi = screen()->handle()->logicalDpi().second;
auto font = this->font();
font.setPixelSize(base::SafeRound(std::floor(font.pointSizeF() * dpi / 72. * 100. + 0.5) / 100.));
_size = QFontMetrics(font).height();
int paddingVertical = (_size / 2);
@@ -110,12 +102,12 @@ PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(paren
setFont(logFont);
QPalette p(palette());
p.setColor(QPalette::Window, QColor(255, 255, 255));
p.setColor(QPalette::Base, QColor(255, 255, 255));
p.setColor(QPalette::WindowText, QColor(0, 0, 0));
p.setColor(QPalette::Text, QColor(0, 0, 0));
setPalette(p);
setStyleSheet("QLineEdit { background-color: white; }");
QLineEdit::setTextMargins(0, 0, 0, 0);
setContentsMargins(0, 0, 0, 0);
if (password) {

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
#include "boxes/change_phone_box.h"
#include "passport/passport_form_controller.h"
#include "window/window_session_controller.h"
#include "ui/toast/toast.h"
@@ -40,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h"
#include "settings/settings_folders.h"
#include "settings/settings_main.h"
#include "settings/settings_privacy_security.h"
#include "settings/settings_chat.h"
#include "mainwidget.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
@@ -444,22 +447,30 @@ bool ResolveSettings(
}
controller->window().activate();
const auto section = match->captured(1).mid(1).toLower();
if (section.isEmpty()) {
controller->window().showSettings();
return true;
} else if (section == qstr("language")) {
ShowLanguagesBox();
return true;
} else if (section == qstr("devices")) {
controller->session().api().authorizations().reload();
const auto type = [&]() -> std::optional<::Settings::Type> {
if (section == qstr("language")) {
ShowLanguagesBox();
return {};
} else if (section == qstr("devices")) {
controller->session().api().authorizations().reload();
return ::Settings::Sessions::Id();
} else if (section == qstr("folders")) {
return ::Settings::Folders::Id();
} else if (section == qstr("privacy")) {
return ::Settings::PrivacySecurity::Id();
} else if (section == qstr("themes")) {
return ::Settings::Chat::Id();
} else if (section == qstr("change_number")) {
return ::Settings::ChangePhone::Id();
}
return ::Settings::Main::Id();
}();
if (type.has_value()) {
controller->showSettings(*type);
controller->window().activate();
}
const auto type = (section == qstr("folders"))
? ::Settings::Folders::Id()
: (section == qstr("devices"))
? ::Settings::Sessions::Id()
: ::Settings::Main::Id();
controller->showSettings(type);
controller->window().activate();
return true;
}
@@ -760,7 +771,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
ResolvePrivatePost
},
{
qsl("^settings(/folders|/devices|/language)?$"),
qsl("^settings(/language|/devices|/folders|/privacy|/themes|/change_number)?$"),
ResolveSettings
},
{

View File

@@ -324,6 +324,14 @@ QString UiIntegration::phraseFormattingSpoiler() {
return tr::lng_menu_formatting_spoiler(tr::now);
}
QString UiIntegration::phraseButtonOk() {
return tr::lng_box_ok(tr::now);
}
QString UiIntegration::phraseButtonCancel() {
return tr::lng_cancel(tr::now);
}
bool OpenGLLastCheckFailed() {
return QFile::exists(OpenGLCheckFilePath());
}

View File

@@ -71,6 +71,8 @@ public:
QString phraseFormattingStrikeOut() override;
QString phraseFormattingMonospace() override;
QString phraseFormattingSpoiler() override;
QString phraseButtonOk() override;
QString phraseButtonCancel() 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 = 3007002;
constexpr auto AppVersionStr = "3.7.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 3007004;
constexpr auto AppVersionStr = "3.7.4";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -161,25 +161,7 @@ void NotifySettings::updateLocal(not_null<PeerData*> peer) {
} else {
_mutedPeers.erase(peer);
}
if (const auto sound = peer->notifySound(); sound && sound->id) {
if (const auto doc = _owner->document(sound->id); !doc->isNull()) {
cacheSound(doc);
} else {
_ringtones.pendingIds.push_back(sound->id);
if (!_ringtones.pendingLifetime) {
// Not requested yet.
_owner->session().api().ringtones().listUpdates(
) | rpl::start_with_next([=] {
for (const auto id : base::take(_ringtones.pendingIds)) {
cacheSound(id);
}
_ringtones.pendingLifetime.destroy();
}, _ringtones.pendingLifetime);
_owner->session().api().ringtones().requestList();
}
}
}
cacheSound(peer->notifySound());
}
void NotifySettings::cacheSound(DocumentId id) {
@@ -196,6 +178,28 @@ void NotifySettings::cacheSound(not_null<DocumentData*> document) {
document->save(Data::FileOriginRingtones(), QString());
}
void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) {
if (!sound || !sound->id) {
return;
} else if (const auto doc = _owner->document(sound->id); !doc->isNull()) {
cacheSound(doc);
return;
}
_ringtones.pendingIds.push_back(sound->id);
if (_ringtones.pendingLifetime) {
return;
}
// Not requested yet.
_owner->session().api().ringtones().listUpdates(
) | rpl::start_with_next([=] {
for (const auto id : base::take(_ringtones.pendingIds)) {
cacheSound(id);
}
_ringtones.pendingLifetime.destroy();
}, _ringtones.pendingLifetime);
_owner->session().api().ringtones().requestList();
}
void NotifySettings::updateLocal(DefaultNotify type) {
defaultValue(type).updates.fire({});
@@ -220,6 +224,7 @@ void NotifySettings::updateLocal(DefaultNotify type) {
_owner->enumerateBroadcasts(callback);
break;
}
cacheSound(defaultValue(type).settings.sound());
}
std::shared_ptr<DocumentMedia> NotifySettings::lookupRingtone(

View File

@@ -71,6 +71,8 @@ private:
rpl::event_stream<> updates;
};
void cacheSound(const std::optional<NotifySound> &sound);
[[nodiscard]] bool isMuted(
not_null<const PeerData*> peer,
crl::time *changesIn) const;

View File

@@ -27,11 +27,6 @@ namespace {
constexpr auto kMaxBrush = 25.;
constexpr auto kMinBrush = 1.;
constexpr auto kViewStyle = "QGraphicsView {\
background-color: transparent;\
border: 0px\
}"_cs;
std::shared_ptr<Scene> EnsureScene(
PhotoModifications &mods,
const QSize &size) {
@@ -62,7 +57,8 @@ Paint::Paint(
_view->show();
_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_view->setStyleSheet(kViewStyle.utf8());
_view->setFrameStyle(int(QFrame::NoFrame) | QFrame::Plain);
_view->viewport()->setAutoFillBackground(false);
// Undo / Redo.
controllers->undoController->performRequestChanges(

View File

@@ -1243,6 +1243,11 @@ bool ListWidget::hasCopyRestrictionForSelected() const {
if (hasCopyRestriction()) {
return true;
}
if (_selected.empty()) {
if (_selectedTextItem && _selectedTextItem->forbidsForward()) {
return true;
}
}
for (const auto &[itemId, selection] : _selected) {
if (const auto item = session().data().message(itemId)) {
if (item->forbidsForward()) {
@@ -1254,6 +1259,11 @@ bool ListWidget::hasCopyRestrictionForSelected() const {
}
bool ListWidget::showCopyRestrictionForSelected() {
if (_selected.empty()) {
if (_selectedTextItem && showCopyRestriction(_selectedTextItem)) {
return true;
}
}
for (const auto &[itemId, selection] : _selected) {
if (showCopyRestriction(session().data().message(itemId))) {
return true;

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_pinned_tracker.h"
#include "history/history_item.h"
#include "history/history.h"
#include "base/weak_ptr.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
@@ -163,27 +164,32 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
consumer.put_next(nullptr);
struct State {
HistoryMessageReplyMarkup *previousReplyMarkup = nullptr;
bool hasReplyMarkup = false;
base::has_weak_ptr guard;
rpl::lifetime lifetime;
FullMsgId resolvedId;
};
const auto state = lifetime.make_state<State>();
const auto pushUnique = [=](not_null<HistoryItem*> item) {
const auto replyMarkup = item->inlineReplyMarkup();
if (state->previousReplyMarkup == replyMarkup) {
if (!state->hasReplyMarkup && !replyMarkup) {
return;
}
state->hasReplyMarkup = (replyMarkup != nullptr);
consumer.put_next(item.get());
state->previousReplyMarkup = replyMarkup;
};
rpl::duplicate(
id
) | rpl::start_with_next([=](PinnedId current) {
) | rpl::filter([=](PinnedId current) {
return current.message && (current.message != state->resolvedId);
}) | rpl::start_with_next([=](PinnedId current) {
const auto fullId = current.message;
if (!fullId) {
return;
}
state->lifetime.destroy();
state->resolvedId = fullId;
invalidate_weak_ptrs(&state->guard);
const auto messageFlag = [=](not_null<HistoryItem*> item) {
using Update = Data::MessageUpdate;
session->changes().messageFlagsValue(
@@ -195,12 +201,17 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
};
if (const auto item = session->data().message(fullId)) {
messageFlag(item);
} else {
session->api().requestMessageData(
session->data().peer(fullId.peer),
fullId.msg,
[=] { messageFlag(session->data().message(fullId)); });
return;
}
const auto resolved = crl::guard(&state->guard, [=] {
if (const auto item = session->data().message(fullId)) {
messageFlag(item);
}
});
session->api().requestMessageData(
session->data().peer(fullId.peer),
fullId.msg,
resolved);
}, lifetime);
return lifetime;
});

View File

@@ -316,7 +316,7 @@ infoProfileSeparatorPadding: margins(
infoIconFg: windowBoldFg;
infoIconInformation: icon {{ "info/info_information", infoIconFg }};
infoIconRequests: icon {{ "info/info_add_member", infoIconFg }};
infoIconRequests: icon {{ "info/edit/group_manage_join_requests", infoIconFg }};
infoIconNotifications: icon {{ "info/info_notifications", infoIconFg }};
infoIconMediaPhoto: icon {{ "info/info_media_photo", infoIconFg }};
infoIconMediaVideo: icon {{ "info/info_media_video", infoIconFg }};
@@ -331,7 +331,6 @@ infoIconRecentActions: icon {{ "info/edit/group_manage_actions", settingsIconFg
infoIconAdministrators: icon {{ "info/edit/group_manage_admins", settingsIconFg }};
infoIconInviteLinks: icon {{ "info/edit/group_manage_links", settingsIconFg }};
infoIconReactions: icon {{ "info/edit/group_manage_reactions", settingsIconFg }};
infoIconGroupType: icon {{ "info/edit/group_manage_type", settingsIconFg }};
infoIconSignature: icon {{ "info/edit/channel_manage_signature", settingsIconFg }};
infoIconShare: icon {{ "info/info_share", infoIconFg }};
infoIconEdit: icon {{ "info/info_edit", infoIconFg }};
@@ -622,13 +621,6 @@ editPeerPrivacyBoxCheckbox: Checkbox(defaultBoxCheckbox) {
editPeerHistoryVisibilityLabelMargins: margins(34px, 0px, 48px, 0px);
editPeerPrivacyLabelMargins: margins(42px, 0px, 34px, 0px);
editPeerPreHistoryLabelMargins: margins(34px, 0px, 34px, 0px);
editPeerSectionLabel: FlatLabel(boxTitle) {
style: TextStyle(defaultTextStyle) {
font: font(15px semibold);
linkFont: font(15px semibold);
linkFontOver: font(15px semibold underline);
}
}
editPeerUsernameTitleLabelMargins: margins(22px, 17px, 22px, 10px);
editPeerUsernameFieldMargins: margins(22px, 0px, 22px, 20px);
editPeerUsername: setupChannelLink;
@@ -637,7 +629,6 @@ editPeerInviteLink: FlatLabel(defaultFlatLabel) {
minWidth: 1px; // for break everywhere
style: boxTextStyle;
}
editPeerInviteLinkButton: boxLinkButton;
editPeerUsernameGood: FlatLabel(defaultFlatLabel) {
textFg: boxTextFgGood;
style: boxTextStyle;
@@ -646,11 +637,6 @@ editPeerUsernameError: FlatLabel(editPeerUsernameGood) {
textFg: boxTextFgError;
}
editPeerUsernamePosition: point(22px, 18px);
editPeerInviteLinkSkip: 10px;
editPeerInvitesMargins: margins(22px, 17px, 22px, 16px);
editPeerInvitesTopSkip: 10px;
editPeerInvitesSkip: 10px;
editPeerInviteLinkBoxBottomSkip: 15px;
editPeerReactionsButton: SettingsButton(infoProfileButton) {
padding: margins(59px, 13px, 8px, 11px);
@@ -752,36 +738,6 @@ topBarConnectingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimatio
infoFeedLeaveIconMargins: margins(10px, 12px, 20px, 10px);
separatePanelBorderCacheSize: 60px;
separatePanelTitleHeight: 62px;
separatePanelClose: IconButton(boxTitleClose) {
width: 60px;
height: 60px;
rippleAreaPosition: point(8px, 8px);
rippleAreaSize: 44px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
separatePanelTitleFont: font(18px semibold);
separatePanelTitle: FlatLabel(defaultFlatLabel) {
textFg: boxTitleFg;
maxHeight: 26px;
style: TextStyle(defaultTextStyle) {
font: separatePanelTitleFont;
linkFont: separatePanelTitleFont;
linkFontOver: font(18px semibold underline);
}
}
separatePanelTitleTop: 18px;
separatePanelTitleLeft: 22px;
separatePanelTitleSkip: 0px;
separatePanelBack: IconButton(separatePanelClose) {
icon: infoTopBarBackIcon;
iconOver: infoTopBarBackIconOver;
}
inviteLinkField: FlatInput(defaultFlatInput) {
font: font(fsize);

View File

@@ -82,7 +82,11 @@ void LayerWidget::setupHeightConsumers() {
_content->scrollTillBottomChanges(
) | rpl::filter([this] {
return !_inResize;
if (!_inResize) {
return true;
}
_pendingResize = true;
return false;
}) | rpl::start_with_next([this] {
resizeToWidth(width());
}, lifetime());
@@ -125,8 +129,11 @@ void LayerWidget::setContentHeight(int height) {
if (_contentHeight == height) {
return;
}
_contentHeight = height;
if (_content && !_inResize) {
if (_inResize) {
_pendingResize = true;
} else if (_content) {
resizeToWidth(width());
}
}
@@ -240,9 +247,29 @@ int LayerWidget::resizeGetHeight(int newWidth) {
if (!parentWidget() || !_content) {
return 0;
}
_inResize = true;
auto guard = gsl::finally([&] { _inResize = false; });
constexpr auto kMaxAttempts = 5;
auto attempts = 0;
while (true) {
_inResize = true;
const auto newGeometry = countGeometry(newWidth);
_inResize = false;
if (!_pendingResize) {
const auto oldGeometry = geometry();
if (newGeometry != oldGeometry) {
_content->forceContentRepaint();
}
if (newGeometry.topLeft() != oldGeometry.topLeft()) {
move(newGeometry.topLeft());
}
floatPlayerUpdatePositions();
return newGeometry.height();
}
_pendingResize = false;
Assert(attempts++ < kMaxAttempts);
}
}
QRect LayerWidget::countGeometry(int newWidth) {
auto parentSize = parentWidget()->size();
auto windowWidth = parentSize.width();
auto windowHeight = parentSize.height();
@@ -282,16 +309,7 @@ int LayerWidget::resizeGetHeight(int newWidth) {
contentHeight,
}, expanding, additionalScroll);
auto newGeometry = QRect(newLeft, newTop, newWidth, desiredHeight);
if (newGeometry != geometry()) {
_content->forceContentRepaint();
}
if (newGeometry.topLeft() != geometry().topLeft()) {
move(newGeometry.topLeft());
}
floatPlayerUpdatePositions();
return desiredHeight;
return QRect(newLeft, newTop, newWidth, desiredHeight);
}
void LayerWidget::doSetInnerFocus() {

View File

@@ -69,6 +69,7 @@ private:
void setupHeightConsumers();
void setContentHeight(int height);
[[nodiscard]] QRect countGeometry(int newWidth);
not_null<Window::SessionController*> _controller;
object_ptr<WrapWidget> _content;
@@ -80,6 +81,7 @@ private:
Ui::Animations::Simple _savedHeightAnimation;
bool _heightAnimated = false;
bool _inResize = false;
bool _pendingResize = false;
bool _tillBottom = false;
bool _floatPlayerDelegateRestored = false;

View File

@@ -47,11 +47,25 @@ Widget::Widget(
, _type(controller->section().settingsType())
, _inner(
setInnerWidget(
_type()->create(this, controller->parentController()))) {
_type()->create(this, controller->parentController())))
, _pinnedToTop(_inner->createPinnedToTop(this)) {
_inner->sectionShowOther(
) | rpl::start_with_next([=](Type type) {
controller->showSettings(type);
}, _inner->lifetime());
if (_pinnedToTop) {
_inner->widthValue(
) | rpl::start_with_next([=](int w) {
_pinnedToTop->resizeToWidth(w);
setScrollTopSkip(_pinnedToTop->height());
}, _pinnedToTop->lifetime());
_pinnedToTop->heightValue(
) | rpl::start_with_next([=](int h) {
setScrollTopSkip(h);
}, _pinnedToTop->lifetime());
}
}
Widget::~Widget() = default;

View File

@@ -81,6 +81,7 @@ private:
Type _type = Type();
not_null<::Settings::AbstractSection*> _inner;
QPointer<Ui::RpWidget> _pinnedToTop;
};

View File

@@ -123,55 +123,6 @@ void MainWindow::initHook() {
}
}
void MainWindow::createTrayIconMenu() {
#ifdef Q_OS_WIN
trayIconMenu = new Ui::PopupMenu(nullptr);
trayIconMenu->deleteOnHide(false);
#else // Q_OS_WIN
trayIconMenu = new QMenu(this);
connect(trayIconMenu, &QMenu::aboutToShow, [=] {
updateIsActive();
updateTrayMenu();
});
#endif // else for Q_OS_WIN
const auto minimizeAction = trayIconMenu->addAction(QString(), [=] {
if (_activeForTrayIconAction) {
minimizeToTray();
} else {
showFromTrayMenu();
}
});
const auto notificationAction = trayIconMenu->addAction(QString(), [=] {
toggleDisplayNotifyFromTray();
});
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), [=] {
quitFromTray();
});
_updateTrayMenuTextActions.events(
) | rpl::start_with_next([=] {
if (!trayIconMenu) {
return;
}
_activeForTrayIconAction = isActiveForTrayMenu();
minimizeAction->setText(_activeForTrayIconAction
? tr::lng_minimize_to_tray(tr::now)
: tr::lng_open_from_tray(tr::now));
auto notificationActionText = Core::App().settings().desktopNotify()
? tr::lng_disable_notifications_from_tray(tr::now)
: tr::lng_enable_notifications_from_tray(tr::now);
notificationAction->setText(notificationActionText);
}, lifetime());
_updateTrayMenuTextActions.fire({});
initTrayMenuHook();
}
void MainWindow::applyInitialWorkMode() {
const auto workMode = Core::App().settings().workMode();
workmodeUpdated(workMode);
@@ -196,7 +147,6 @@ void MainWindow::applyInitialWorkMode() {
}
void MainWindow::finishFirstShow() {
createTrayIconMenu();
applyInitialWorkMode();
createGlobalMenu();
@@ -680,15 +630,6 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) {
return Platform::MainWindow::eventFilter(object, e);
}
void MainWindow::updateTrayMenu() {
if (!trayIconMenu) {
return;
}
_updateTrayMenuTextActions.fire({});
psTrayMenuUpdated();
}
bool MainWindow::takeThirdSectionFromLayer() {
return _layer ? _layer->takeToThirdSection() : false;
}
@@ -700,91 +641,6 @@ void MainWindow::fixOrder() {
if (_testingThemeWarning) _testingThemeWarning->raise();
}
void MainWindow::handleTrayIconActication(
QSystemTrayIcon::ActivationReason reason) {
updateIsActive();
if (Platform::IsMac() && isActive()) {
if (trayIcon && !trayIcon->contextMenu()) {
showFromTray();
}
return;
}
if (reason == QSystemTrayIcon::Context) {
updateTrayMenu();
InvokeQueued(this, [=] {
psShowTrayMenu();
});
} else if (!skipTrayClick()) {
if (isActiveForTrayMenu()) {
minimizeToTray();
} else {
showFromTray();
}
_lastTrayClickTime = crl::now();
}
}
bool MainWindow::skipTrayClick() const {
return (_lastTrayClickTime > 0)
&& (crl::now() - _lastTrayClickTime
< QApplication::doubleClickInterval());
}
void MainWindow::toggleDisplayNotifyFromTray() {
if (controller().locked()) {
if (!isActive()) showFromTray();
Ui::show(Ui::MakeInformBox(tr::lng_passcode_need_unblock()));
return;
}
if (!sessionController()) {
return;
}
auto soundNotifyChanged = false;
auto flashBounceNotifyChanged = false;
auto &settings = Core::App().settings();
settings.setDesktopNotify(!settings.desktopNotify());
if (settings.desktopNotify()) {
if (settings.rememberedSoundNotifyFromTray()
&& !settings.soundNotify()) {
settings.setSoundNotify(true);
settings.setRememberedSoundNotifyFromTray(false);
soundNotifyChanged = true;
}
if (settings.rememberedFlashBounceNotifyFromTray()
&& !settings.flashBounceNotify()) {
settings.setFlashBounceNotify(true);
settings.setRememberedFlashBounceNotifyFromTray(false);
flashBounceNotifyChanged = true;
}
} else {
if (settings.soundNotify()) {
settings.setSoundNotify(false);
settings.setRememberedSoundNotifyFromTray(true);
soundNotifyChanged = true;
} else {
settings.setRememberedSoundNotifyFromTray(false);
}
if (settings.flashBounceNotify()) {
settings.setFlashBounceNotify(false);
settings.setRememberedFlashBounceNotifyFromTray(true);
flashBounceNotifyChanged = true;
} else {
settings.setRememberedFlashBounceNotifyFromTray(false);
}
}
Core::App().saveSettingsDelayed();
using Change = Window::Notifications::ChangeType;
auto &notifications = Core::App().notifications();
notifications.notifySettingsChanged(Change::DesktopEnabled);
if (soundNotifyChanged) {
notifications.notifySettingsChanged(Change::SoundEnabled);
}
if (flashBounceNotifyChanged) {
notifications.notifySettingsChanged(Change::FlashBounceEnabled);
}
}
void MainWindow::closeEvent(QCloseEvent *e) {
if (Core::Sandbox::Instance().isSavingSession() || Core::Quitting()) {
e->accept();
@@ -853,10 +709,7 @@ void MainWindow::activeChangedHook() {
}
}
MainWindow::~MainWindow() {
delete trayIcon;
delete trayIconMenu;
}
MainWindow::~MainWindow() = default;
namespace App {

View File

@@ -80,7 +80,6 @@ public:
}
void showMainMenu();
void updateTrayMenu() override;
void fixOrder() override;
void showLayer(
@@ -119,12 +118,6 @@ protected:
void clearWidgetsHook() override;
private:
[[nodiscard]] bool skipTrayClick() const;
void createTrayIconMenu();
void handleTrayIconActication(
QSystemTrayIcon::ActivationReason reason) override;
void applyInitialWorkMode();
void ensureLayerCreated();
void destroyLayer();
@@ -139,15 +132,11 @@ private:
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
void toggleDisplayNotifyFromTray();
QPixmap grabInner();
std::unique_ptr<Media::SystemMediaControlsManager> _mediaControlsManager;
crl::time _lastTrayClickTime = 0;
QPoint _lastMousePosition;
bool _activeForTrayIconAction = true;
object_ptr<Window::PasscodeLockWidget> _passcodeLock = { nullptr };
object_ptr<Intro::Widget> _intro = { nullptr };
@@ -157,8 +146,6 @@ private:
object_ptr<Window::Theme::WarningWidget> _testingThemeWarning = { nullptr };
rpl::event_stream<> _updateTrayMenuTextActions;
};
namespace App {

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio_track.h"
#include "media/audio/media_openal_functions.h"
#include "media/streaming/media_streaming_utility.h"
#include "webrtc/webrtc_media_devices.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
@@ -80,6 +81,10 @@ bool PlaybackErrorHappened() {
}
void EnumeratePlaybackDevices() {
if (!Webrtc::InitPipewireStubs()) {
LOG(("Audio Info: Failed to load pipewire 0.3 stubs."));
}
auto deviceNames = QStringList();
auto devices = [&] {
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) {

View File

@@ -537,8 +537,6 @@ void OverlayWidget::updateGeometry(bool inMove) {
.arg(use.width())
.arg(use.height()));
_widget->setGeometry(use);
_widget->setMinimumSize(use.size());
_widget->setMaximumSize(use.size());
if (possibleSizeHack) {
_widget->setMask(mask);
}

View File

@@ -108,9 +108,11 @@ bool TTLValidator::can() const {
&& !_peer->isNotificationsUser()
&& !_peer->asUser()->isInaccessible())
|| (_peer->isChat()
&& _peer->asChat()->canDeleteMessages())
&& _peer->asChat()->canDeleteMessages()
&& _peer->asChat()->amIn())
|| (_peer->isChannel()
&& _peer->asChannel()->canDeleteMessages());
&& _peer->asChannel()->canDeleteMessages()
&& _peer->asChannel()->amIn());
}
void TTLValidator::showToast() const {

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/100.0.4896.60 Safari/537.36");
"Chrome/100.0.4896.127 Safari/537.36");
return kResult;
}

View File

@@ -52,8 +52,9 @@ Launcher::Launcher(int argc, char *argv[])
int Launcher::exec() {
for (auto i = begin(_arguments), e = end(_arguments); i != e; ++i) {
if (*i == "-webviewhelper" && std::distance(i, e) > 1) {
Webview::WebKit2Gtk::SetSocketPath(*(i + 1));
if (*i == "-webviewhelper" && std::distance(i, e) > 2) {
Webview::WebKit2Gtk::SetDebug(*(i + 1));
Webview::WebKit2Gtk::SetSocketPath(*(i + 2));
return Webview::WebKit2Gtk::Exec();
}
}

View File

@@ -59,16 +59,6 @@ namespace {
using internal::WaylandIntegration;
using WorkMode = Core::Settings::WorkMode;
constexpr auto kPanelTrayIconName = "telegram-panel"_cs;
constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs;
constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs;
bool TrayIconMuted = true;
int32 TrayIconCount = 0;
base::flat_map<int, QImage> TrayIconImageBack;
QIcon TrayIcon;
QString TrayIconThemeName, TrayIconName;
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
void XCBSkipTaskbar(QWindow *window, bool skip) {
const auto connection = base::Platform::XCB::GetConnectionFromQt();
@@ -164,186 +154,6 @@ void SkipTaskbar(QWindow *window, bool skip) {
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
}
QString GetPanelIconName(int counter, bool muted) {
return (counter > 0)
? (muted
? kMutePanelTrayIconName.utf16()
: kAttentionPanelTrayIconName.utf16())
: kPanelTrayIconName.utf16();
}
QString GetTrayIconName(int counter, bool muted) {
const auto iconName = GetIconName();
const auto panelIconName = GetPanelIconName(counter, muted);
if (QIcon::hasThemeIcon(panelIconName)) {
return panelIconName;
} else if (QIcon::hasThemeIcon(iconName)) {
return iconName;
}
return QString();
}
int GetCounterSlice(int counter) {
return (counter >= 1000)
? (1000 + (counter % 100))
: counter;
}
bool IsIconRegenerationNeeded(
int counter,
bool muted,
const QString &iconThemeName = QIcon::themeName()) {
const auto iconName = GetTrayIconName(counter, muted);
const auto counterSlice = GetCounterSlice(counter);
return TrayIcon.isNull()
|| iconThemeName != TrayIconThemeName
|| iconName != TrayIconName
|| muted != TrayIconMuted
|| counterSlice != TrayIconCount;
}
void UpdateIconRegenerationNeeded(
const QIcon &icon,
int counter,
bool muted,
const QString &iconThemeName) {
const auto iconName = GetTrayIconName(counter, muted);
const auto counterSlice = GetCounterSlice(counter);
TrayIcon = icon;
TrayIconMuted = muted;
TrayIconCount = counterSlice;
TrayIconThemeName = iconThemeName;
TrayIconName = iconName;
}
QIcon TrayIconGen(int counter, bool muted) {
const auto iconThemeName = QIcon::themeName();
if (!IsIconRegenerationNeeded(counter, muted, iconThemeName)) {
return TrayIcon;
}
const auto iconName = GetTrayIconName(counter, muted);
const auto panelIconName = GetPanelIconName(counter, muted);
if (iconName == panelIconName) {
const auto result = QIcon::fromTheme(iconName);
UpdateIconRegenerationNeeded(result, counter, muted, iconThemeName);
return result;
}
QIcon result;
QIcon systemIcon;
static const auto iconSizes = {
16,
22,
24,
32,
48,
};
static const auto dprSize = [](const QImage &image) {
return image.size() / image.devicePixelRatio();
};
for (const auto iconSize : iconSizes) {
auto &currentImageBack = TrayIconImageBack[iconSize];
const auto desiredSize = QSize(iconSize, iconSize);
if (currentImageBack.isNull()
|| iconThemeName != TrayIconThemeName
|| iconName != TrayIconName) {
if (!iconName.isEmpty()) {
if (systemIcon.isNull()) {
systemIcon = QIcon::fromTheme(iconName);
}
// We can't use QIcon::actualSize here
// since it works incorrectly with svg icon themes
currentImageBack = systemIcon
.pixmap(desiredSize)
.toImage();
const auto firstAttemptSize = dprSize(currentImageBack);
// if current icon theme is not a svg one, Qt can return
// a pixmap that less in size even if there are a bigger one
if (firstAttemptSize.width() < desiredSize.width()) {
const auto availableSizes = systemIcon.availableSizes();
const auto biggestSize = ranges::max_element(
availableSizes,
std::less<>(),
&QSize::width);
if (biggestSize->width() > firstAttemptSize.width()) {
currentImageBack = systemIcon
.pixmap(*biggestSize)
.toImage();
}
}
} else {
currentImageBack = Window::Logo();
}
if (dprSize(currentImageBack) != desiredSize) {
currentImageBack = currentImageBack.scaled(
desiredSize * currentImageBack.devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
}
auto iconImage = currentImageBack;
if (counter > 0) {
const auto &bg = muted
? st::trayCounterBgMute
: st::trayCounterBg;
const auto &fg = st::trayCounterFg;
if (iconSize >= 22) {
const auto layerSize = (iconSize >= 48)
? 32
: (iconSize >= 36)
? 24
: (iconSize >= 32)
? 20
: 16;
const auto layer = Window::GenerateCounterLayer({
.size = layerSize,
.count = counter,
.bg = bg,
.fg = fg,
});
QPainter p(&iconImage);
p.drawImage(
iconImage.width() - layer.width() - 1,
iconImage.height() - layer.height() - 1,
layer);
} else {
iconImage = Window::WithSmallCounter(std::move(iconImage), {
.size = 16,
.count = counter,
.bg = bg,
.fg = fg,
});
}
}
result.addPixmap(Ui::PixmapFromImage(std::move(iconImage)));
}
UpdateIconRegenerationNeeded(result, counter, muted, iconThemeName);
return result;
}
void SendKeySequence(
Qt::Key key,
Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
@@ -445,48 +255,14 @@ void MainWindow::initHook() {
LOG(("System tray available: %1").arg(Logs::b(TrayIconSupported())));
}
bool MainWindow::hasTrayIcon() const {
return trayIcon;
}
bool MainWindow::isActiveForTrayMenu() {
updateIsActive();
return Platform::IsWayland() ? isVisible() : isActive();
}
void MainWindow::psShowTrayMenu() {
_trayIconMenuXEmbed->popup(QCursor::pos());
}
void MainWindow::psTrayMenuUpdated() {
}
void MainWindow::psSetupTrayIcon() {
if (!trayIcon) {
trayIcon = new QSystemTrayIcon(this);
trayIcon->setContextMenu(trayIconMenu);
trayIcon->setIcon(TrayIconGen(
Core::App().unreadBadge(),
Core::App().unreadBadgeMuted()));
attachToTrayIcon(trayIcon);
}
updateIconCounters();
trayIcon->show();
}
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
if (!TrayIconSupported()) {
return;
} else if (mode == WorkMode::WindowOnly) {
if (trayIcon) {
trayIcon->setContextMenu(0);
trayIcon->deleteLater();
}
trayIcon = nullptr;
} else {
psSetupTrayIcon();
}
SkipTaskbar(windowHandle(), mode == WorkMode::TrayOnly);
@@ -497,9 +273,6 @@ void MainWindow::unreadCounterChangedHook() {
}
void MainWindow::updateIconCounters() {
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
updateWindowIcon();
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
@@ -507,7 +280,7 @@ void MainWindow::updateIconCounters() {
const auto launcherUrl = Glib::ustring(
"application://"
+ QGuiApplication::desktopFileName().toStdString());
const auto counterSlice = std::min(counter, 9999);
const auto counterSlice = std::min(Core::App().unreadBadge(), 9999);
std::map<Glib::ustring, Glib::VariantBase> dbusUnityProperties;
if (counterSlice > 0) {
@@ -541,15 +314,6 @@ void MainWindow::updateIconCounters() {
}
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (trayIcon && IsIconRegenerationNeeded(counter, muted)) {
trayIcon->setIcon(TrayIconGen(counter, muted));
}
}
void MainWindow::initTrayMenuHook() {
_trayIconMenuXEmbed.emplace(nullptr, trayIconMenu);
_trayIconMenuXEmbed->deleteOnHide(false);
}
void MainWindow::createGlobalMenu() {
@@ -820,17 +584,8 @@ void MainWindow::updateGlobalMenuHook() {
}
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
QEvent::Type t = evt->type();
if (t == QEvent::MouseButtonPress
&& obj->objectName() == qstr("QSystemTrayIconSys")) {
const auto ee = static_cast<QMouseEvent*>(evt);
if (ee->button() == Qt::RightButton) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
handleTrayIconActication(QSystemTrayIcon::Context);
});
return true;
}
} else if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
const auto t = evt->type();
if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
if (qobject_cast<QLineEdit*>(obj)
|| qobject_cast<QTextEdit*>(obj)
|| dynamic_cast<HistoryInner*>(obj)) {

View File

@@ -22,8 +22,6 @@ class MainWindow : public Window::MainWindow {
public:
explicit MainWindow(not_null<Window::Controller*> controller);
void psShowTrayMenu();
bool isActiveForTrayMenu() override;
~MainWindow();
@@ -35,21 +33,10 @@ protected:
void unreadCounterChangedHook() override;
void updateGlobalMenuHook() override;
void initTrayMenuHook() override;
bool hasTrayIcon() const override;
void workmodeUpdated(Core::Settings::WorkMode mode) override;
void createGlobalMenu() override;
QSystemTrayIcon *trayIcon = nullptr;
QMenu *trayIconMenu = nullptr;
void psTrayMenuUpdated();
void psSetupTrayIcon();
private:
base::unique_qptr<Ui::PopupMenu> _trayIconMenuXEmbed;
QMenuBar *psMainMenu = nullptr;
QAction *psLogout = nullptr;
QAction *psUndo = nullptr;

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <QtWidgets/QApplication>
#include <QtWidgets/QSystemTrayIcon>
#include <QtCore/QStandardPaths>
#include <QtCore/QProcess>
#include <QtGui/QWindow>
@@ -64,7 +65,6 @@ constexpr auto kDesktopFile = ":/misc/telegramdesktop.desktop"_cs;
constexpr auto kIconName = "telegram"_cs;
constexpr auto kIBusPortalService = "org.freedesktop.portal.IBus"_cs;
constexpr auto kWebviewService = "org.telegram.desktop.GtkIntegration.WebviewHelper-%1-%2"_cs;
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
void PortalAutostart(bool start, bool silent) {

View File

@@ -0,0 +1,427 @@
/*
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/tray_linux.h"
#include "base/invoke_queued.h"
#include "base/qt_signal_producer.h"
#include "core/application.h"
#include "core/sandbox.h"
#include "platform/linux/specific_linux.h"
#include "ui/ui_utility.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "styles/style_window.h"
#include <QtCore/QCoreApplication>
#include <QtWidgets/QMenu>
#include <QtWidgets/QSystemTrayIcon>
namespace Platform {
namespace {
[[nodiscard]] QWidget *Parent() {
Expects(Core::App().primaryWindow() != nullptr);
return Core::App().primaryWindow()->widget();
}
} // namespace
class IconGraphic final {
public:
explicit IconGraphic();
~IconGraphic();
[[nodiscard]] bool isRefreshNeeded(
int counter,
bool muted,
const QString &iconThemeName) const;
[[nodiscard]] QIcon trayIcon(int counter, bool muted);
private:
[[nodiscard]] QString panelIconName(int counter, bool muted) const;
[[nodiscard]] QString trayIconName(int counter, bool muted) const;
[[nodiscard]] int counterSlice(int counter) const;
void updateIconRegenerationNeeded(
const QIcon &icon,
int counter,
bool muted,
const QString &iconThemeName);
[[nodiscard]] QSize dprSize(const QImage &image) const;
const QString _panelTrayIconName;
const QString _mutePanelTrayIconName;
const QString _attentionPanelTrayIconName;
const int _iconSizes[5];
bool _muted = true;
int32 _count = 0;
base::flat_map<int, QImage> _imageBack;
QIcon _trayIcon;
QString _themeName;
QString _name;
};
IconGraphic::IconGraphic()
: _panelTrayIconName("telegram-panel")
, _mutePanelTrayIconName("telegram-mute-panel")
, _attentionPanelTrayIconName("telegram-attention-panel")
, _iconSizes{ 16, 22, 24, 32, 48 } {
}
IconGraphic::~IconGraphic() = default;
QString IconGraphic::panelIconName(int counter, bool muted) const {
return (counter > 0)
? (muted
? _mutePanelTrayIconName
: _attentionPanelTrayIconName)
: _panelTrayIconName;
}
QString IconGraphic::trayIconName(int counter, bool muted) const {
const auto iconName = GetIconName();
const auto panelName = panelIconName(counter, muted);
if (QIcon::hasThemeIcon(panelName)) {
return panelName;
} else if (QIcon::hasThemeIcon(iconName)) {
return iconName;
}
return QString();
}
int IconGraphic::counterSlice(int counter) const {
return (counter >= 1000)
? (1000 + (counter % 100))
: counter;
}
bool IconGraphic::isRefreshNeeded(
int counter,
bool muted,
const QString &iconThemeName) const {
const auto iconName = trayIconName(counter, muted);
return _trayIcon.isNull()
|| iconThemeName != _themeName
|| iconName != _name
|| muted != _muted
|| counterSlice(counter) != _count;
}
void IconGraphic::updateIconRegenerationNeeded(
const QIcon &icon,
int counter,
bool muted,
const QString &iconThemeName) {
const auto iconName = trayIconName(counter, muted);
_trayIcon = icon;
_muted = muted;
_count = counterSlice(counter);
_themeName = iconThemeName;
_name = iconName;
}
QSize IconGraphic::dprSize(const QImage &image) const {
return image.size() / image.devicePixelRatio();
}
QIcon IconGraphic::trayIcon(int counter, bool muted) {
const auto iconThemeName = QIcon::themeName();
if (!isRefreshNeeded(counter, muted, iconThemeName)) {
return _trayIcon;
}
const auto iconName = trayIconName(counter, muted);
if (iconName == panelIconName(counter, muted)) {
const auto result = QIcon::fromTheme(iconName);
updateIconRegenerationNeeded(result, counter, muted, iconThemeName);
return result;
}
QIcon result;
QIcon systemIcon;
for (const auto iconSize : _iconSizes) {
auto &currentImageBack = _imageBack[iconSize];
const auto desiredSize = QSize(iconSize, iconSize);
if (currentImageBack.isNull()
|| iconThemeName != _themeName
|| iconName != _name) {
if (!iconName.isEmpty()) {
if (systemIcon.isNull()) {
systemIcon = QIcon::fromTheme(iconName);
}
// We can't use QIcon::actualSize here
// since it works incorrectly with svg icon themes
currentImageBack = systemIcon
.pixmap(desiredSize)
.toImage();
const auto firstAttemptSize = dprSize(currentImageBack);
// if current icon theme is not a svg one, Qt can return
// a pixmap that less in size even if there are a bigger one
if (firstAttemptSize.width() < desiredSize.width()) {
const auto availableSizes = systemIcon.availableSizes();
const auto biggestSize = ranges::max_element(
availableSizes,
std::less<>(),
&QSize::width);
if (biggestSize->width() > firstAttemptSize.width()) {
currentImageBack = systemIcon
.pixmap(*biggestSize)
.toImage();
}
}
} else {
currentImageBack = Window::Logo();
}
if (dprSize(currentImageBack) != desiredSize) {
currentImageBack = currentImageBack.scaled(
desiredSize * currentImageBack.devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
}
auto iconImage = currentImageBack;
if (counter > 0) {
const auto &bg = muted
? st::trayCounterBgMute
: st::trayCounterBg;
const auto &fg = st::trayCounterFg;
if (iconSize >= 22) {
const auto layerSize = (iconSize >= 48)
? 32
: (iconSize >= 36)
? 24
: (iconSize >= 32)
? 20
: 16;
const auto layer = Window::GenerateCounterLayer({
.size = layerSize,
.count = counter,
.bg = bg,
.fg = fg,
});
QPainter p(&iconImage);
p.drawImage(
iconImage.width() - layer.width() - 1,
iconImage.height() - layer.height() - 1,
layer);
} else {
iconImage = Window::WithSmallCounter(std::move(iconImage), {
.size = 16,
.count = counter,
.bg = bg,
.fg = fg,
});
}
}
result.addPixmap(Ui::PixmapFromImage(std::move(iconImage)));
}
updateIconRegenerationNeeded(result, counter, muted, iconThemeName);
return result;
}
class TrayEventFilter final : public QObject {
public:
TrayEventFilter(not_null<QObject*> parent);
[[nodiscard]] rpl::producer<> contextMenuFilters() const;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
const QString _iconObjectName;
rpl::event_stream<> _contextMenuFilters;
};
TrayEventFilter::TrayEventFilter(not_null<QObject*> parent)
: QObject(parent)
, _iconObjectName("QSystemTrayIconSys") {
parent->installEventFilter(this);
}
bool TrayEventFilter::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::MouseButtonPress
&& obj->objectName() == _iconObjectName) {
const auto m = static_cast<QMouseEvent*>(event);
if (m->button() == Qt::RightButton) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
_contextMenuFilters.fire({});
});
return true;
}
}
return false;
}
rpl::producer<> TrayEventFilter::contextMenuFilters() const {
return _contextMenuFilters.events();
}
Tray::Tray() {
}
void Tray::createIcon() {
if (!_icon) {
if (!_iconGraphic) {
_iconGraphic = std::make_unique<IconGraphic>();
}
const auto showXEmbed = [=] {
_aboutToShowRequests.fire({});
InvokeQueued(_menuXEmbed.get(), [=] {
_menuXEmbed->popup(QCursor::pos());
});
};
_icon = base::make_unique_q<QSystemTrayIcon>(Parent());
_icon->setIcon(_iconGraphic->trayIcon(
Core::App().unreadBadge(),
Core::App().unreadBadgeMuted()));
_icon->setToolTip(AppName.utf16());
using Reason = QSystemTrayIcon::ActivationReason;
base::qt_signal_producer(
_icon.get(),
&QSystemTrayIcon::activated
) | rpl::start_with_next([=](Reason reason) {
if (reason == QSystemTrayIcon::Context) {
showXEmbed();
} else {
_iconClicks.fire({});
}
}, _lifetime);
_icon->setContextMenu(_menu.get());
if (!_eventFilter) {
_eventFilter = base::make_unique_q<TrayEventFilter>(
QCoreApplication::instance());
_eventFilter->contextMenuFilters(
) | rpl::start_with_next([=] {
showXEmbed();
}, _lifetime);
}
}
updateIcon();
_icon->show();
}
void Tray::destroyIcon() {
_icon = nullptr;
}
void Tray::updateIcon() {
if (!_icon || !_iconGraphic) {
return;
}
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
if (_iconGraphic->isRefreshNeeded(counter, muted, QIcon::themeName())) {
_icon->setIcon(_iconGraphic->trayIcon(counter, muted));
}
}
void Tray::createMenu() {
if (!_menu) {
_menu = base::make_unique_q<QMenu>(Parent());
}
if (!_menuXEmbed) {
_menuXEmbed = base::make_unique_q<Ui::PopupMenu>(nullptr);
_menuXEmbed->deleteOnHide(false);
}
}
void Tray::destroyMenu() {
_menuXEmbed = nullptr;
if (_menu) {
_menu->clear();
}
_actionsLifetime.destroy();
}
void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
if (_menuXEmbed) {
const auto XEAction = _menuXEmbed->addAction(QString(), callback);
rpl::duplicate(
text
) | rpl::start_with_next([=](const QString &text) {
XEAction->setText(text);
}, _actionsLifetime);
}
if (_menu) {
const auto action = _menu->addAction(QString(), std::move(callback));
std::move(
text
) | rpl::start_with_next([=](const QString &text) {
action->setText(text);
}, _actionsLifetime);
}
}
void Tray::showTrayMessage() const {
}
bool Tray::hasTrayMessageSupport() const {
return false;
}
rpl::producer<> Tray::aboutToShowRequests() const {
return rpl::merge(
_aboutToShowRequests.events(),
_menu
? base::qt_signal_producer(_menu.get(), &QMenu::aboutToShow)
: rpl::never<>() | rpl::type_erased());
}
rpl::producer<> Tray::showFromTrayRequests() const {
return rpl::never<>();
}
rpl::producer<> Tray::hideToTrayRequests() const {
return rpl::never<>();
}
rpl::producer<> Tray::iconClicks() const {
return _iconClicks.events();
}
rpl::lifetime &Tray::lifetime() {
return _lifetime;
}
Tray::~Tray() = default;
} // namespace Platform

View File

@@ -0,0 +1,68 @@
/*
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 "platform/platform_tray.h"
#include "base/unique_qptr.h"
namespace Ui {
class PopupMenu;
} // namespace Ui
class QMenu;
class QSystemTrayIcon;
namespace Platform {
class IconGraphic;
class TrayEventFilter;
class Tray final {
public:
Tray();
~Tray();
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
[[nodiscard]] rpl::producer<> showFromTrayRequests() const;
[[nodiscard]] rpl::producer<> hideToTrayRequests() const;
[[nodiscard]] rpl::producer<> iconClicks() const;
void createIcon();
void destroyIcon();
void updateIcon();
void createMenu();
void destroyMenu();
void addAction(rpl::producer<QString> text, Fn<void()> &&callback);
void showTrayMessage() const;
[[nodiscard]] bool hasTrayMessageSupport() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
std::unique_ptr<IconGraphic> _iconGraphic;
base::unique_qptr<QSystemTrayIcon> _icon;
base::unique_qptr<QMenu> _menu;
base::unique_qptr<Ui::PopupMenu> _menuXEmbed;
base::unique_qptr<TrayEventFilter> _eventFilter;
rpl::event_stream<> _iconClicks;
rpl::event_stream<> _aboutToShowRequests;
rpl::lifetime _actionsLifetime;
rpl::lifetime _lifetime;
};
} // namespace Platform

View File

@@ -30,8 +30,6 @@ public:
void updateWindowIcon() override;
void psShowTrayMenu();
bool preventsQuit(Core::QuitReason reason) override;
class Private;
@@ -39,25 +37,12 @@ public:
protected:
bool eventFilter(QObject *obj, QEvent *evt) override;
void handleActiveChangedHook() override;
void stateChangedHook(Qt::WindowState state) override;
void initHook() override;
void unreadCounterChangedHook() override;
bool hasTrayIcon() const override {
return trayIcon;
}
void updateGlobalMenuHook() override;
void workmodeUpdated(Core::Settings::WorkMode mode) override;
QSystemTrayIcon *trayIcon = nullptr;
QMenu *trayIconMenu = nullptr;
void psTrayMenuUpdated();
void psSetupTrayIcon();
void closeWithoutDestroy() override;
void createGlobalMenu() override;
@@ -66,7 +51,6 @@ private:
void hideAndDeactivate();
void updateIconCounters();
[[nodiscard]] QIcon generateIconForTray(int counter, bool muted) const;
std::unique_ptr<Private> _private;

View File

@@ -65,17 +65,6 @@ namespace {
// fullscreen mode, after that we'll hide the window no matter what.
constexpr auto kHideAfterFullscreenTimeoutMs = 3000;
[[nodiscard]] QImage TrayIconBack(bool darkMode) {
static const auto WithColor = [](QColor color) {
return st::macTrayIcon.instance(color, 100);
};
static const auto DarkModeResult = WithColor({ 255, 255, 255 });
static const auto LightModeResult = WithColor({ 0, 0, 0, 180 });
auto result = darkMode ? DarkModeResult : LightModeResult;
result.detach();
return result;
}
} // namespace
class MainWindow::Private {
@@ -248,20 +237,6 @@ void MainWindow::stateChangedHook(Qt::WindowState state) {
}
}
void MainWindow::handleActiveChangedHook() {
// On macOS just remove trayIcon menu if the window is not active.
// So we will activate the window on click instead of showing the menu.
if (isActiveForTrayMenu()) {
if (trayIcon
&& trayIconMenu
&& trayIcon->contextMenu() != trayIconMenu) {
trayIcon->setContextMenu(trayIconMenu);
}
} else if (trayIcon) {
trayIcon->setContextMenu(nullptr);
}
}
void MainWindow::initHook() {
_customTitleHeight = 0;
if (auto view = reinterpret_cast<NSView*>(winId())) {
@@ -282,9 +257,6 @@ void MainWindow::hideAndDeactivate() {
hide();
}
void MainWindow::psShowTrayMenu() {
}
bool MainWindow::preventsQuit(Core::QuitReason reason) {
// Thanks Chromium, see
// chromium.org/developers/design-documents/confirm-to-quit-experiment
@@ -298,87 +270,12 @@ bool MainWindow::preventsQuit(Core::QuitReason reason) {
Platform::ConfirmQuit::QuitKeysString()));
}
void MainWindow::psTrayMenuUpdated() {
}
void MainWindow::psSetupTrayIcon() {
if (!trayIcon) {
trayIcon = new QSystemTrayIcon(this);
trayIcon->setIcon(generateIconForTray(
Core::App().unreadBadge(),
Core::App().unreadBadgeMuted()));
if (isActiveForTrayMenu()) {
trayIcon->setContextMenu(trayIconMenu);
} else {
trayIcon->setContextMenu(nullptr);
}
attachToTrayIcon(trayIcon);
} else {
updateIconCounters();
}
trayIcon->show();
}
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
psSetupTrayIcon();
if (mode == Core::Settings::WorkMode::WindowOnly) {
if (trayIcon) {
trayIcon->setContextMenu(0);
delete trayIcon;
trayIcon = nullptr;
}
}
}
void _placeCounter(QImage &img, int size, int count, style::color bg, style::color color) {
if (!count) return;
auto savedRatio = img.devicePixelRatio();
img.setDevicePixelRatio(1.);
{
Painter p(&img);
PainterHighQualityEnabler hq(p);
auto cnt = (count < 100) ? QString("%1").arg(count) : QString("..%1").arg(count % 100, 2, 10, QChar('0'));
auto cntSize = cnt.size();
p.setBrush(bg);
p.setPen(Qt::NoPen);
int32 fontSize, skip;
if (size == 22) {
skip = 1;
fontSize = 8;
} else {
skip = 2;
fontSize = 16;
}
style::font f(fontSize, 0, 0);
int32 w = f->width(cnt), d, r;
if (size == 22) {
d = (cntSize < 2) ? 3 : 2;
r = (cntSize < 2) ? 6 : 5;
} else {
d = (cntSize < 2) ? 6 : 5;
r = (cntSize < 2) ? 9 : 11;
}
p.drawRoundedRect(QRect(size - w - d * 2 - skip, size - f->height - skip, w + d * 2, f->height), r, r);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setFont(f);
p.setPen(color);
p.drawText(size - w - d - skip, size - f->height + f->ascent - skip, cnt);
}
img.setDevicePixelRatio(savedRatio);
}
void MainWindow::unreadCounterChangedHook() {
updateIconCounters();
}
void MainWindow::updateIconCounters() {
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
const auto string = !counter
? QString()
@@ -386,43 +283,6 @@ void MainWindow::updateIconCounters() {
? QString("%1").arg(counter)
: QString("..%1").arg(counter % 100, 2, 10, QChar('0'));
_private->setWindowBadge(string);
if (trayIcon) {
trayIcon->setIcon(generateIconForTray(counter, muted));
}
}
QIcon MainWindow::generateIconForTray(int counter, bool muted) const {
auto result = QIcon();
auto lightMode = TrayIconBack(false);
auto darkMode = TrayIconBack(true);
auto lightModeActive = darkMode;
auto darkModeActive = darkMode;
lightModeActive.detach();
darkModeActive.detach();
const auto size = 22 * cIntRetinaFactor();
const auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg);
_placeCounter(lightMode, size, counter, bg, st::trayCounterFg);
_placeCounter(darkMode, size, counter, bg, muted ? st::trayCounterFgMacInvert : st::trayCounterFg);
_placeCounter(lightModeActive, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert);
_placeCounter(darkModeActive, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert);
result.addPixmap(Ui::PixmapFromImage(
std::move(lightMode)),
QIcon::Normal,
QIcon::Off);
result.addPixmap(Ui::PixmapFromImage(
std::move(darkMode)),
QIcon::Normal,
QIcon::On);
result.addPixmap(Ui::PixmapFromImage(
std::move(lightModeActive)),
QIcon::Active,
QIcon::Off);
result.addPixmap(Ui::PixmapFromImage(
std::move(darkModeActive)),
QIcon::Active,
QIcon::On);
return result;
}
void MainWindow::createGlobalMenu() {

View File

@@ -0,0 +1,56 @@
/*
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 "platform/platform_tray.h"
#include "base/unique_qptr.h"
class QMenu;
namespace Platform {
class NativeIcon;
class Tray final {
public:
Tray();
~Tray();
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
[[nodiscard]] rpl::producer<> showFromTrayRequests() const;
[[nodiscard]] rpl::producer<> hideToTrayRequests() const;
[[nodiscard]] rpl::producer<> iconClicks() const;
void createIcon();
void destroyIcon();
void updateIcon();
void createMenu();
void destroyMenu();
void addAction(rpl::producer<QString> text, Fn<void()> &&callback);
void showTrayMessage() const;
[[nodiscard]] bool hasTrayMessageSupport() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
std::unique_ptr<NativeIcon> _nativeIcon;
base::unique_qptr<QMenu> _menu;
rpl::event_stream<> _showFromTrayRequests;
rpl::lifetime _actionsLifetime;
rpl::lifetime _lifetime;
};
} // namespace Platform

View File

@@ -0,0 +1,413 @@
/*
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/mac/tray_mac.h"
#include "base/platform/mac/base_utilities_mac.h"
#include "core/application.h"
#include "core/sandbox.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_window.h"
#include <QtWidgets/QMenu>
#import <AppKit/NSMenu.h>
#import <AppKit/NSStatusItem.h>
@interface CommonDelegate : NSObject<NSMenuDelegate> {
}
- (void) menuDidClose:(NSMenu *)menu;
- (void) menuWillOpen:(NSMenu *)menu;
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context;
- (rpl::producer<>) closes;
- (rpl::producer<>) aboutToShowRequests;
- (rpl::producer<>) appearanceChanges;
@end // @interface CommonDelegate
@implementation CommonDelegate {
rpl::event_stream<> _closes;
rpl::event_stream<> _aboutToShowRequests;
rpl::event_stream<> _appearanceChanges;
}
- (void) menuDidClose:(NSMenu *)menu {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
_closes.fire({});
});
}
- (void) menuWillOpen:(NSMenu *)menu {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
_aboutToShowRequests.fire({});
});
}
// Thanks https://stackoverflow.com/a/64525038
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"button.effectiveAppearance"]) {
_appearanceChanges.fire({});
}
}
- (rpl::producer<>) closes {
return _closes.events();
}
- (rpl::producer<>) aboutToShowRequests {
return _aboutToShowRequests.events();
}
- (rpl::producer<>) appearanceChanges {
return _appearanceChanges.events();
}
@end // @implementation MenuDelegate
namespace Platform {
namespace {
[[nodiscard]] QImage TrayIconBack(bool darkMode) {
static const auto WithColor = [](QColor color) {
return st::macTrayIcon.instance(color, 100);
};
static const auto DarkModeResult = WithColor({ 255, 255, 255 });
static const auto LightModeResult = WithColor({ 0, 0, 0, 180 });
auto result = darkMode ? DarkModeResult : LightModeResult;
result.detach();
return result;
}
void PlaceCounter(
QImage &img,
int size,
int count,
style::color bg,
style::color color) {
if (!count) {
return;
}
const auto savedRatio = img.devicePixelRatio();
img.setDevicePixelRatio(1.);
{
Painter p(&img);
PainterHighQualityEnabler hq(p);
const auto cnt = (count < 100)
? QString("%1").arg(count)
: QString("..%1").arg(count % 100, 2, 10, QChar('0'));
const auto cntSize = cnt.size();
p.setBrush(bg);
p.setPen(Qt::NoPen);
int32 fontSize, skip;
if (size == 22) {
skip = 1;
fontSize = 8;
} else {
skip = 2;
fontSize = 16;
}
style::font f(fontSize, 0, 0);
int32 w = f->width(cnt), d, r;
if (size == 22) {
d = (cntSize < 2) ? 3 : 2;
r = (cntSize < 2) ? 6 : 5;
} else {
d = (cntSize < 2) ? 6 : 5;
r = (cntSize < 2) ? 9 : 11;
}
p.drawRoundedRect(
QRect(
size - w - d * 2 - skip,
size - f->height - skip,
w + d * 2,
f->height),
r,
r);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setFont(f);
p.setPen(color);
p.drawText(
size - w - d - skip,
size - f->height + f->ascent - skip,
cnt);
}
img.setDevicePixelRatio(savedRatio);
}
void UpdateIcon(const NSStatusItem *status) {
if (!status) {
return;
}
const auto appearance = status.button.effectiveAppearance;
const auto darkMode = [[appearance.name lowercaseString]
containsString:@"dark"];
// The recommended maximum title bar icon height is 18 points
// (device independent pixels). The menu height on past and
// current OS X versions is 22 points. Provide some future-proofing
// by deriving the icon height from the menu height.
const int padding = 0;
const int menuHeight = NSStatusBar.systemStatusBar.thickness;
// [[status.button window] backingScaleFactor];
const int maxImageHeight = (menuHeight - padding)
* style::DevicePixelRatio();
// Select pixmap based on the device pixel height. Ideally we would use
// the devicePixelRatio of the target screen, but that value is not
// known until draw time. Use qApp->devicePixelRatio, which returns the
// devicePixelRatio for the "best" screen on the system.
const auto side = 22 * style::DevicePixelRatio();
const auto selectedSize = QSize(side, side);
auto result = TrayIconBack(darkMode);
auto resultActive = result;
resultActive.detach();
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
const auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg);
const auto &fg = st::trayCounterFg;
const auto &fgInvert = st::trayCounterFgMacInvert;
const auto &bgInvert = st::trayCounterBgMacInvert;
const auto &resultFg = !darkMode ? fg : muted ? fgInvert : fg;
PlaceCounter(result, side, counter, bg, resultFg);
PlaceCounter(resultActive, side, counter, bgInvert, fgInvert);
// Scale large pixmaps to fit the available menu bar area.
if (result.height() > maxImageHeight) {
result = result.scaledToHeight(
maxImageHeight,
Qt::SmoothTransformation);
}
if (resultActive.height() > maxImageHeight) {
resultActive = resultActive.scaledToHeight(
maxImageHeight,
Qt::SmoothTransformation);
}
status.button.image = Q2NSImage(result);
status.button.alternateImage = Q2NSImage(resultActive);
status.button.imageScaling = NSImageScaleProportionallyDown;
}
[[nodiscard]] QWidget *Parent() {
Expects(Core::App().primaryWindow() != nullptr);
return Core::App().primaryWindow()->widget();
}
} // namespace
class NativeIcon final {
public:
NativeIcon();
~NativeIcon();
void updateIcon();
void showMenu(not_null<QMenu*> menu);
void deactivateButton();
[[nodiscard]] rpl::producer<> clicks() const;
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
private:
CommonDelegate *_delegate;
NSStatusItem *_status;
rpl::event_stream<> _clicks;
rpl::lifetime _lifetime;
};
NativeIcon::NativeIcon()
: _delegate([[CommonDelegate alloc] init])
, _status([
[NSStatusBar.systemStatusBar
statusItemWithLength:NSSquareStatusItemLength] retain]) {
[_status
addObserver:_delegate
forKeyPath:@"button.effectiveAppearance"
options:0
| NSKeyValueObservingOptionNew
| NSKeyValueObservingOptionInitial
context:nil];
[_delegate closes] | rpl::start_with_next([=] {
_status.menu = nil;
}, _lifetime);
[_delegate appearanceChanges] | rpl::start_with_next([=] {
updateIcon();
}, _lifetime);
const auto masks = NSEventMaskLeftMouseDown
| NSEventMaskLeftMouseUp
| NSEventMaskRightMouseDown
| NSEventMaskRightMouseUp
| NSEventMaskOtherMouseUp;
[_status.button sendActionOn:masks];
id buttonCallback = [^{
const auto type = NSApp.currentEvent.type;
if ((type == NSEventTypeLeftMouseDown)
|| (type == NSEventTypeRightMouseDown)) {
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
_clicks.fire({});
});
}
} copy];
_lifetime.add([=] {
[buttonCallback release];
});
_status.button.target = buttonCallback;
_status.button.action = @selector(invoke);
_status.button.toolTip = Q2NSString(AppName.utf16());
}
NativeIcon::~NativeIcon() {
[_status
removeObserver:_delegate
forKeyPath:@"button.effectiveAppearance"];
[NSStatusBar.systemStatusBar removeStatusItem:_status];
[_status release];
[_delegate release];
}
void NativeIcon::updateIcon() {
UpdateIcon(_status);
}
void NativeIcon::showMenu(not_null<QMenu*> menu) {
_status.menu = menu->toNSMenu();
_status.menu.delegate = _delegate;
[_status.button performClick:nil];
}
void NativeIcon::deactivateButton() {
[_status.button highlight:false];
}
rpl::producer<> NativeIcon::clicks() const {
return _clicks.events();
}
rpl::producer<> NativeIcon::aboutToShowRequests() const {
return [_delegate aboutToShowRequests];
}
Tray::Tray() {
}
void Tray::createIcon() {
if (!_nativeIcon) {
_nativeIcon = std::make_unique<NativeIcon>();
// On macOS we are activating the window on click
// instead of showing the menu, when the window is not activated.
_nativeIcon->clicks(
) | rpl::start_with_next([=] {
if (Core::App().isActiveForTrayMenu()) {
_nativeIcon->showMenu(_menu.get());
} else {
_nativeIcon->deactivateButton();
_showFromTrayRequests.fire({});
}
}, _lifetime);
}
updateIcon();
}
void Tray::destroyIcon() {
_nativeIcon = nullptr;
}
void Tray::updateIcon() {
if (_nativeIcon) {
_nativeIcon->updateIcon();
}
}
void Tray::createMenu() {
if (!_menu) {
_menu = base::make_unique_q<QMenu>(Parent());
}
}
void Tray::destroyMenu() {
if (_menu) {
_menu->clear();
}
_actionsLifetime.destroy();
}
void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
if (!_menu) {
return;
}
const auto action = _menu->addAction(QString(), std::move(callback));
std::move(
text
) | rpl::start_with_next([=](const QString &text) {
action->setText(text);
}, _actionsLifetime);
}
void Tray::showTrayMessage() const {
}
bool Tray::hasTrayMessageSupport() const {
return false;
}
rpl::producer<> Tray::aboutToShowRequests() const {
return _nativeIcon
? _nativeIcon->aboutToShowRequests()
: rpl::never<>();
}
rpl::producer<> Tray::showFromTrayRequests() const {
return _showFromTrayRequests.events();
}
rpl::producer<> Tray::hideToTrayRequests() const {
return rpl::never<>();
}
rpl::producer<> Tray::iconClicks() const {
return rpl::never<>();
}
rpl::lifetime &Tray::lifetime() {
return _lifetime;
}
Tray::~Tray() = default;
} // namespace Platform

View File

@@ -0,0 +1,24 @@
/*
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
namespace Platform {
class Tray;
} // namespace Platform
// Platform dependent implementations.
#ifdef Q_OS_MAC
#include "platform/mac/tray_mac.h"
#elif defined Q_OS_UNIX // Q_OS_MAC
#include "platform/linux/tray_linux.h"
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX
#include "platform/win/tray_win.h"
#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_window.h"
#include "platform/platform_specific.h"
#include "platform/platform_notifications_manager.h"
#include "platform/win/tray_win.h"
#include "platform/win/windows_dlls.h"
#include "window/notifications_manager.h"
#include "window/window_session_controller.h"
@@ -95,69 +96,6 @@ uint32 kTaskbarCreatedMsgId = 0;
return nullptr;
}
[[nodiscard]] QImage IconWithCounter(
Window::CounterLayerArgs &&args,
Main::Session *session,
bool smallIcon) {
static constexpr auto kCount = 3;
static auto ScaledLogo = std::array<QImage, kCount>();
static auto ScaledLogoNoMargin = std::array<QImage, kCount>();
struct Dimensions {
int index = 0;
int size = 0;
};
const auto d = [&]() -> Dimensions {
switch (args.size) {
case 16:
return {
.index = 0,
.size = 16,
};
case 32:
return {
.index = 1,
.size = 32,
};
default:
return {
.index = 2,
.size = 64,
};
}
}();
Assert(d.index < kCount);
auto &scaled = smallIcon ? ScaledLogoNoMargin : ScaledLogo;
auto result = [&] {
auto &image = scaled[d.index];
if (image.isNull()) {
image = (smallIcon
? Window::LogoNoMargin()
: Window::Logo()).scaledToWidth(
d.size,
Qt::SmoothTransformation);
}
return image;
}();
if (session && session->supportMode()) {
Window::ConvertIconToBlack(result);
}
if (!args.count) {
return result;
} else if (smallIcon) {
return Window::WithSmallCounter(std::move(result), std::move(args));
}
QPainter p(&result);
const auto half = d.size / 2;
args.size = half;
p.drawPixmap(
half,
half,
Ui::PixmapFromImage(Window::GenerateCounterLayer(std::move(args))));
return result;
}
EventFilter::EventFilter(not_null<MainWindow*> window) : _window(window) {
}
@@ -277,10 +215,6 @@ void MainWindow::shadowsDeactivate() {
_hasActiveFrame = false;
}
void MainWindow::psShowTrayMenu() {
trayIconMenu->popup(QCursor::pos());
}
void MainWindow::destroyedFromSystem() {
Core::Quit();
}
@@ -310,47 +244,11 @@ void MainWindow::forceIconRefresh() {
updateIconCounters();
}
void MainWindow::psTrayMenuUpdated() {
}
void MainWindow::psSetupTrayIcon() {
if (!trayIcon) {
trayIcon = new QSystemTrayIcon(this);
const auto icon = QIcon(Ui::PixmapFromImage(
QImage(Window::LogoNoMargin())));
trayIcon->setIcon(icon);
connect(
trayIcon,
&QSystemTrayIcon::messageClicked,
this,
[=] { showFromTray(); });
attachToTrayIcon(trayIcon);
}
updateIconCounters();
trayIcon->show();
}
void MainWindow::showTrayTooltip() {
if (trayIcon && !cSeenTrayTooltip()) {
trayIcon->showMessage(
AppName.utf16(),
tr::lng_tray_icon_text(tr::now),
QSystemTrayIcon::Information,
10000);
cSetSeenTrayTooltip(true);
Local::writeSettings();
}
}
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
using WorkMode = Core::Settings::WorkMode;
switch (mode) {
case WorkMode::WindowAndTray: {
psSetupTrayIcon();
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
if (psOwner) {
SetWindowLongPtr(_hWnd, GWLP_HWNDPARENT, 0);
@@ -360,7 +258,6 @@ void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
} break;
case WorkMode::TrayOnly: {
psSetupTrayIcon();
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
if (!psOwner) {
const auto hwnd = _taskbarHiderWindow->winId();
@@ -370,12 +267,6 @@ void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
} break;
case WorkMode::WindowOnly: {
if (trayIcon) {
trayIcon->setContextMenu(0);
trayIcon->deleteLater();
}
trayIcon = 0;
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
if (psOwner) {
SetWindowLongPtr(_hWnd, GWLP_HWNDPARENT, 0);
@@ -446,40 +337,28 @@ void MainWindow::updateIconCounters() {
const auto iconSizeBig = QSize(
GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON));
const auto supportMode = session && session->supportMode();
const auto &bg = muted ? st::trayCounterBgMute : st::trayCounterBg;
const auto &fg = st::trayCounterFg;
const auto counterArgs = [&](int size, int counter) {
return Window::CounterLayerArgs{
.size = size,
.count = counter,
.bg = bg,
.fg = fg,
};
};
const auto iconWithCounter = [&](int size, int counter, bool smallIcon) {
return Ui::PixmapFromImage(IconWithCounter(
counterArgs(size, counter),
session,
smallIcon));
};
auto iconSmallPixmap16 = iconWithCounter(16, counter, true);
auto iconSmallPixmap32 = iconWithCounter(32, counter, true);
auto iconSmallPixmap16 = Tray::IconWithCounter(
Tray::CounterLayerArgs(16, counter, muted),
true,
supportMode);
auto iconSmallPixmap32 = Tray::IconWithCounter(
Tray::CounterLayerArgs(32, counter, muted),
true,
supportMode);
QIcon iconSmall, iconBig;
iconSmall.addPixmap(iconSmallPixmap16);
iconSmall.addPixmap(iconSmallPixmap32);
const auto bigCounter = taskbarList.Get() ? 0 : counter;
iconBig.addPixmap(iconWithCounter(32, bigCounter, false));
iconBig.addPixmap(iconWithCounter(64, bigCounter, false));
if (trayIcon) {
// Force Qt to use right icon size, not the larger one.
QIcon forTrayIcon;
forTrayIcon.addPixmap(iconSizeSmall.width() >= 20
? iconSmallPixmap32
: iconSmallPixmap16);
trayIcon->setIcon(forTrayIcon);
}
iconBig.addPixmap(Tray::IconWithCounter(
Tray::CounterLayerArgs(32, bigCounter, muted),
false,
supportMode));
iconBig.addPixmap(Tray::IconWithCounter(
Tray::CounterLayerArgs(64, bigCounter, muted),
false,
supportMode));
destroyCachedIcons();
_iconSmall = NativeIcon(iconSmall, iconSizeSmall);
@@ -490,7 +369,7 @@ void MainWindow::updateIconCounters() {
if (counter > 0) {
const auto pixmap = [&](int size) {
return Ui::PixmapFromImage(Window::GenerateCounterLayer(
counterArgs(size, counter)));
Tray::CounterLayerArgs(size, counter, muted)));
};
QIcon iconOverlay;
iconOverlay.addPixmap(pixmap(16));
@@ -624,17 +503,6 @@ void MainWindow::validateWindowTheme(bool native, bool night) {
SendMessage(_hWnd, WM_NCACTIVATE, _hasActiveFrame ? 1 : 0, 0);
}
void MainWindow::showFromTrayMenu() {
// If we try to activate() window before the trayIconMenu is hidden,
// then the window will be shown in semi-active state (Qt bug).
// It will receive input events, but it will be rendered as inactive.
using namespace rpl::mappers;
_showFromTrayLifetime = trayIconMenu->shownValue(
) | rpl::filter(!_1) | rpl::take(1) | rpl::start_with_next([=] {
showFromTray();
});
}
HWND MainWindow::psHwnd() const {
return _hWnd;
}

View File

@@ -21,8 +21,6 @@ class MainWindow : public Window::MainWindow {
public:
explicit MainWindow(not_null<Window::Controller*> controller);
void showFromTrayMenu() override;
HWND psHwnd() const;
void updateWindowIcon() override;
@@ -34,8 +32,6 @@ public:
[[nodiscard]] bool hasTabletView() const;
void psShowTrayMenu();
void destroyedFromSystem();
~MainWindow();
@@ -45,18 +41,6 @@ protected:
int32 screenNameChecksum(const QString &name) const override;
void unreadCounterChangedHook() override;
bool hasTrayIcon() const override {
return trayIcon;
}
QSystemTrayIcon *trayIcon = nullptr;
Ui::PopupMenu *trayIconMenu = nullptr;
void psTrayMenuUpdated();
void psSetupTrayIcon();
void showTrayTooltip() override;
void workmodeUpdated(Core::Settings::WorkMode mode) override;
bool initGeometryFromSystem() override;
@@ -83,7 +67,6 @@ private:
// Workarounds for activation from tray icon.
crl::time _lastDeactivateTime = 0;
rpl::lifetime _showFromTrayLifetime;
bool _hasActiveFrame = false;

View File

@@ -0,0 +1,258 @@
/*
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/win/tray_win.h"
#include "base/invoke_queued.h"
#include "base/qt_signal_producer.h"
#include "core/application.h"
#include "main/main_session.h"
#include "storage/localstorage.h"
#include "ui/ui_utility.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_window.h"
#include <QtWidgets/QSystemTrayIcon>
namespace Platform {
namespace {
constexpr auto kTooltipDelay = crl::time(10000);
[[nodiscard]] QImage ImageIconWithCounter(
Window::CounterLayerArgs &&args,
bool supportMode,
bool smallIcon) {
static constexpr auto kCount = 3;
static auto ScaledLogo = std::array<QImage, kCount>();
static auto ScaledLogoNoMargin = std::array<QImage, kCount>();
struct Dimensions {
int index = 0;
int size = 0;
};
const auto d = [&]() -> Dimensions {
switch (args.size) {
case 16:
return {
.index = 0,
.size = 16,
};
case 32:
return {
.index = 1,
.size = 32,
};
default:
return {
.index = 2,
.size = 64,
};
}
}();
Assert(d.index < kCount);
auto &scaled = smallIcon ? ScaledLogoNoMargin : ScaledLogo;
auto result = [&] {
auto &image = scaled[d.index];
if (image.isNull()) {
image = (smallIcon
? Window::LogoNoMargin()
: Window::Logo()).scaledToWidth(
d.size,
Qt::SmoothTransformation);
}
return image;
}();
if (supportMode) {
Window::ConvertIconToBlack(result);
}
if (!args.count) {
return result;
} else if (smallIcon) {
return Window::WithSmallCounter(std::move(result), std::move(args));
}
QPainter p(&result);
const auto half = d.size / 2;
args.size = half;
p.drawPixmap(
half,
half,
Ui::PixmapFromImage(Window::GenerateCounterLayer(std::move(args))));
return result;
}
[[nodiscard]] QWidget *Parent() {
Expects(Core::App().primaryWindow() != nullptr);
return Core::App().primaryWindow()->widget();
}
} // namespace
Tray::Tray() {
}
void Tray::createIcon() {
if (!_icon) {
_icon = base::make_unique_q<QSystemTrayIcon>(Parent());
updateIcon();
_icon->setToolTip(AppName.utf16());
using Reason = QSystemTrayIcon::ActivationReason;
base::qt_signal_producer(
_icon.get(),
&QSystemTrayIcon::activated
) | rpl::start_with_next([=](Reason reason) {
if (reason == QSystemTrayIcon::Context && _menu) {
_aboutToShowRequests.fire({});
InvokeQueued(_menu.get(), [=] {
_menu->popup(QCursor::pos());
});
} else {
_iconClicks.fire({});
}
}, _lifetime);
} else {
updateIcon();
}
_icon->show();
}
void Tray::destroyIcon() {
_icon = nullptr;
}
void Tray::updateIcon() {
if (!_icon) {
return;
}
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
const auto controller = Core::App().primaryWindow();
const auto session = !controller
? nullptr
: !controller->sessionController()
? nullptr
: &controller->sessionController()->session();
const auto supportMode = session && session->supportMode();
const auto iconSizeWidth = GetSystemMetrics(SM_CXSMICON);
auto iconSmallPixmap16 = Tray::IconWithCounter(
CounterLayerArgs(16, counter, muted),
true,
supportMode);
auto iconSmallPixmap32 = Tray::IconWithCounter(
CounterLayerArgs(32, counter, muted),
true,
supportMode);
auto iconSmall = QIcon();
iconSmall.addPixmap(iconSmallPixmap16);
iconSmall.addPixmap(iconSmallPixmap32);
// Force Qt to use right icon size, not the larger one.
QIcon forTrayIcon;
forTrayIcon.addPixmap(iconSizeWidth >= 20
? iconSmallPixmap32
: iconSmallPixmap16);
_icon->setIcon(forTrayIcon);
}
void Tray::createMenu() {
if (!_menu) {
_menu = base::make_unique_q<Ui::PopupMenu>(nullptr);
_menu->deleteOnHide(false);
}
}
void Tray::destroyMenu() {
_menu = nullptr;
_actionsLifetime.destroy();
}
void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
if (!_menu) {
return;
}
// If we try to activate() window before the _menu is hidden,
// then the window will be shown in semi-active state (Qt bug).
// It will receive input events, but it will be rendered as inactive.
auto callbackLater = crl::guard(_menu.get(), [=] {
using namespace rpl::mappers;
_callbackFromTrayLifetime = _menu->shownValue(
) | rpl::filter(!_1) | rpl::take(1) | rpl::start_with_next([=] {
callback();
});
});
const auto action = _menu->addAction(QString(), std::move(callbackLater));
std::move(
text
) | rpl::start_with_next([=](const QString &text) {
action->setText(text);
}, _actionsLifetime);
}
void Tray::showTrayMessage() const {
if (!cSeenTrayTooltip() && _icon) {
_icon->showMessage(
AppName.utf16(),
tr::lng_tray_icon_text(tr::now),
QSystemTrayIcon::Information,
kTooltipDelay);
cSetSeenTrayTooltip(true);
Local::writeSettings();
}
}
bool Tray::hasTrayMessageSupport() const {
return !cSeenTrayTooltip();
}
rpl::producer<> Tray::aboutToShowRequests() const {
return _aboutToShowRequests.events();
}
rpl::producer<> Tray::showFromTrayRequests() const {
return rpl::never<>();
}
rpl::producer<> Tray::hideToTrayRequests() const {
return rpl::never<>();
}
rpl::producer<> Tray::iconClicks() const {
return _iconClicks.events();
}
rpl::lifetime &Tray::lifetime() {
return _lifetime;
}
Window::CounterLayerArgs Tray::CounterLayerArgs(
int size,
int counter,
bool muted) {
return Window::CounterLayerArgs{
.size = size,
.count = counter,
.bg = muted ? st::trayCounterBgMute : st::trayCounterBg,
.fg = st::trayCounterFg,
};
}
QPixmap Tray::IconWithCounter(
Window::CounterLayerArgs &&args,
bool smallIcon,
bool supportMode) {
return Ui::PixmapFromImage(
ImageIconWithCounter(std::move(args), supportMode, smallIcon));
}
} // namespace Platform

View File

@@ -0,0 +1,73 @@
/*
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 "platform/platform_tray.h"
#include "base/unique_qptr.h"
namespace Window {
class CounterLayerArgs;
} // namespace Window
namespace Ui {
class PopupMenu;
} // namespace Ui
class QSystemTrayIcon;
namespace Platform {
class Tray final {
public:
Tray();
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
[[nodiscard]] rpl::producer<> showFromTrayRequests() const;
[[nodiscard]] rpl::producer<> hideToTrayRequests() const;
[[nodiscard]] rpl::producer<> iconClicks() const;
void createIcon();
void destroyIcon();
void updateIcon();
void createMenu();
void destroyMenu();
void addAction(rpl::producer<QString> text, Fn<void()> &&callback);
void showTrayMessage() const;
[[nodiscard]] bool hasTrayMessageSupport() const;
[[nodiscard]] rpl::lifetime &lifetime();
// Windows only.
[[nodiscard]] static Window::CounterLayerArgs CounterLayerArgs(
int size,
int counter,
bool muted);
[[nodiscard]] static QPixmap IconWithCounter(
Window::CounterLayerArgs &&args,
bool smallIcon,
bool supportMode);
private:
base::unique_qptr<QSystemTrayIcon> _icon;
base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<> _iconClicks;
rpl::event_stream<> _aboutToShowRequests;
rpl::lifetime _callbackFromTrayLifetime;
rpl::lifetime _actionsLifetime;
rpl::lifetime _lifetime;
};
} // namespace Platform

View File

@@ -18,6 +18,8 @@ settingsButton: SettingsButton(infoProfileButton) {
settingsButtonNoIcon: SettingsButton(settingsButton) {
padding: margins(22px, 10px, 22px, 8px);
}
settingsButtonActive: SettingsButton(infoMainButton, settingsButton) {
}
settingsAttentionButton: SettingsButton(settingsButtonNoIcon) {
textFg: attentionButtonFg;
textFgOver: attentionButtonFgOver;
@@ -91,6 +93,9 @@ settingsIconPin: icon {{ "settings/pin", settingsIconFg }};
settingsIconDownload: icon {{ "settings/download", settingsIconFg }};
settingsIconMention: icon {{ "settings/mention", settingsIconFg }};
settingsIconAdd: icon {{ "settings/add", windowFgActive }};
settingsIconRemove: icon {{ "settings/remove", windowFgActive }};
settingsCheckbox: Checkbox(defaultBoxCheckbox) {
textPosition: point(15px, 1px);
}
@@ -247,8 +252,14 @@ settingsAccentColorSize: 24px;
settingsAccentColorSkip: 4px;
settingsAccentColorLine: 3px;
settingsFilterIconSkip: 68px;
settingsFilterIconLeft: 17px;
settingsFilterDividerLabel: FlatLabel(boxDividerLabel) {
minWidth: 258px;
maxHeight: 0px;
align: align(top);
}
settingsFilterDividerLabelPadding: margins(0px, 16px, 0px, 22px);
settingsFilterIconSize: 74px;
settingsFilterIconPadding: margins(0px, 17px, 0px, 5px);
settingsDeviceName: InputField(defaultInputField) {
textBg: transparent;

View File

@@ -0,0 +1,215 @@
/*
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 "settings/settings_blocked_peers.h"
#include "api/api_blocked_peers.h"
#include "apiwrap.h"
#include "data/data_changes.h"
#include "data/data_peer.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "settings/settings_privacy_controllers.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_menu_icons.h"
namespace Settings {
Blocked::Blocked(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent)
, _controller(controller) {
setupContent();
{
auto padding = st::changePhoneIconPadding;
padding.setBottom(padding.top());
_loading = base::make_unique_q<Ui::CenterWrap<>>(
this,
object_ptr<Ui::PaddingWrap<>>(
this,
object_ptr<Ui::FlatLabel>(
this,
tr::lng_contacts_loading(),
st::changePhoneDescription),
std::move(padding)));
Ui::ResizeFitChild(this, _loading.get());
}
_controller->session().api().blockedPeers().slice(
) | rpl::start_with_next([=](const Api::BlockedPeers::Slice &slice) {
checkTotal(slice.total);
}, lifetime());
_controller->session().changes().peerUpdates(
Data::PeerUpdate::Flag::IsBlocked
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
if (update.peer->isBlocked()) {
checkTotal(1);
}
}, lifetime());
}
rpl::producer<QString> Blocked::title() {
return tr::lng_settings_blocked_users();
}
QPointer<Ui::RpWidget> Blocked::createPinnedToTop(not_null<QWidget*> parent) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(parent.get());
AddSkip(content);
AddButton(
content,
tr::lng_blocked_list_add(),
st::settingsButtonActive,
{ &st::menuIconBlockSettings, 0, IconType::Round, &st::transparent }
)->addClickHandler([=] {
BlockedBoxController::BlockNewPeer(_controller);
});
AddSkip(content);
AddDividerText(content, tr::lng_blocked_list_about());
{
const auto subtitle = content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
content,
object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
AddSkip(subtitle->entity());
auto subtitleText = _countBlocked.value(
) | rpl::map([=](int count) {
return tr::lng_blocked_list_subtitle(tr::now, lt_count, count);
});
AddSubsectionTitle(
subtitle->entity(),
rpl::duplicate(subtitleText),
st::blockedUsersListSubtitleAddPadding);
subtitle->toggleOn(
rpl::merge(
_emptinessChanges.events() | rpl::map(!rpl::mappers::_1),
_countBlocked.value() | rpl::map(rpl::mappers::_1 > 0)
) | rpl::distinct_until_changed());
// Workaround.
std::move(
subtitleText
) | rpl::start_with_next([=] {
subtitle->entity()->resizeToWidth(content->width());
}, subtitle->lifetime());
}
return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
}
void Blocked::setupContent() {
using namespace rpl::mappers;
const auto container = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto listWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
listWrap->toggleOn(
_emptinessChanges.events_starting_with(true) | rpl::map(!_1),
anim::type::instant);
{
struct State {
std::unique_ptr<BlockedBoxController> controller;
std::unique_ptr<PeerListContentDelegateSimple> delegate;
};
auto controller = std::make_unique<BlockedBoxController>(_controller);
const auto content = listWrap->entity()->add(
object_ptr<PeerListContent>(this, controller.get()));
const auto state = content->lifetime().make_state<State>();
state->controller = std::move(controller);
state->delegate = std::make_unique<PeerListContentDelegateSimple>();
state->delegate->setContent(content);
state->controller->setDelegate(state->delegate.get());
state->controller->rowsCountChanges(
) | rpl::start_with_next([=](int total) {
_countBlocked = total;
checkTotal(total);
}, content->lifetime());
_countBlocked = content->fullRowsCount();
}
const auto emptyWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
emptyWrap->toggleOn(
_emptinessChanges.events_starting_with(false),
anim::type::instant);
{
const auto content = emptyWrap->entity();
auto icon = CreateLottieIcon(
content,
{
.name = u"blocked_peers_empty"_q,
.sizeOverride = {
st::changePhoneIconSize,
st::changePhoneIconSize,
},
},
st::blockedUsersListIconPadding);
content->add(std::move(icon.widget));
_showFinished.events(
) | rpl::start_with_next([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
}, content->lifetime());
content->add(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::FlatLabel>(
content,
tr::lng_blocked_list_empty_title(),
st::changePhoneTitle)),
st::changePhoneTitlePadding);
content->add(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::FlatLabel>(
content,
tr::lng_blocked_list_empty_description(),
st::changePhoneDescription)),
st::changePhoneDescriptionPadding);
AddSkip(content, st::blockedUsersListIconPadding.top());
}
Ui::ResizeFitChild(this, container);
}
void Blocked::checkTotal(int total) {
_loading = nullptr;
_emptinessChanges.fire(total <= 0);
}
void Blocked::showFinished() {
_showFinished.fire({});
}
} // namespace Settings

View File

@@ -0,0 +1,46 @@
/*
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 "settings/settings_common.h"
namespace Window {
class Controller;
} // namespace Window
namespace Settings {
class Blocked : public Section<Blocked> {
public:
Blocked(
QWidget *parent,
not_null<Window::SessionController*> controller);
void showFinished() override;
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
not_null<QWidget*> parent) override;
private:
void setupContent();
void checkTotal(int total);
const not_null<Window::SessionController*> _controller;
base::unique_qptr<Ui::RpWidget> _loading;
rpl::variable<int> _countBlocked;
rpl::event_stream<> _showFinished;
rpl::event_stream<bool> _emptinessChanges;
};
} // namespace Settings

View File

@@ -262,17 +262,22 @@ LottieIcon CreateLottieIcon(
const auto icon = owned.get();
raw->lifetime().add([kept = std::move(owned)]{});
const auto looped = raw->lifetime().make_state<bool>(true);
const auto animate = [=] {
icon->animate([=] { raw->update(); }, 0, icon->framesCount());
const auto start = [=] {
icon->animate([=] { raw->update(); }, 0, icon->framesCount() - 1);
};
const auto animate = [=](anim::repeat repeat) {
*looped = (repeat == anim::repeat::loop);
start();
};
raw->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(raw);
const auto left = (raw->width() - width) / 2;
icon->paint(p, left, padding.top());
if (!icon->animating() && icon->frameIndex() > 0) {
animate();
if (!icon->animating() && icon->frameIndex() > 0 && *looped) {
start();
}
}, raw->lifetime());

Some files were not shown because too many files have changed in this diff Show More