Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ed56bb4e4 | ||
|
|
3793f7c3c9 | ||
|
|
0d1b778612 | ||
|
|
b919a0627a | ||
|
|
6374d4eeda | ||
|
|
3967052375 | ||
|
|
89ccc95023 | ||
|
|
24f2ca7443 | ||
|
|
f90e13f8b1 | ||
|
|
606f5377d5 | ||
|
|
c698327b24 | ||
|
|
655731741c | ||
|
|
d5cdb5582b | ||
|
|
5cb081ca9a | ||
|
|
f1e0b36f61 | ||
|
|
ea9813825d | ||
|
|
36b6f70613 | ||
|
|
5e60b87cf9 | ||
|
|
ada22ee6cc | ||
|
|
bb016e1489 | ||
|
|
b115ea74d0 | ||
|
|
1008774aef | ||
|
|
73018ff958 | ||
|
|
e799fdaa3d | ||
|
|
7656a546b0 | ||
|
|
57f9ae4b2a | ||
|
|
cbdd86d398 | ||
|
|
2fe2105a5f | ||
|
|
a986d7a3d6 | ||
|
|
690c5df87c | ||
|
|
1e2759840d | ||
|
|
bad888496c | ||
|
|
4348ddf938 | ||
|
|
894d6028bd | ||
|
|
e8edbb16ae | ||
|
|
a0a71687e7 | ||
|
|
d042963a47 | ||
|
|
64b12bde55 | ||
|
|
49736cd879 | ||
|
|
e55581e0c9 | ||
|
|
574d915c23 | ||
|
|
2616659116 | ||
|
|
3d1f21bd05 | ||
|
|
dc631ef631 | ||
|
|
5277080115 | ||
|
|
1ccfcc824c | ||
|
|
97e8c0956f | ||
|
|
03a7131a1a | ||
|
|
2d906bddb2 | ||
|
|
dd7598a701 | ||
|
|
b6f17e1cea | ||
|
|
eb42a77eb7 | ||
|
|
ad761011d6 | ||
|
|
3fadf2ee54 | ||
|
|
15254599e2 | ||
|
|
1607752cf9 | ||
|
|
cf0cde6e83 | ||
|
|
8fffe7d128 | ||
|
|
a483eb98a1 | ||
|
|
838a3b23c7 | ||
|
|
a030911ad5 | ||
|
|
8ae1b10b91 | ||
|
|
adc8d6a6d1 |
21
.github/lock.yml
vendored
Normal file
21
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 45
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs.
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
11
.github/no-response.yml
vendored
Normal file
11
.github/no-response.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Number of days of inactivity before an Issue is closed for lack of response
|
||||
daysUntilClose: 30
|
||||
# Label requiring a response
|
||||
responseRequiredLabel: waiting for answer
|
||||
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because there has been no response
|
||||
to our request for more information from the original author. With only the
|
||||
information that is currently in the issue, we don't have enough information
|
||||
to take action. Please reach out if you have or find the answers we need so
|
||||
that we can investigate further.
|
||||
@@ -821,8 +821,15 @@ PRIVATE
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_libs.cpp
|
||||
platform/linux/linux_libs.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_gtk_integration.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_open_with_dialog.cpp
|
||||
platform/linux/linux_open_with_dialog.h
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
platform/linux/linux_wayland_integration.h
|
||||
platform/linux/linux_xlib_helper.cpp
|
||||
@@ -961,6 +968,8 @@ PRIVATE
|
||||
storage/storage_account.h
|
||||
storage/storage_cloud_blob.cpp
|
||||
storage/storage_cloud_blob.h
|
||||
storage/storage_cloud_song_cover.cpp
|
||||
storage/storage_cloud_song_cover.h
|
||||
storage/storage_domain.cpp
|
||||
storage/storage_domain.h
|
||||
storage/storage_facade.cpp
|
||||
@@ -1114,6 +1123,8 @@ if (LINUX AND DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/notifications_manager_linux.cpp
|
||||
)
|
||||
|
||||
@@ -1128,6 +1139,26 @@ if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp)
|
||||
endif()
|
||||
|
||||
if (LINUX AND TDESKTOP_DISABLE_GTK_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_open_with_dialog.cpp
|
||||
platform/linux/linux_open_with_dialog.h
|
||||
platform/linux/linux_xlib_helper.cpp
|
||||
platform/linux/linux_xlib_helper.h
|
||||
)
|
||||
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
platform/linux/linux_gtk_integration_dummy.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
|
||||
endif()
|
||||
|
||||
@@ -1187,6 +1187,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_in_reply_to" = "In reply to";
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
"lng_admin_badge" = "admin";
|
||||
"lng_owner_badge" = "owner";
|
||||
"lng_channel_badge" = "channel";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.5.4.0" />
|
||||
Version="2.5.6.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,5,4,0
|
||||
PRODUCTVERSION 2,5,4,0
|
||||
FILEVERSION 2,5,6,0
|
||||
PRODUCTVERSION 2,5,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.5.4.0"
|
||||
VALUE "FileVersion", "2.5.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.5.4.0"
|
||||
VALUE "ProductVersion", "2.5.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,5,4,0
|
||||
PRODUCTVERSION 2,5,4,0
|
||||
FILEVERSION 2,5,6,0
|
||||
PRODUCTVERSION 2,5,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.5.4.0"
|
||||
VALUE "FileVersion", "2.5.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.5.4.0"
|
||||
VALUE "ProductVersion", "2.5.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/history_drag_area.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
|
||||
#include "platform/platform_specific.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
@@ -171,7 +172,9 @@ EditCaptionBox::EditCaptionBox(
|
||||
_thumbw = 0;
|
||||
_thumbnailImageLoaded = true;
|
||||
} else {
|
||||
const auto thumbSize = st::msgFileThumbLayout.thumbSize;
|
||||
const auto thumbSize = (!media->document()->isSongWithCover()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout).thumbSize;
|
||||
const auto tw = dimensions.width(), th = dimensions.height();
|
||||
if (tw > th) {
|
||||
_thumbw = (tw * thumbSize) / th;
|
||||
@@ -183,19 +186,31 @@ EditCaptionBox::EditCaptionBox(
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
const auto options = Images::Option::Smooth
|
||||
| Images::Option::RoundedSmall
|
||||
| Images::Option::RoundedTopLeft
|
||||
| Images::Option::RoundedTopRight
|
||||
| Images::Option::RoundedBottomLeft
|
||||
| Images::Option::RoundedBottomRight;
|
||||
_thumb = App::pixmapFromImageInPlace(Images::prepare(
|
||||
image->original(),
|
||||
_thumbw * cIntRetinaFactor(),
|
||||
0,
|
||||
options,
|
||||
thumbSize,
|
||||
thumbSize));
|
||||
if (media->document()->isSongWithCover()) {
|
||||
const auto size = QSize(thumbSize, thumbSize);
|
||||
_thumb = QPixmap(size);
|
||||
_thumb.fill(Qt::transparent);
|
||||
Painter p(&_thumb);
|
||||
|
||||
HistoryView::DrawThumbnailAsSongCover(
|
||||
p,
|
||||
_documentMedia,
|
||||
QRect(QPoint(), size));
|
||||
} else {
|
||||
const auto options = Images::Option::Smooth
|
||||
| Images::Option::RoundedSmall
|
||||
| Images::Option::RoundedTopLeft
|
||||
| Images::Option::RoundedTopRight
|
||||
| Images::Option::RoundedBottomLeft
|
||||
| Images::Option::RoundedBottomRight;
|
||||
_thumb = App::pixmapFromImageInPlace(Images::prepare(
|
||||
image->original(),
|
||||
_thumbw * cIntRetinaFactor(),
|
||||
0,
|
||||
options,
|
||||
thumbSize,
|
||||
thumbSize));
|
||||
}
|
||||
_thumbnailImageLoaded = true;
|
||||
};
|
||||
_refreshThumbnail();
|
||||
@@ -539,6 +554,14 @@ void EditCaptionBox::updateEditPreview() {
|
||||
song->title,
|
||||
song->performer);
|
||||
_isAudio = true;
|
||||
|
||||
if (auto cover = song->cover; !cover.isNull()) {
|
||||
_thumb = Ui::PrepareSongCoverForThumbnail(
|
||||
cover,
|
||||
st::msgFileLayout.thumbSize);
|
||||
_thumbw = _thumb.width() / cIntRetinaFactor();
|
||||
_thumbh = _thumb.height() / cIntRetinaFactor();
|
||||
}
|
||||
}
|
||||
|
||||
const auto getExt = [&] {
|
||||
@@ -810,15 +833,21 @@ void EditCaptionBox::setupDragArea() {
|
||||
areas.photo->setDroppedCallback(droppedCallback(true));
|
||||
}
|
||||
|
||||
bool EditCaptionBox::isThumbedLayout() const {
|
||||
return (_thumbw && !_isAudio);
|
||||
}
|
||||
|
||||
void EditCaptionBox::updateBoxSize() {
|
||||
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
|
||||
if (_photo) {
|
||||
newHeight += _wayWrap->height() / 2;
|
||||
}
|
||||
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
|
||||
const auto &st = isThumbedLayout()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout;
|
||||
if (_photo || _animated) {
|
||||
newHeight += std::max(_thumbh, _gifh);
|
||||
} else if (_thumbw || _doc) {
|
||||
} else if (isThumbedLayout() || _doc) {
|
||||
newHeight += 0 + st.thumbSize + 0;
|
||||
} else {
|
||||
newHeight += st::boxTitleFont->height;
|
||||
@@ -902,7 +931,9 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
icon->paintInCenter(p, inner);
|
||||
}
|
||||
} else if (_doc) {
|
||||
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
|
||||
const auto &st = isThumbedLayout()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout;
|
||||
const auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
const auto h = 0 + st.thumbSize + 0;
|
||||
const auto nameleft = 0 + st.thumbSize + st.padding.right();
|
||||
@@ -918,18 +949,24 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
// Ui::FillRoundCorner(p, x, y, w, h, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow);
|
||||
|
||||
const auto rthumb = style::rtlrect(x + 0, y + 0, st.thumbSize, st.thumbSize, width());
|
||||
if (_thumbw) {
|
||||
if (isThumbedLayout()) {
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgFileInBg);
|
||||
|
||||
{
|
||||
if (_isAudio && _thumbw) {
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
p.setBrush(st::msgFileInBg);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(rthumb);
|
||||
}
|
||||
|
||||
const auto icon = &(_isAudio ? st::historyFileInPlay : _isImage ? st::historyFileInImage : st::historyFileInDocument);
|
||||
const auto icon = &(_isAudio
|
||||
? st::historyFileInPlay
|
||||
: _isImage
|
||||
? st::historyFileInImage
|
||||
: st::historyFileInDocument);
|
||||
icon->paintInCenter(p, rthumb);
|
||||
}
|
||||
p.setFont(st::semiboldFont);
|
||||
|
||||
@@ -96,6 +96,8 @@ private:
|
||||
void createEditMediaButton();
|
||||
bool setPreparedList(Ui::PreparedList &&list);
|
||||
|
||||
bool isThumbedLayout() const;
|
||||
|
||||
inline QString getNewMediaPath() {
|
||||
return _preparedList.files.empty()
|
||||
? QString()
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "history/history.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "window/window_session_controller.h" // onShowAddContact()
|
||||
#include "window/window_session_controller.h" // showAddContact()
|
||||
#include "facades.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_profile.h"
|
||||
@@ -112,7 +112,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
box->addLeftButton(
|
||||
tr::lng_profile_add_contact(),
|
||||
[=] { controller->widget()->onShowAddContact(); });
|
||||
[=] { controller->widget()->showAddContact(); });
|
||||
};
|
||||
return Box<PeerListBox>(
|
||||
std::make_unique<ContactsBoxController>(
|
||||
|
||||
@@ -1138,7 +1138,7 @@ void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
SendMenu::DefaultSilentCallback(send),
|
||||
SendMenu::DefaultScheduleCallback(this, type, send));
|
||||
|
||||
if (!_menu->actions().empty()) {
|
||||
if (!_menu->empty()) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ bool TabbedSelector::preventAutoHide() const {
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasMenu() const {
|
||||
return (_menu && !_menu->actions().empty());
|
||||
return (_menu && !_menu->empty());
|
||||
}
|
||||
|
||||
QImage TabbedSelector::grabForAnimation() {
|
||||
@@ -877,18 +877,22 @@ void TabbedSelector::scrollToY(int y) {
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::contextMenuEvent(QContextMenuEvent *e) {
|
||||
void TabbedSelector::showMenuWithType(SendMenu::Type type) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(this);
|
||||
const auto type = _sendMenuType
|
||||
? _sendMenuType()
|
||||
: SendMenu::Type::Disabled;
|
||||
currentTab()->widget()->fillContextMenu(_menu, type);
|
||||
|
||||
if (!_menu->actions().empty()) {
|
||||
if (!_menu->empty()) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
||||
return events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
return e->type() == QEvent::ContextMenu;
|
||||
}) | rpl::to_empty;
|
||||
}
|
||||
|
||||
TabbedSelector::Inner::Inner(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
rpl::producer<> cancelled() const;
|
||||
rpl::producer<> checkForHide() const;
|
||||
rpl::producer<> slideFinished() const;
|
||||
rpl::producer<> contextMenuRequested() const;
|
||||
|
||||
void setRoundRadius(int radius);
|
||||
void refreshStickers();
|
||||
@@ -109,9 +110,7 @@ public:
|
||||
_beforeHidingCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void setSendMenuType(Fn<SendMenu::Type()> &&callback) {
|
||||
_sendMenuType = std::move(callback);
|
||||
}
|
||||
void showMenuWithType(SendMenu::Type type);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e);
|
||||
@@ -127,7 +126,6 @@ public:
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
private:
|
||||
class Tab {
|
||||
@@ -228,8 +226,6 @@ private:
|
||||
Fn<void(SelectorTab)> _afterShownCallback;
|
||||
Fn<void(SelectorTab)> _beforeHidingCallback;
|
||||
|
||||
Fn<SendMenu::Type()> _sendMenuType;
|
||||
|
||||
rpl::event_stream<> _showRequests;
|
||||
rpl::event_stream<> _slideFinished;
|
||||
|
||||
|
||||
@@ -99,6 +99,21 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Fix media viewer updating when screen resolution is changed.\n"
|
||||
},
|
||||
{
|
||||
2005005,
|
||||
"- Fix recording of audio in voice chats.\n"
|
||||
|
||||
"- Fix media viewer zoom and crashing.\n"
|
||||
},
|
||||
{
|
||||
2005006,
|
||||
"- Press Up arrow to edit your last sent comment.\n"
|
||||
|
||||
"- Add more information to date tooltips "
|
||||
"in Recent Actions and channel comments.\n"
|
||||
|
||||
"- Bug and crash fixes.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/sandbox.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "storage/localstorage.h"
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/parse_helper.h"
|
||||
#include "facades.h"
|
||||
|
||||
|
||||
@@ -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 = 2005004;
|
||||
constexpr auto AppVersionStr = "2.5.4";
|
||||
constexpr auto AppVersion = 2005006;
|
||||
constexpr auto AppVersionStr = "2.5.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_gif.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "storage/storage_cloud_song_cover.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
@@ -540,6 +541,13 @@ void DocumentData::setattributes(
|
||||
songData->duration = data.vduration().v;
|
||||
songData->title = qs(data.vtitle().value_or_empty());
|
||||
songData->performer = qs(data.vperformer().value_or_empty());
|
||||
|
||||
if (!hasThumbnail()
|
||||
&& !songData->title.isEmpty()
|
||||
&& !songData->performer.isEmpty()) {
|
||||
|
||||
Storage::CloudSongCover::LoadThumbnailFromExternal(this);
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDdocumentAttributeFilename &data) {
|
||||
_filename = qs(data.vfile_name());
|
||||
@@ -1488,6 +1496,10 @@ bool DocumentData::isSong() const {
|
||||
return (type == SongDocument);
|
||||
}
|
||||
|
||||
bool DocumentData::isSongWithCover() const {
|
||||
return isSong() && hasThumbnail();
|
||||
}
|
||||
|
||||
bool DocumentData::isAudioFile() const {
|
||||
if (isVoiceMessage()) {
|
||||
return false;
|
||||
|
||||
@@ -139,6 +139,7 @@ public:
|
||||
[[nodiscard]] bool isVoiceMessage() const;
|
||||
[[nodiscard]] bool isVideoMessage() const;
|
||||
[[nodiscard]] bool isSong() const;
|
||||
[[nodiscard]] bool isSongWithCover() const;
|
||||
[[nodiscard]] bool isAudioFile() const;
|
||||
[[nodiscard]] bool isVideoFile() const;
|
||||
[[nodiscard]] bool isAnimation() const;
|
||||
|
||||
@@ -145,4 +145,20 @@ void Groups::refreshViews(const HistoryItemsList &items) {
|
||||
}
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> Groups::findItemToEdit(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto group = find(item);
|
||||
if (!group) {
|
||||
return item;
|
||||
}
|
||||
const auto &list = group->items;
|
||||
const auto it = ranges::find_if(
|
||||
list,
|
||||
ranges::not_fn(&HistoryItem::emptyText));
|
||||
if (it == end(list)) {
|
||||
return list.front();
|
||||
}
|
||||
return (*it);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
|
||||
const Group *find(not_null<HistoryItem*> item) const;
|
||||
|
||||
not_null<HistoryItem*> findItemToEdit(not_null<HistoryItem*> item) const;
|
||||
|
||||
private:
|
||||
HistoryItemsList::const_iterator findPositionForItem(
|
||||
const HistoryItemsList &group,
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_replies_list.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_service.h"
|
||||
@@ -626,4 +627,22 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
|
||||
return (list.size() == skipped);
|
||||
}
|
||||
|
||||
HistoryItem *RepliesList::lastEditableMessage() {
|
||||
const auto message = [&](MsgId msgId) {
|
||||
return _history->owner().message(_history->channelId(), msgId);
|
||||
};
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
auto proj = [&](MsgId msgId) {
|
||||
if (const auto item = message(msgId)) {
|
||||
return item->allowsEdit(now);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto it = ranges::find_if(_list, std::move(proj));
|
||||
return (it == end(_list))
|
||||
? nullptr
|
||||
: _history->owner().groups().findItemToEdit(message(*it)).get();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
|
||||
[[nodiscard]] rpl::producer<int> fullCount() const;
|
||||
|
||||
[[nodiscard]] HistoryItem *lastEditableMessage();
|
||||
|
||||
private:
|
||||
struct Viewer;
|
||||
|
||||
|
||||
@@ -543,7 +543,8 @@ int32 ScheduledMessages::countListHash(const List &list) const {
|
||||
return HashFinalize(hash);
|
||||
}
|
||||
|
||||
HistoryItem *ScheduledMessages::lastSentMessage(not_null<History*> history) {
|
||||
HistoryItem *ScheduledMessages::lastEditableMessage(
|
||||
not_null<History*> history) {
|
||||
const auto i = _data.find(history);
|
||||
if (i == end(_data)) {
|
||||
return nullptr;
|
||||
@@ -552,10 +553,16 @@ HistoryItem *ScheduledMessages::lastSentMessage(not_null<History*> history) {
|
||||
|
||||
sort(list);
|
||||
const auto items = ranges::view::reverse(list.items);
|
||||
const auto it = ranges::find_if(
|
||||
items,
|
||||
&HistoryItem::canBeEditedFromHistory);
|
||||
return (it == end(items)) ? nullptr : (*it).get();
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
auto proj = [&](const OwnedItem &item) {
|
||||
return item->allowsEdit(now);
|
||||
};
|
||||
|
||||
const auto it = ranges::find_if(items, std::move(proj));
|
||||
return (it == end(items))
|
||||
? nullptr
|
||||
: history->owner().groups().findItemToEdit((*it).get()).get();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -32,7 +32,8 @@ public:
|
||||
[[nodiscard]] HistoryItem *lookupItem(PeerId peer, MsgId msg) const;
|
||||
[[nodiscard]] HistoryItem *lookupItem(FullMsgId itemId) const;
|
||||
[[nodiscard]] int count(not_null<History*> history) const;
|
||||
[[nodiscard]] HistoryItem *lastSentMessage(not_null<History*> history);
|
||||
[[nodiscard]] HistoryItem *lastEditableMessage(
|
||||
not_null<History*> history);
|
||||
|
||||
void checkEntitiesAndUpdate(const MTPDmessage &data);
|
||||
void apply(const MTPDupdateNewScheduledMessage &update);
|
||||
|
||||
@@ -1841,7 +1841,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||
selectByMouse(globalPosition);
|
||||
}
|
||||
});
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(e->globalPos());
|
||||
@@ -2281,7 +2281,7 @@ void InnerWidget::refreshEmptyLabel() {
|
||||
resizeEmptyLabel();
|
||||
_empty->setClickHandlerFilter([=](const auto &...) {
|
||||
if (_emptyState == EmptyState::NoContacts) {
|
||||
App::wnd()->onShowAddContact();
|
||||
App::wnd()->showAddContact();
|
||||
} else if (_emptyState == EmptyState::EmptyFolder) {
|
||||
editOpenedFilter();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
#include "core/application.h"
|
||||
@@ -509,8 +510,17 @@ QString InnerWidget::tooltipText() const {
|
||||
if (_mouseCursorState == CursorState::Date
|
||||
&& _mouseAction == MouseAction::None) {
|
||||
if (const auto view = App::hoveredItem()) {
|
||||
auto dateText = view->dateTime().toString(
|
||||
QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||
const auto format = QLocale::system().dateTimeFormat(
|
||||
QLocale::LongFormat);
|
||||
auto dateText = HistoryView::DateTooltipText(view);
|
||||
|
||||
const auto sentIt = _itemDates.find(view->data());
|
||||
if (sentIt != end(_itemDates)) {
|
||||
dateText += '\n' + tr::lng_sent_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(sentIt->second).toString(format));
|
||||
}
|
||||
return dateText;
|
||||
}
|
||||
} else if (_mouseCursorState == CursorState::Forwarded
|
||||
@@ -722,7 +732,10 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
|
||||
}
|
||||
|
||||
auto count = 0;
|
||||
const auto addOne = [&](OwnedItem item) {
|
||||
const auto addOne = [&](OwnedItem item, TimeId sentDate) {
|
||||
if (sentDate) {
|
||||
_itemDates.emplace(item->data(), sentDate);
|
||||
}
|
||||
_eventIds.emplace(id);
|
||||
_itemsByData.emplace(item->data(), item.get());
|
||||
addToItems.push_back(std::move(item));
|
||||
@@ -1177,7 +1190,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(e->globalPos());
|
||||
|
||||
@@ -242,6 +242,7 @@ private:
|
||||
std::vector<OwnedItem> _items;
|
||||
std::set<uint64> _eventIds;
|
||||
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
|
||||
base::flat_map<not_null<const HistoryItem*>, TimeId> _itemDates;
|
||||
base::flat_set<FullMsgId> _animatedStickersPlayed;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
|
||||
@@ -45,6 +45,16 @@ TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
|
||||
return result;
|
||||
}
|
||||
|
||||
TimeId ExtractSentDate(const MTPMessage &message) {
|
||||
return message.match([&](const MTPDmessageEmpty &) {
|
||||
return 0;
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
return data.vdate().v;
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return data.vdate().v;
|
||||
});
|
||||
}
|
||||
|
||||
MTPMessage PrepareLogMessage(
|
||||
const MTPMessage &message,
|
||||
MsgId newId,
|
||||
@@ -380,7 +390,7 @@ void GenerateItems(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
const MTPDchannelAdminLogEvent &event,
|
||||
Fn<void(OwnedItem item)> callback) {
|
||||
Fn<void(OwnedItem item, TimeId sentDate)> callback) {
|
||||
Expects(history->peer->isChannel());
|
||||
|
||||
const auto session = &history->session();
|
||||
@@ -389,8 +399,10 @@ void GenerateItems(
|
||||
const auto channel = history->peer->asChannel();
|
||||
const auto &action = event.vaction();
|
||||
const auto date = event.vdate().v;
|
||||
const auto addPart = [&](not_null<HistoryItem*> item) {
|
||||
return callback(OwnedItem(delegate, item));
|
||||
const auto addPart = [&](
|
||||
not_null<HistoryItem*> item,
|
||||
TimeId sentDate = 0) {
|
||||
return callback(OwnedItem(delegate, item), sentDate);
|
||||
};
|
||||
|
||||
using Flag = MTPDmessage::Flag;
|
||||
@@ -545,13 +557,15 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
|
||||
auto detachExistingItem = false;
|
||||
addPart(history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem));
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
}, [&](const auto &) {
|
||||
auto text = tr::lng_admin_log_unpinned_message(tr::now, lt_from, fromLinkText);
|
||||
addSimpleServiceMessage(text);
|
||||
@@ -598,10 +612,15 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
|
||||
auto detachExistingItem = false;
|
||||
addPart(history->createItem(
|
||||
PrepareLogMessage(action.vmessage(), history->nextNonHistoryEntryId(), date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem));
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
};
|
||||
|
||||
auto createParticipantJoin = [&]() {
|
||||
@@ -740,10 +759,15 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
|
||||
auto detachExistingItem = false;
|
||||
addPart(history->createItem(
|
||||
PrepareLogMessage(action.vmessage(), history->nextNonHistoryEntryId(), date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem));
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
};
|
||||
|
||||
auto createChangeLinkedChat = [&](const MTPDchannelAdminLogEventActionChangeLinkedChat &action) {
|
||||
|
||||
@@ -22,7 +22,7 @@ void GenerateItems(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
const MTPDchannelAdminLogEvent &event,
|
||||
Fn<void(OwnedItem item)> callback);
|
||||
Fn<void(OwnedItem item, TimeId sentDate)> callback);
|
||||
|
||||
// Smart pointer wrapper for HistoryItem* that destroys the owned item.
|
||||
class OwnedItem {
|
||||
|
||||
@@ -2704,15 +2704,16 @@ MsgId History::msgIdForRead() const {
|
||||
: result;
|
||||
}
|
||||
|
||||
HistoryItem *History::lastSentMessage() const {
|
||||
HistoryItem *History::lastEditableMessage() const {
|
||||
if (!loadedAtBottom()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto now = base::unixtime::now();
|
||||
for (const auto &block : ranges::view::reverse(blocks)) {
|
||||
for (const auto &message : ranges::view::reverse(block->messages)) {
|
||||
const auto item = message->data();
|
||||
if (item->canBeEditedFromHistory()) {
|
||||
return item;
|
||||
if (item->allowsEdit(now)) {
|
||||
return owner().groups().findItemToEdit(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ public:
|
||||
MsgId minMsgId() const;
|
||||
MsgId maxMsgId() const;
|
||||
MsgId msgIdForRead() const;
|
||||
HistoryItem *lastSentMessage() const;
|
||||
HistoryItem *lastEditableMessage() const;
|
||||
|
||||
void resizeToWidth(int newWidth);
|
||||
void forceFullResize();
|
||||
|
||||
@@ -1851,7 +1851,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(e->globalPos());
|
||||
@@ -3313,40 +3313,7 @@ QString HistoryInner::tooltipText() const {
|
||||
if (_mouseCursorState == CursorState::Date
|
||||
&& _mouseAction == MouseAction::None) {
|
||||
if (const auto view = App::hoveredItem()) {
|
||||
auto dateText = view->dateTime().toString(
|
||||
QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||
if (const auto editedDate = view->displayedEditDate()) {
|
||||
dateText += '\n' + tr::lng_edited_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(editedDate).toString(
|
||||
QLocale::system().dateTimeFormat(
|
||||
QLocale::LongFormat)));
|
||||
}
|
||||
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) {
|
||||
dateText += '\n' + tr::lng_forwarded_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(forwarded->originalDate).toString(
|
||||
QLocale::system().dateTimeFormat(
|
||||
QLocale::LongFormat)));
|
||||
if (const auto media = view->media()) {
|
||||
if (media->hidesForwardedInfo()) {
|
||||
dateText += "\n" + tr::lng_forwarded(
|
||||
tr::now,
|
||||
lt_user,
|
||||
(forwarded->originalSender
|
||||
? forwarded->originalSender->shortName()
|
||||
: forwarded->hiddenSenderInfo->firstName));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
|
||||
if (msgsigned->isElided && !msgsigned->isAnonymousRank) {
|
||||
dateText += '\n' + tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
|
||||
}
|
||||
}
|
||||
return dateText;
|
||||
return HistoryView::DateTooltipText(view);
|
||||
}
|
||||
} else if (_mouseCursorState == CursorState::Forwarded
|
||||
&& _mouseAction == MouseAction::None) {
|
||||
|
||||
@@ -762,27 +762,6 @@ bool HistoryItem::canUpdateDate() const {
|
||||
return isScheduled();
|
||||
}
|
||||
|
||||
bool HistoryItem::canBeEditedFromHistory() const {
|
||||
// Skip if message is editing media.
|
||||
if (isEditingMedia()) {
|
||||
return false;
|
||||
}
|
||||
// Skip if message is video message or sticker.
|
||||
if (const auto m = media()) {
|
||||
// Skip only if media is not webpage.
|
||||
if (!m->webpage() && !m->allowsEditCaption()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((IsServerMsgId(id) || isScheduled())
|
||||
&& !serviceMsg()
|
||||
&& (out() || history()->peer->isSelf())
|
||||
&& !Has<HistoryMessageForwarded>()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistoryItem::sendFailed() {
|
||||
Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending);
|
||||
Expects(!(_clientFlags & MTPDmessage_ClientFlag::f_failed));
|
||||
|
||||
@@ -390,8 +390,6 @@ public:
|
||||
void updateDate(TimeId newDate);
|
||||
[[nodiscard]] bool canUpdateDate() const;
|
||||
|
||||
[[nodiscard]] bool canBeEditedFromHistory() const;
|
||||
|
||||
virtual ~HistoryItem();
|
||||
|
||||
MsgId id;
|
||||
|
||||
@@ -413,23 +413,13 @@ ReplyMarkupClickHandler::ReplyMarkupClickHandler(
|
||||
|
||||
// Copy to clipboard support.
|
||||
QString ReplyMarkupClickHandler::copyToClipboardText() const {
|
||||
if (const auto button = getButton()) {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
if (button->type == Type::Url || button->type == Type::Auth) {
|
||||
return QString::fromUtf8(button->data);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
const auto button = getUrlButton();
|
||||
return button ? QString::fromUtf8(button->data) : QString();
|
||||
}
|
||||
|
||||
QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
|
||||
if (const auto button = getButton()) {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
if (button->type == Type::Url || button->type == Type::Auth) {
|
||||
return tr::lng_context_copy_link(tr::now);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
const auto button = getUrlButton();
|
||||
return button ? tr::lng_context_copy_link(tr::now) : QString();
|
||||
}
|
||||
|
||||
// Finds the corresponding button in the items markup struct.
|
||||
@@ -440,6 +430,17 @@ const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const {
|
||||
return HistoryMessageMarkupButton::Get(_owner, _itemId, _row, _column);
|
||||
}
|
||||
|
||||
auto ReplyMarkupClickHandler::getUrlButton() const
|
||||
-> const HistoryMessageMarkupButton* {
|
||||
if (const auto button = getButton()) {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
if (button->type == Type::Url || button->type == Type::Auth) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ReplyMarkupClickHandler::onClickImpl() const {
|
||||
if (const auto item = _owner->message(_itemId)) {
|
||||
App::activateBotCommand(item, _row, _column);
|
||||
@@ -454,6 +455,19 @@ QString ReplyMarkupClickHandler::buttonText() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString ReplyMarkupClickHandler::tooltip() const {
|
||||
const auto button = getUrlButton();
|
||||
const auto url = button ? QString::fromUtf8(button->data) : QString();
|
||||
const auto text = _fullDisplayed ? QString() : buttonText();
|
||||
if (!url.isEmpty() && !text.isEmpty()) {
|
||||
return QString("%1\n\n%2").arg(text).arg(url);
|
||||
} else if (url.isEmpty() != text.isEmpty()) {
|
||||
return text + url;
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
ReplyKeyboard::Button::Button() = default;
|
||||
ReplyKeyboard::Button::Button(Button &&other) = default;
|
||||
ReplyKeyboard::Button &ReplyKeyboard::Button::operator=(
|
||||
|
||||
@@ -251,9 +251,7 @@ public:
|
||||
int column,
|
||||
FullMsgId context);
|
||||
|
||||
QString tooltip() const override {
|
||||
return _fullDisplayed ? QString() : buttonText();
|
||||
}
|
||||
QString tooltip() const override;
|
||||
|
||||
void setFullDisplayed(bool full) {
|
||||
_fullDisplayed = full;
|
||||
@@ -269,6 +267,8 @@ public:
|
||||
// than the one was used when constructing the handler, but not a big deal.
|
||||
const HistoryMessageMarkupButton *getButton() const;
|
||||
|
||||
const HistoryMessageMarkupButton *getUrlButton() const;
|
||||
|
||||
// We hold only FullMsgId, not HistoryItem*, because all click handlers
|
||||
// are activated async and the item may be already destroyed.
|
||||
void setMessageId(const FullMsgId &msgId) {
|
||||
|
||||
@@ -846,6 +846,11 @@ void HistoryWidget::initTabbedSelector() {
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
auto filter = rpl::filter([=] {
|
||||
return !isHidden();
|
||||
});
|
||||
using Selector = TabbedSelector;
|
||||
|
||||
selector->emojiChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden() && !_field->isHidden();
|
||||
@@ -854,27 +859,24 @@ void HistoryWidget::initTabbedSelector() {
|
||||
}, lifetime());
|
||||
|
||||
selector->fileChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::FileChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
|
||||
sendExistingDocument(data.document, data.options);
|
||||
}, lifetime());
|
||||
|
||||
selector->photoChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::PhotoChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::PhotoChosen data) {
|
||||
sendExistingPhoto(data.photo, data.options);
|
||||
}, lifetime());
|
||||
|
||||
selector->inlineResultChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::InlineChosen data) {
|
||||
sendInlineResult(data);
|
||||
}, lifetime());
|
||||
|
||||
selector->setSendMenuType([=] { return sendMenuType(); });
|
||||
selector->contextMenuRequested(
|
||||
) | filter | rpl::start_with_next([=] {
|
||||
selector->showMenuWithType(sendMenuType());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::supportInitAutocomplete() {
|
||||
@@ -5020,10 +5022,9 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
||||
_scroll->keyPressEvent(e);
|
||||
} else if (e->key() == Qt::Key_Up && !commonModifiers) {
|
||||
const auto item = _history
|
||||
? _history->lastSentMessage()
|
||||
? _history->lastEditableMessage()
|
||||
: nullptr;
|
||||
if (item
|
||||
&& item->allowsEdit(base::unixtime::now())
|
||||
&& _field->empty()
|
||||
&& !_editMsgId
|
||||
&& !_replyToId) {
|
||||
|
||||
@@ -1410,7 +1410,10 @@ void ComposeControls::initTabbedSelector() {
|
||||
selector->inlineResultChosen(
|
||||
) | rpl::start_to_stream(_inlineResultChosen, wrap->lifetime());
|
||||
|
||||
selector->setSendMenuType([=] { return sendMenuType(); });
|
||||
selector->contextMenuRequested(
|
||||
) | rpl::start_with_next([=] {
|
||||
selector->showMenuWithType(sendMenuType());
|
||||
}, wrap->lifetime());
|
||||
}
|
||||
|
||||
void ComposeControls::initSendButton() {
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/media/history_view_large_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -157,6 +158,40 @@ TextSelection ShiftItemSelection(
|
||||
return ShiftItemSelection(selection, byText.length());
|
||||
}
|
||||
|
||||
QString DateTooltipText(not_null<Element*> view) {
|
||||
const auto format = QLocale::system().dateTimeFormat(QLocale::LongFormat);
|
||||
auto dateText = view->dateTime().toString(format);
|
||||
if (const auto editedDate = view->displayedEditDate()) {
|
||||
dateText += '\n' + tr::lng_edited_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(editedDate).toString(format));
|
||||
}
|
||||
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) {
|
||||
dateText += '\n' + tr::lng_forwarded_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(forwarded->originalDate).toString(format));
|
||||
if (const auto media = view->media()) {
|
||||
if (media->hidesForwardedInfo()) {
|
||||
dateText += '\n' + tr::lng_forwarded(
|
||||
tr::now,
|
||||
lt_user,
|
||||
(forwarded->originalSender
|
||||
? forwarded->originalSender->shortName()
|
||||
: forwarded->hiddenSenderInfo->firstName));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
|
||||
if (msgsigned->isElided && !msgsigned->isAnonymousRank) {
|
||||
dateText += '\n'
|
||||
+ tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
|
||||
}
|
||||
}
|
||||
return dateText;
|
||||
}
|
||||
|
||||
void UnreadBar::init(const QString &string) {
|
||||
text = string;
|
||||
width = st::semiboldFont->width(text);
|
||||
|
||||
@@ -126,6 +126,8 @@ TextSelection ShiftItemSelection(
|
||||
TextSelection selection,
|
||||
const Ui::Text::String &byText);
|
||||
|
||||
QString DateTooltipText(not_null<Element*> view);
|
||||
|
||||
// Any HistoryView::Element can have this Component for
|
||||
// displaying the unread messages bar above the message.
|
||||
struct UnreadBar : public RuntimeComponent<UnreadBar, Element> {
|
||||
|
||||
@@ -1216,8 +1216,7 @@ QString ListWidget::tooltipText() const {
|
||||
? _overElement->data().get()
|
||||
: nullptr;
|
||||
if (_mouseCursorState == CursorState::Date && item) {
|
||||
return _overElement->dateTime().toString(
|
||||
QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||
return HistoryView::DateTooltipText(_overElement);
|
||||
} else if (_mouseCursorState == CursorState::Forwarded && item) {
|
||||
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->text.toString();
|
||||
@@ -1832,7 +1831,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
_overState));
|
||||
|
||||
_menu = FillContextMenu(this, request);
|
||||
if (_menu && !_menu->actions().empty()) {
|
||||
if (_menu && !_menu->empty()) {
|
||||
_menu->popup(e->globalPos());
|
||||
e->accept();
|
||||
} else if (_menu) {
|
||||
|
||||
@@ -492,13 +492,11 @@ void RepliesWidget::setupComposeControls() {
|
||||
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
|
||||
if (e->key() == Qt::Key_Up) {
|
||||
if (!_composeControls->isEditingMessage()) {
|
||||
// #TODO replies edit last sent message
|
||||
//auto &messages = session().data().scheduledMessages();
|
||||
//if (const auto item = messages.lastSentMessage(_history)) {
|
||||
// _inner->editMessageRequestNotify(item->fullId());
|
||||
//} else {
|
||||
if (const auto item = _replies->lastEditableMessage()) {
|
||||
_inner->editMessageRequestNotify(item->fullId());
|
||||
} else {
|
||||
_scroll->keyPressEvent(e);
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
_scroll->keyPressEvent(e);
|
||||
}
|
||||
|
||||
@@ -235,8 +235,9 @@ void ScheduledWidget::setupComposeControls() {
|
||||
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
|
||||
if (e->key() == Qt::Key_Up) {
|
||||
if (!_composeControls->isEditingMessage()) {
|
||||
auto &messages = session().data().scheduledMessages();
|
||||
if (const auto item = messages.lastSentMessage(_history)) {
|
||||
const auto item = session().data().scheduledMessages()
|
||||
.lastEditableMessage(_history);
|
||||
if (item) {
|
||||
_inner->editMessageRequestNotify(item->fullId());
|
||||
} else {
|
||||
_scroll->keyPressEvent(e);
|
||||
|
||||
@@ -256,7 +256,7 @@ void TopBarWidget::showMenu() {
|
||||
_controller,
|
||||
_activeChat,
|
||||
addAction);
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu.destroy();
|
||||
} else {
|
||||
_menu->moveToRight((parentWidget()->width() - width()) + st::topBarMenuPosition.x(), st::topBarMenuPosition.y());
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "layout.h" // FullSelection
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -442,14 +443,14 @@ void Document::draw(
|
||||
}
|
||||
} else {
|
||||
p.setPen(Qt::NoPen);
|
||||
if (selected) {
|
||||
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
|
||||
} else {
|
||||
p.setBrush(outbg ? st::msgFileOutBg : st::msgFileInBg);
|
||||
}
|
||||
|
||||
{
|
||||
const auto coverDrawn = _data->isSongWithCover()
|
||||
&& DrawThumbnailAsSongCover(p, _dataMedia, inner, selected);
|
||||
if (!coverDrawn) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setBrush(selected
|
||||
? (outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected)
|
||||
: (outbg ? st::msgFileOutBg : st::msgFileInBg));
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
@@ -581,7 +582,7 @@ void Document::ensureDataMediaCreated() const {
|
||||
return;
|
||||
}
|
||||
_dataMedia = _data->createMediaView();
|
||||
if (Get<HistoryDocumentThumbed>()) {
|
||||
if (Get<HistoryDocumentThumbed>() || _data->isSongWithCover()) {
|
||||
_dataMedia->thumbnailWanted(_realParent->fullId());
|
||||
}
|
||||
history()->owner().registerHeavyViewPart(_parent);
|
||||
@@ -1072,4 +1073,49 @@ Ui::Text::String Document::createCaption() {
|
||||
timestampLinkBase);
|
||||
}
|
||||
|
||||
bool DrawThumbnailAsSongCover(
|
||||
Painter &p,
|
||||
const std::shared_ptr<Data::DocumentMedia> &dataMedia,
|
||||
const QRect &rect,
|
||||
const bool selected) {
|
||||
if (!dataMedia) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QPixmap cover;
|
||||
|
||||
const auto ow = rect.width();
|
||||
const auto oh = rect.height();
|
||||
const auto r = ImageRoundRadius::Ellipse;
|
||||
const auto c = RectPart::AllCorners;
|
||||
const auto color = &st::songCoverOverlayFg;
|
||||
const auto aspectRatio = Qt::KeepAspectRatioByExpanding;
|
||||
|
||||
const auto scaled = [&](not_null<Image*> image) -> std::pair<int, int> {
|
||||
const auto size = image->size().scaled(ow, oh, aspectRatio);
|
||||
return { size.width(), size.height() };
|
||||
};
|
||||
|
||||
if (const auto normal = dataMedia->thumbnail()) {
|
||||
const auto &[w, h] = scaled(normal);
|
||||
cover = normal->pixSingle(w, h, ow, oh, r, c, color);
|
||||
} else if (const auto blurred = dataMedia->thumbnailInline()) {
|
||||
const auto &[w, h] = scaled(blurred);
|
||||
cover = blurred->pixBlurredSingle(w, h, ow, oh, r, c, color);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (selected) {
|
||||
auto selectedCover = Images::prepareColored(
|
||||
p.textPalette().selectOverlay,
|
||||
cover.toImage());
|
||||
cover = QPixmap::fromImage(
|
||||
std::move(selectedCover),
|
||||
Qt::ColorOnly);
|
||||
}
|
||||
p.drawPixmap(rect.topLeft(), cover);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -143,4 +143,10 @@ private:
|
||||
|
||||
};
|
||||
|
||||
bool DrawThumbnailAsSongCover(
|
||||
Painter &p,
|
||||
const std::shared_ptr<Data::DocumentMedia> &dataMedia,
|
||||
const QRect &rect,
|
||||
const bool selected = false);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
@@ -865,16 +866,21 @@ void File::paint(Painter &p, const QRect &clip, const PaintContext *context) con
|
||||
|
||||
auto inner = style::rtlrect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize, _width);
|
||||
p.setPen(Qt::NoPen);
|
||||
if (isThumbAnimation()) {
|
||||
auto over = _animation->a_thumbOver.value(1.);
|
||||
p.setBrush(anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
|
||||
} else {
|
||||
bool over = ClickHandler::showAsActive(_document->loading() ? _cancel : _open);
|
||||
p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
|
||||
}
|
||||
|
||||
{
|
||||
const auto coverDrawn = _document->isSongWithCover()
|
||||
&& HistoryView::DrawThumbnailAsSongCover(p, _documentMedia, inner);
|
||||
if (!coverDrawn) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
if (isThumbAnimation()) {
|
||||
const auto over = _animation->a_thumbOver.value(1.);
|
||||
p.setBrush(
|
||||
anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
|
||||
} else {
|
||||
const auto over = ClickHandler::showAsActive(_document->loading()
|
||||
? _cancel
|
||||
: _open);
|
||||
p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
|
||||
}
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ void Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
SendMenu::DefaultSilentCallback(send),
|
||||
SendMenu::DefaultScheduleCallback(this, type, send));
|
||||
|
||||
if (!_menu->actions().empty()) {
|
||||
if (!_menu->empty()) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,10 +478,17 @@ void Widget::resetAccount() {
|
||||
_resetRequest = 0;
|
||||
|
||||
Ui::hideLayer();
|
||||
moveToStep(
|
||||
new SignupWidget(this, _account, getData()),
|
||||
StackAction::Replace,
|
||||
Animate::Forward);
|
||||
if (getData()->phone.isEmpty()) {
|
||||
moveToStep(
|
||||
new QrWidget(this, _account, getData()),
|
||||
StackAction::Replace,
|
||||
Animate::Back);
|
||||
} else {
|
||||
moveToStep(
|
||||
new SignupWidget(this, _account, getData()),
|
||||
StackAction::Replace,
|
||||
Animate::Forward);
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_resetRequest = 0;
|
||||
|
||||
|
||||
@@ -146,11 +146,24 @@ void MainWindow::createTrayIconMenu() {
|
||||
: tr::lng_enable_notifications_from_tray(tr::now);
|
||||
|
||||
if (Platform::IsLinux() && !Platform::IsWayland()) {
|
||||
trayIconMenu->addAction(tr::lng_open_from_tray(tr::now), this, SLOT(showFromTray()));
|
||||
trayIconMenu->addAction(tr::lng_open_from_tray(tr::now), [=] {
|
||||
showFromTray();
|
||||
});
|
||||
}
|
||||
trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray()));
|
||||
trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray()));
|
||||
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), this, SLOT(quitFromTray()));
|
||||
const auto showLifetime = std::make_shared<rpl::lifetime>();
|
||||
trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), [=] {
|
||||
if (_activeForTrayIconAction) {
|
||||
minimizeToTray();
|
||||
} else {
|
||||
showFromTrayMenu();
|
||||
}
|
||||
});
|
||||
trayIconMenu->addAction(notificationActionText, [=] {
|
||||
toggleDisplayNotifyFromTray();
|
||||
});
|
||||
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), [=] {
|
||||
quitFromTray();
|
||||
});
|
||||
|
||||
initTrayMenuHook();
|
||||
}
|
||||
@@ -635,18 +648,17 @@ void MainWindow::updateTrayMenu(bool force) {
|
||||
|
||||
auto actions = trayIconMenu->actions();
|
||||
if (Platform::IsLinux() && !Platform::IsWayland()) {
|
||||
auto minimizeAction = actions.at(1);
|
||||
const auto minimizeAction = actions.at(1);
|
||||
minimizeAction->setEnabled(isVisible());
|
||||
} else {
|
||||
updateIsActive();
|
||||
auto active = Platform::IsWayland() ? isVisible() : isActive();
|
||||
auto toggleAction = actions.at(0);
|
||||
disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(minimizeToTray()));
|
||||
disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(showFromTray()));
|
||||
connect(toggleAction, SIGNAL(triggered(bool)), this, active ? SLOT(minimizeToTray()) : SLOT(showFromTray()));
|
||||
toggleAction->setText(active
|
||||
? tr::lng_minimize_to_tray(tr::now)
|
||||
: tr::lng_open_from_tray(tr::now));
|
||||
const auto active = isActiveForTrayMenu();
|
||||
if (_activeForTrayIconAction != active) {
|
||||
_activeForTrayIconAction = active;
|
||||
const auto toggleAction = actions.at(0);
|
||||
toggleAction->setText(_activeForTrayIconAction
|
||||
? tr::lng_minimize_to_tray(tr::now)
|
||||
: tr::lng_open_from_tray(tr::now));
|
||||
}
|
||||
}
|
||||
auto notificationAction = actions.at(Platform::IsLinux() && !Platform::IsWayland() ? 2 : 1);
|
||||
auto notificationActionText = Core::App().settings().desktopNotify()
|
||||
@@ -657,8 +669,10 @@ void MainWindow::updateTrayMenu(bool force) {
|
||||
psTrayMenuUpdated();
|
||||
}
|
||||
|
||||
void MainWindow::onShowAddContact() {
|
||||
if (isHidden()) showFromTray();
|
||||
void MainWindow::showAddContact() {
|
||||
if (isHidden()) {
|
||||
showFromTray();
|
||||
}
|
||||
|
||||
if (const auto controller = sessionController()) {
|
||||
Ui::show(
|
||||
@@ -667,8 +681,10 @@ void MainWindow::onShowAddContact() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onShowNewGroup() {
|
||||
if (isHidden()) showFromTray();
|
||||
void MainWindow::showNewGroup() {
|
||||
if (isHidden()) {
|
||||
showFromTray();
|
||||
}
|
||||
|
||||
if (const auto controller = sessionController()) {
|
||||
Ui::show(
|
||||
@@ -677,8 +693,10 @@ void MainWindow::onShowNewGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onShowNewChannel() {
|
||||
if (isHidden()) showFromTray();
|
||||
void MainWindow::showNewChannel() {
|
||||
if (isHidden()) {
|
||||
showFromTray();
|
||||
}
|
||||
|
||||
if (const auto controller = sessionController()) {
|
||||
Ui::show(
|
||||
@@ -720,24 +738,6 @@ void MainWindow::showLogoutConfirmation() {
|
||||
callback));
|
||||
}
|
||||
|
||||
void MainWindow::quitFromTray() {
|
||||
App::quit();
|
||||
}
|
||||
|
||||
void MainWindow::activate() {
|
||||
bool wasHidden = !isVisible();
|
||||
setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||
setVisible(true);
|
||||
psActivateProcess();
|
||||
activateWindow();
|
||||
controller().updateIsActiveFocus();
|
||||
if (wasHidden) {
|
||||
if (_main) {
|
||||
_main->windowShown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::takeThirdSectionFromLayer() {
|
||||
return _layer ? _layer->takeToThirdSection() : false;
|
||||
}
|
||||
@@ -749,23 +749,12 @@ void MainWindow::fixOrder() {
|
||||
if (_testingThemeWarning) _testingThemeWarning->raise();
|
||||
}
|
||||
|
||||
void MainWindow::showFromTray(QSystemTrayIcon::ActivationReason reason) {
|
||||
if (reason != QSystemTrayIcon::Context) {
|
||||
base::call_delayed(1, this, [this] {
|
||||
updateTrayMenu();
|
||||
updateGlobalMenu();
|
||||
});
|
||||
activate();
|
||||
updateUnreadCounter();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::handleTrayIconActication(
|
||||
QSystemTrayIcon::ActivationReason reason) {
|
||||
updateIsActive();
|
||||
if (Platform::IsMac() && isActive()) {
|
||||
if (trayIcon && !trayIcon->contextMenu()) {
|
||||
showFromTray(reason);
|
||||
showFromTray();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -775,10 +764,10 @@ void MainWindow::handleTrayIconActication(
|
||||
psShowTrayMenu();
|
||||
});
|
||||
} else if (!skipTrayClick()) {
|
||||
if (Platform::IsWayland() ? isVisible() : isActive()) {
|
||||
if (isActiveForTrayMenu()) {
|
||||
minimizeToTray();
|
||||
} else {
|
||||
showFromTray(reason);
|
||||
showFromTray();
|
||||
}
|
||||
_lastTrayClickTime = crl::now();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_main_window.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/layers/layer_widget.h"
|
||||
@@ -40,8 +39,6 @@ class LayerStackWidget;
|
||||
class MediaPreviewWidget;
|
||||
|
||||
class MainWindow : public Platform::MainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> controller);
|
||||
~MainWindow();
|
||||
@@ -55,11 +52,17 @@ public:
|
||||
void setupIntro(Intro::EnterPoint point);
|
||||
void setupMain();
|
||||
|
||||
void showSettings();
|
||||
void showAddContact();
|
||||
void showNewGroup();
|
||||
void showNewChannel();
|
||||
|
||||
void setInnerFocus();
|
||||
|
||||
MainWidget *sessionContent() const;
|
||||
|
||||
[[nodiscard]] bool doWeMarkAsRead();
|
||||
|
||||
void activate();
|
||||
|
||||
bool takeThirdSectionFromLayer();
|
||||
|
||||
@@ -115,18 +118,6 @@ protected:
|
||||
void updateIsActiveHook() override;
|
||||
void clearWidgetsHook() override;
|
||||
|
||||
public slots:
|
||||
void showSettings();
|
||||
void setInnerFocus();
|
||||
|
||||
void quitFromTray();
|
||||
void showFromTray(QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Unknown);
|
||||
void toggleDisplayNotifyFromTray();
|
||||
|
||||
void onShowAddContact();
|
||||
void onShowNewGroup();
|
||||
void onShowNewChannel();
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool skipTrayClick() const;
|
||||
|
||||
@@ -140,12 +131,15 @@ private:
|
||||
|
||||
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
|
||||
|
||||
void toggleDisplayNotifyFromTray();
|
||||
|
||||
QPixmap grabInner();
|
||||
|
||||
QImage icon16, icon32, icon64, iconbig16, iconbig32, iconbig64;
|
||||
|
||||
crl::time _lastTrayClickTime = 0;
|
||||
QPoint _lastMousePosition;
|
||||
bool _activeForTrayIconAction = true;
|
||||
|
||||
object_ptr<Window::PasscodeLockWidget> _passcodeLock = { nullptr };
|
||||
object_ptr<Intro::Widget> _intro = { nullptr };
|
||||
|
||||
@@ -276,7 +276,16 @@ void StopDetachIfNotUsedSafe() {
|
||||
}
|
||||
|
||||
bool SupportsSpeedControl() {
|
||||
return OpenAL::HasEFXExtension();
|
||||
return OpenAL::HasEFXExtension()
|
||||
&& (alGetEnumValue("AL_AUXILIARY_SEND_FILTER") != 0)
|
||||
&& (alGetEnumValue("AL_DIRECT_FILTER") != 0)
|
||||
&& (alGetEnumValue("AL_EFFECT_TYPE") != 0)
|
||||
&& (alGetEnumValue("AL_EFFECT_PITCH_SHIFTER") != 0)
|
||||
&& (alGetEnumValue("AL_FILTER_TYPE") != 0)
|
||||
&& (alGetEnumValue("AL_FILTER_LOWPASS") != 0)
|
||||
&& (alGetEnumValue("AL_LOWPASS_GAIN") != 0)
|
||||
&& (alGetEnumValue("AL_PITCH_SHIFTER_COARSE_TUNE") != 0)
|
||||
&& (alGetEnumValue("AL_EFFECTSLOT_EFFECT") != 0);
|
||||
}
|
||||
|
||||
} // namespace Audio
|
||||
@@ -587,7 +596,7 @@ Mixer::Mixer(not_null<Audio::Instance*> instance)
|
||||
}, _lifetime);
|
||||
|
||||
connect(this, SIGNAL(loaderOnStart(const AudioMsgId&, qint64)), _loader, SLOT(onStart(const AudioMsgId&, qint64)));
|
||||
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)));
|
||||
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)), Qt::QueuedConnection);
|
||||
connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer()));
|
||||
connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&)));
|
||||
connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&)));
|
||||
|
||||
@@ -148,6 +148,10 @@ Stream File::Context::initStream(
|
||||
|
||||
const auto info = format->streams[index];
|
||||
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (info->disposition & AV_DISPOSITION_ATTACHED_PIC) {
|
||||
// ignore cover streams
|
||||
return Stream();
|
||||
}
|
||||
result.rotation = FFmpeg::ReadRotationFromMetadata(info);
|
||||
result.aspect = FFmpeg::ValidateAspectRatio(info->sample_aspect_ratio);
|
||||
} else if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
@@ -159,10 +163,6 @@ Stream File::Context::initStream(
|
||||
|
||||
result.codec = FFmpeg::MakeCodecPointer(info);
|
||||
if (!result.codec) {
|
||||
if (info->codecpar->codec_id == AV_CODEC_ID_MJPEG) {
|
||||
// mp3 files contain such "video stream", just ignore it.
|
||||
return Stream();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -322,8 +322,7 @@ OverlayWidget::OverlayWidget()
|
||||
, _radial([=](crl::time now) { return radialAnimationCallback(now); })
|
||||
, _lastAction(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction)
|
||||
, _stateAnimation([=](crl::time now) { return stateAnimationCallback(now); })
|
||||
, _dropdown(this, st::mediaviewDropdownMenu)
|
||||
, _dropdownShowTimer(this) {
|
||||
, _dropdown(this, st::mediaviewDropdownMenu) {
|
||||
Lang::Updated(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshLang();
|
||||
@@ -410,23 +409,19 @@ OverlayWidget::OverlayWidget()
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_saveMsgUpdater.setSingleShot(true);
|
||||
connect(&_saveMsgUpdater, SIGNAL(timeout()), this, SLOT(updateImage()));
|
||||
_saveMsgUpdater.setCallback([=] { updateImage(); });
|
||||
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
_touchTimer.setSingleShot(true);
|
||||
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
|
||||
_touchTimer.setCallback([=] { onTouchTimer(); });
|
||||
|
||||
_controlsHideTimer.setSingleShot(true);
|
||||
connect(&_controlsHideTimer, SIGNAL(timeout()), this, SLOT(onHideControls()));
|
||||
_controlsHideTimer.setCallback([=] { onHideControls(); });
|
||||
|
||||
_docDownload->addClickHandler([=] { onDownload(); });
|
||||
_docSaveAs->addClickHandler([=] { onSaveAs(); });
|
||||
_docCancel->addClickHandler([=] { onSaveCancel(); });
|
||||
|
||||
_dropdown->setHiddenCallback([this] { dropdownHidden(); });
|
||||
_dropdownShowTimer->setSingleShot(true);
|
||||
connect(_dropdownShowTimer, SIGNAL(timeout()), this, SLOT(onDropdown()));
|
||||
_dropdownShowTimer.setCallback([=] { onDropdown(); });
|
||||
}
|
||||
|
||||
void OverlayWidget::refreshLang() {
|
||||
@@ -454,7 +449,7 @@ void OverlayWidget::moveToScreen() {
|
||||
}
|
||||
|
||||
void OverlayWidget::updateGeometry() {
|
||||
if (!Platform::IsMac()) {
|
||||
if (Platform::IsLinux()) {
|
||||
return;
|
||||
}
|
||||
const auto screen = windowHandle() && windowHandle()->screen()
|
||||
@@ -483,8 +478,8 @@ void OverlayWidget::updateControlsGeometry() {
|
||||
_saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2);
|
||||
_photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize);
|
||||
|
||||
resizeContentByScreenSize();
|
||||
updateControls();
|
||||
resizeContentByScreenSize();
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -798,33 +793,37 @@ void OverlayWidget::refreshCaptionGeometry() {
|
||||
captionHeight);
|
||||
}
|
||||
|
||||
void OverlayWidget::updateActions() {
|
||||
_actions.clear();
|
||||
|
||||
void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) {
|
||||
if (_document && _document->loading()) {
|
||||
_actions.push_back({ tr::lng_cancel(tr::now), SLOT(onSaveCancel()) });
|
||||
addAction(tr::lng_cancel(tr::now), [=] { onSaveCancel(); });
|
||||
}
|
||||
if (IsServerMsgId(_msgid.msg)) {
|
||||
_actions.push_back({ tr::lng_context_to_msg(tr::now), SLOT(onToMessage()) });
|
||||
addAction(tr::lng_context_to_msg(tr::now), [=] { onToMessage(); });
|
||||
}
|
||||
if (_document && !_document->filepath(true).isEmpty()) {
|
||||
_actions.push_back({ Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), SLOT(onShowInFolder()) });
|
||||
const auto text = Platform::IsMac()
|
||||
? tr::lng_context_show_in_finder(tr::now)
|
||||
: tr::lng_context_show_in_folder(tr::now);
|
||||
addAction(text, [=] { onShowInFolder(); });
|
||||
}
|
||||
if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) {
|
||||
_actions.push_back({ tr::lng_mediaview_copy(tr::now), SLOT(onCopy()) });
|
||||
addAction(tr::lng_mediaview_copy(tr::now), [=] { onCopy(); });
|
||||
}
|
||||
if ((_photo && _photo->hasAttachedStickers())
|
||||
|| (_document && _document->hasAttachedStickers())) {
|
||||
auto member = _photo
|
||||
? SLOT(onPhotoAttachedStickers())
|
||||
: SLOT(onDocumentAttachedStickers());
|
||||
_actions.push_back({
|
||||
auto callback = [=] {
|
||||
if (_photo) {
|
||||
onPhotoAttachedStickers();
|
||||
} else if (_document) {
|
||||
onDocumentAttachedStickers();
|
||||
}
|
||||
};
|
||||
addAction(
|
||||
tr::lng_context_attached_stickers(tr::now),
|
||||
std::move(member)
|
||||
});
|
||||
std::move(callback));
|
||||
}
|
||||
if (_canForwardItem) {
|
||||
_actions.push_back({ tr::lng_mediaview_forward(tr::now), SLOT(onForward()) });
|
||||
addAction(tr::lng_mediaview_forward(tr::now), [=] { onForward(); });
|
||||
}
|
||||
const auto canDelete = [&] {
|
||||
if (_canDeleteItem) {
|
||||
@@ -844,12 +843,15 @@ void OverlayWidget::updateActions() {
|
||||
return false;
|
||||
}();
|
||||
if (canDelete) {
|
||||
_actions.push_back({ tr::lng_mediaview_delete(tr::now), SLOT(onDelete()) });
|
||||
addAction(tr::lng_mediaview_delete(tr::now), [=] { onDelete(); });
|
||||
}
|
||||
_actions.push_back({ tr::lng_mediaview_save_as(tr::now), SLOT(onSaveAs()) });
|
||||
addAction(tr::lng_mediaview_save_as(tr::now), [=] { onSaveAs(); });
|
||||
|
||||
if (const auto overviewType = computeOverviewType()) {
|
||||
_actions.push_back({ _document ? tr::lng_mediaview_files_all(tr::now) : tr::lng_mediaview_photos_all(tr::now), SLOT(onOverview()) });
|
||||
const auto text = _document
|
||||
? tr::lng_mediaview_files_all(tr::now)
|
||||
: tr::lng_mediaview_photos_all(tr::now);
|
||||
addAction(text, [=] { onOverview(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1167,8 +1169,6 @@ void OverlayWidget::clearSession() {
|
||||
_animationOpacities.clear();
|
||||
}
|
||||
clearStreaming();
|
||||
delete _menu;
|
||||
_menu = nullptr;
|
||||
setContext(v::null);
|
||||
_from = nullptr;
|
||||
_fromName = QString();
|
||||
@@ -1240,7 +1240,7 @@ void OverlayWidget::close() {
|
||||
|
||||
void OverlayWidget::activateControls() {
|
||||
if (!_menu && !_mousePressed) {
|
||||
_controlsHideTimer.start(int(st::mediaviewWaitHide));
|
||||
_controlsHideTimer.callOnce(st::mediaviewWaitHide);
|
||||
}
|
||||
if (_fullScreenVideo) {
|
||||
if (_streamed) {
|
||||
@@ -1625,7 +1625,9 @@ void OverlayWidget::onDelete() {
|
||||
}
|
||||
|
||||
void OverlayWidget::onOverview() {
|
||||
if (_menu) _menu->hideMenu(true);
|
||||
if (_menu) {
|
||||
_menu->hideMenu(true);
|
||||
}
|
||||
update();
|
||||
if (const auto overviewType = computeOverviewType()) {
|
||||
close();
|
||||
@@ -3113,7 +3115,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
if (!_blurred) {
|
||||
auto nextFrame = (dt < st::mediaviewSaveMsgShowing || hidingDt >= 0) ? int(AnimationTimerDelta) : (st::mediaviewSaveMsgShowing + st::mediaviewSaveMsgShown + 1 - dt);
|
||||
_saveMsgUpdater.start(nextFrame);
|
||||
_saveMsgUpdater.callOnce(nextFrame);
|
||||
}
|
||||
} else {
|
||||
_saveMsgStarted = 0;
|
||||
@@ -3863,7 +3865,9 @@ void OverlayWidget::preloadData(int delta) {
|
||||
|
||||
void OverlayWidget::mousePressEvent(QMouseEvent *e) {
|
||||
updateOver(e->pos());
|
||||
if (_menu || !_receiveMouse) return;
|
||||
if (_menu || !_receiveMouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClickHandler::pressed();
|
||||
|
||||
@@ -3968,9 +3972,9 @@ bool OverlayWidget::updateOverState(OverState newState) {
|
||||
bool result = true;
|
||||
if (_over != newState) {
|
||||
if (newState == OverMore && !_ignoringDropdown) {
|
||||
_dropdownShowTimer->start(0);
|
||||
_dropdownShowTimer.callOnce(0);
|
||||
} else {
|
||||
_dropdownShowTimer->stop();
|
||||
_dropdownShowTimer.cancel();
|
||||
}
|
||||
updateOverRect(_over);
|
||||
updateOverRect(newState);
|
||||
@@ -4104,7 +4108,7 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
} else if (_over == OverIcon && _down == OverIcon) {
|
||||
onDocClick();
|
||||
} else if (_over == OverMore && _down == OverMore) {
|
||||
QTimer::singleShot(0, this, SLOT(onDropdown()));
|
||||
InvokeQueued(this, [=] { onDropdown(); });
|
||||
} else if (_over == OverClose && _down == OverClose) {
|
||||
close();
|
||||
} else if (_over == OverVideo && _down == OverVideo) {
|
||||
@@ -4143,16 +4147,17 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
|
||||
void OverlayWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||
if (e->reason() != QContextMenuEvent::Mouse || QRect(_x, _y, _w, _h).contains(e->pos())) {
|
||||
if (_menu) {
|
||||
_menu->deleteLater();
|
||||
_menu = nullptr;
|
||||
}
|
||||
_menu = new Ui::PopupMenu(this, st::mediaviewPopupMenu);
|
||||
updateActions();
|
||||
for_const (auto &action, _actions) {
|
||||
_menu->addAction(action.text, this, action.member);
|
||||
}
|
||||
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::mediaviewPopupMenu);
|
||||
fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
|
||||
_menu->addAction(text, std::move(handler));
|
||||
});
|
||||
_menu->setDestroyedCallback(crl::guard(this, [=] {
|
||||
activateControls();
|
||||
_receiveMouse = false;
|
||||
InvokeQueued(this, [=] { receiveMouse(); });
|
||||
}));
|
||||
_menu->popup(e->globalPos());
|
||||
e->accept();
|
||||
activateControls();
|
||||
@@ -4163,7 +4168,7 @@ void OverlayWidget::touchEvent(QTouchEvent *e) {
|
||||
switch (e->type()) {
|
||||
case QEvent::TouchBegin: {
|
||||
if (_touchPress || e->touchPoints().isEmpty()) return;
|
||||
_touchTimer.start(QApplication::startDragTime());
|
||||
_touchTimer.callOnce(QApplication::startDragTime());
|
||||
_touchPress = true;
|
||||
_touchMove = _touchRightButton = false;
|
||||
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
|
||||
@@ -4203,14 +4208,14 @@ void OverlayWidget::touchEvent(QTouchEvent *e) {
|
||||
}
|
||||
}
|
||||
if (weak) {
|
||||
_touchTimer.stop();
|
||||
_touchTimer.cancel();
|
||||
_touchPress = _touchMove = _touchRightButton = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
case QEvent::TouchCancel: {
|
||||
_touchPress = false;
|
||||
_touchTimer.stop();
|
||||
_touchTimer.cancel();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -4314,8 +4319,10 @@ void OverlayWidget::setVisibleHook(bool visible) {
|
||||
assignMediaPointer(nullptr);
|
||||
_preloadPhotos.clear();
|
||||
_preloadDocuments.clear();
|
||||
if (_menu) _menu->hideMenu(true);
|
||||
_controlsHideTimer.stop();
|
||||
if (_menu) {
|
||||
_menu->hideMenu(true);
|
||||
}
|
||||
_controlsHideTimer.cancel();
|
||||
_controlsState = ControlsShown;
|
||||
_controlsOpacity = anim::value(1, 1);
|
||||
_groupThumbs = nullptr;
|
||||
@@ -4338,25 +4345,15 @@ void OverlayWidget::setVisibleHook(bool visible) {
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::onMenuDestroy(QObject *obj) {
|
||||
if (_menu == obj) {
|
||||
_menu = nullptr;
|
||||
activateControls();
|
||||
}
|
||||
_receiveMouse = false;
|
||||
QTimer::singleShot(0, this, SLOT(receiveMouse()));
|
||||
}
|
||||
|
||||
void OverlayWidget::receiveMouse() {
|
||||
_receiveMouse = true;
|
||||
}
|
||||
|
||||
void OverlayWidget::onDropdown() {
|
||||
updateActions();
|
||||
_dropdown->clearActions();
|
||||
for_const (auto &action, _actions) {
|
||||
_dropdown->addAction(action.text, this, action.member);
|
||||
}
|
||||
fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
|
||||
_dropdown->addAction(text, std::move(handler));
|
||||
});
|
||||
_dropdown->moveToRight(0, height() - _dropdown->height());
|
||||
_dropdown->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
|
||||
_dropdown->setFocus();
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/effects/animations.h"
|
||||
@@ -127,7 +128,6 @@ private slots:
|
||||
void onDelete();
|
||||
void onOverview();
|
||||
void onCopy();
|
||||
void onMenuDestroy(QObject *obj);
|
||||
void receiveMouse();
|
||||
void onPhotoAttachedStickers();
|
||||
void onDocumentAttachedStickers();
|
||||
@@ -274,8 +274,11 @@ private:
|
||||
void dropdownHidden();
|
||||
void updateDocSize();
|
||||
void updateControls();
|
||||
void updateActions();
|
||||
void updateControlsGeometry();
|
||||
|
||||
using MenuCallback = Fn<void(const QString &, Fn<void()>)>;
|
||||
void fillContextMenuActions(const MenuCallback &addAction);
|
||||
|
||||
void resizeCenteredControls();
|
||||
void resizeContentByScreenSize();
|
||||
|
||||
@@ -493,26 +496,20 @@ private:
|
||||
};
|
||||
ControlsState _controlsState = ControlsShown;
|
||||
crl::time _controlsAnimStarted = 0;
|
||||
QTimer _controlsHideTimer;
|
||||
base::Timer _controlsHideTimer;
|
||||
anim::value _controlsOpacity;
|
||||
bool _mousePressed = false;
|
||||
|
||||
Ui::PopupMenu *_menu = nullptr;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
object_ptr<Ui::DropdownMenu> _dropdown;
|
||||
object_ptr<QTimer> _dropdownShowTimer;
|
||||
|
||||
struct ActionData {
|
||||
QString text;
|
||||
const char *member;
|
||||
};
|
||||
QList<ActionData> _actions;
|
||||
base::Timer _dropdownShowTimer;
|
||||
|
||||
bool _receiveMouse = true;
|
||||
|
||||
bool _touchPress = false;
|
||||
bool _touchMove = false;
|
||||
bool _touchRightButton = false;
|
||||
QTimer _touchTimer;
|
||||
base::Timer _touchTimer;
|
||||
QPoint _touchStart;
|
||||
QPoint _accumScroll;
|
||||
|
||||
@@ -520,7 +517,7 @@ private:
|
||||
crl::time _saveMsgStarted = 0;
|
||||
anim::value _saveMsgOpacity;
|
||||
QRect _saveMsg;
|
||||
QTimer _saveMsgUpdater;
|
||||
base::Timer _saveMsgUpdater;
|
||||
Ui::Text::String _saveMsgText;
|
||||
SavePhotoVideo _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
@@ -1008,21 +1009,33 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
|
||||
|
||||
auto inner = style::rtlrect(_st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width);
|
||||
if (clip.intersects(inner)) {
|
||||
const auto isLoading = (!cornerDownload
|
||||
&& (_data->loading() || _data->uploading()));
|
||||
p.setPen(Qt::NoPen);
|
||||
if (selected) {
|
||||
p.setBrush(st::msgFileInBgSelected);
|
||||
} else {
|
||||
auto over = ClickHandler::showAsActive((!cornerDownload && (_data->loading() || _data->uploading())) ? _cancell : (loaded || _dataMedia->canBePlayed()) ? _openl : _savel);
|
||||
p.setBrush(anim::brush(_st.songIconBg, _st.songOverBg, _a_iconOver.value(over ? 1. : 0.)));
|
||||
}
|
||||
|
||||
{
|
||||
using namespace HistoryView;
|
||||
const auto coverDrawn = _data->isSongWithCover()
|
||||
&& DrawThumbnailAsSongCover(p, _dataMedia, inner, selected);
|
||||
if (!coverDrawn) {
|
||||
if (selected) {
|
||||
p.setBrush(st::msgFileInBgSelected);
|
||||
} else {
|
||||
const auto over = ClickHandler::showAsActive(isLoading
|
||||
? _cancell
|
||||
: (loaded || _dataMedia->canBePlayed())
|
||||
? _openl
|
||||
: _savel);
|
||||
p.setBrush(anim::brush(
|
||||
_st.songIconBg,
|
||||
_st.songOverBg,
|
||||
_a_iconOver.value(over ? 1. : 0.)));
|
||||
}
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
const auto icon = [&] {
|
||||
if (!cornerDownload && (_data->loading() || _data->uploading())) {
|
||||
if (isLoading) {
|
||||
return &(selected ? _st.songCancelSelected : _st.songCancel);
|
||||
} else if (showPause) {
|
||||
return &(selected ? _st.songPauseSelected : _st.songPause);
|
||||
|
||||
@@ -7,14 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/file_utilities_linux.h"
|
||||
|
||||
#include "platform/linux/linux_libs.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/application.h"
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
@@ -24,150 +18,10 @@ extern "C" {
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
#include <private/qguiapplication_p.h>
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
using Platform::internal::GtkIntegration;
|
||||
|
||||
namespace Platform {
|
||||
namespace File {
|
||||
namespace {
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
bool ShowOpenWithSupported() {
|
||||
return Platform::internal::GdkHelperLoaded()
|
||||
&& (Libs::gtk_app_chooser_dialog_new != nullptr)
|
||||
&& (Libs::gtk_app_chooser_get_app_info != nullptr)
|
||||
&& (Libs::gtk_app_chooser_get_type != nullptr)
|
||||
&& (Libs::gtk_widget_get_window != nullptr)
|
||||
&& (Libs::gtk_widget_realize != nullptr)
|
||||
&& (Libs::gtk_widget_show != nullptr)
|
||||
&& (Libs::gtk_widget_destroy != nullptr);
|
||||
}
|
||||
|
||||
class OpenWithDialog : public QWindow {
|
||||
public:
|
||||
OpenWithDialog(const QString &filepath);
|
||||
~OpenWithDialog();
|
||||
|
||||
bool exec();
|
||||
|
||||
private:
|
||||
static void handleResponse(OpenWithDialog *dialog, int responseId);
|
||||
|
||||
GFile *_gfileInstance = nullptr;
|
||||
GtkWidget *_gtkWidget = nullptr;
|
||||
QEventLoop _loop;
|
||||
std::optional<bool> _result = std::nullopt;
|
||||
};
|
||||
|
||||
OpenWithDialog::OpenWithDialog(const QString &filepath)
|
||||
: _gfileInstance(g_file_new_for_path(filepath.toUtf8()))
|
||||
, _gtkWidget(Libs::gtk_app_chooser_dialog_new(
|
||||
nullptr,
|
||||
GTK_DIALOG_MODAL,
|
||||
_gfileInstance)) {
|
||||
g_signal_connect_swapped(
|
||||
_gtkWidget,
|
||||
"response",
|
||||
G_CALLBACK(handleResponse),
|
||||
this);
|
||||
}
|
||||
|
||||
OpenWithDialog::~OpenWithDialog() {
|
||||
Libs::gtk_widget_destroy(_gtkWidget);
|
||||
g_object_unref(_gfileInstance);
|
||||
}
|
||||
|
||||
bool OpenWithDialog::exec() {
|
||||
Libs::gtk_widget_realize(_gtkWidget);
|
||||
|
||||
if (const auto activeWindow = Core::App().activeWindow()) {
|
||||
Platform::internal::XSetTransientForHint(
|
||||
Libs::gtk_widget_get_window(_gtkWidget),
|
||||
activeWindow->widget().get()->windowHandle()->winId());
|
||||
}
|
||||
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
Libs::gtk_widget_show(_gtkWidget);
|
||||
|
||||
if (!_result.has_value()) {
|
||||
_loop.exec();
|
||||
}
|
||||
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
return *_result;
|
||||
}
|
||||
|
||||
void OpenWithDialog::handleResponse(OpenWithDialog *dialog, int responseId) {
|
||||
GAppInfo *chosenAppInfo = nullptr;
|
||||
dialog->_result = true;
|
||||
|
||||
switch (responseId) {
|
||||
case GTK_RESPONSE_OK:
|
||||
chosenAppInfo = Libs::gtk_app_chooser_get_app_info(
|
||||
Libs::gtk_app_chooser_cast(dialog->_gtkWidget));
|
||||
|
||||
if (chosenAppInfo) {
|
||||
GList *uris = nullptr;
|
||||
uris = g_list_prepend(uris, g_file_get_uri(dialog->_gfileInstance));
|
||||
g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr);
|
||||
g_list_free(uris);
|
||||
g_object_unref(chosenAppInfo);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case GTK_RESPONSE_CANCEL:
|
||||
case GTK_RESPONSE_DELETE_EVENT:
|
||||
break;
|
||||
|
||||
default:
|
||||
dialog->_result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
dialog->_loop.quit();
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
QByteArray EscapeShell(const QByteArray &content) {
|
||||
auto result = QByteArray();
|
||||
|
||||
auto b = content.constData(), e = content.constEnd();
|
||||
for (auto ch = b; ch != e; ++ch) {
|
||||
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
|
||||
if (result.isEmpty()) {
|
||||
result.reserve(content.size() * 2);
|
||||
}
|
||||
if (ch > b) {
|
||||
result.append(b, ch - b);
|
||||
}
|
||||
result.append('\\');
|
||||
b = ch;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (e > b) {
|
||||
result.append(b, e - b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
void UnsafeOpenUrl(const QString &url) {
|
||||
if (!g_app_info_launch_default_for_uri(
|
||||
@@ -183,18 +37,16 @@ void UnsafeOpenEmailLink(const QString &email) {
|
||||
}
|
||||
|
||||
bool UnsafeShowOpenWith(const QString &filepath) {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (InFlatpak()
|
||||
|| InSnap()
|
||||
|| !ShowOpenWithSupported()) {
|
||||
if (InFlatpak() || InSnap()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto absolutePath = QFileInfo(filepath).absoluteFilePath();
|
||||
return OpenWithDialog(absolutePath).exec();
|
||||
#else // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
const auto absolutePath = QFileInfo(filepath).absoluteFilePath();
|
||||
return integration->showOpenWithDialog(absolutePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif // TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
}
|
||||
|
||||
void UnsafeLaunch(const QString &filepath) {
|
||||
@@ -213,136 +65,6 @@ void UnsafeLaunch(const QString &filepath) {
|
||||
} // namespace File
|
||||
|
||||
namespace FileDialog {
|
||||
namespace {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
// GTK file chooser image preview: thanks to Chromium
|
||||
|
||||
// The size of the preview we display for selected image files. We set height
|
||||
// larger than width because generally there is more free space vertically
|
||||
// than horiztonally (setting the preview image will alway expand the width of
|
||||
// the dialog, but usually not the height). The image's aspect ratio will always
|
||||
// be preserved.
|
||||
constexpr auto kPreviewWidth = 256;
|
||||
constexpr auto kPreviewHeight = 512;
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
bool UseNative(Type type = Type::ReadFile) {
|
||||
// use gtk file dialog on gtk-based desktop environments
|
||||
// or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3)
|
||||
// or if portals are used and operation is to open folder
|
||||
// and portal doesn't support folder choosing
|
||||
const auto sandboxedOrCustomPortal = InFlatpak()
|
||||
|| InSnap()
|
||||
|| UseXDGDesktopPortal();
|
||||
|
||||
const auto neededForPortal = (type == Type::ReadFolder)
|
||||
&& !CanOpenDirectoryWithPortal();
|
||||
|
||||
const auto neededNonForced = DesktopEnvironment::IsGtkBased()
|
||||
|| (sandboxedOrCustomPortal && neededForPortal);
|
||||
|
||||
const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal;
|
||||
|
||||
return IsGtkIntegrationForced()
|
||||
|| (neededNonForced && !excludeNonForced);
|
||||
}
|
||||
|
||||
bool NativeSupported() {
|
||||
return Platform::internal::GdkHelperLoaded()
|
||||
&& (Libs::gtk_widget_hide_on_delete != nullptr)
|
||||
&& (Libs::gtk_clipboard_store != nullptr)
|
||||
&& (Libs::gtk_clipboard_get != nullptr)
|
||||
&& (Libs::gtk_widget_destroy != nullptr)
|
||||
&& (Libs::gtk_dialog_get_type != nullptr)
|
||||
&& (Libs::gtk_dialog_run != nullptr)
|
||||
&& (Libs::gtk_widget_realize != nullptr)
|
||||
&& (Libs::gdk_window_set_modal_hint != nullptr)
|
||||
&& (Libs::gtk_widget_show != nullptr)
|
||||
&& (Libs::gdk_window_focus != nullptr)
|
||||
&& (Libs::gtk_widget_hide != nullptr)
|
||||
&& (Libs::gtk_widget_hide_on_delete != nullptr)
|
||||
&& (Libs::gtk_file_chooser_dialog_new != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_type != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_current_folder != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_current_folder != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_current_name != nullptr)
|
||||
&& (Libs::gtk_file_chooser_select_filename != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_filenames != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_filter != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_filter != nullptr)
|
||||
&& (Libs::gtk_window_get_type != nullptr)
|
||||
&& (Libs::gtk_window_set_title != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_local_only != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_action != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_select_multiple != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_do_overwrite_confirmation != nullptr)
|
||||
&& (Libs::gtk_file_chooser_remove_filter != nullptr)
|
||||
&& (Libs::gtk_file_filter_set_name != nullptr)
|
||||
&& (Libs::gtk_file_filter_add_pattern != nullptr)
|
||||
&& (Libs::gtk_file_chooser_add_filter != nullptr)
|
||||
&& (Libs::gtk_file_filter_new != nullptr);
|
||||
}
|
||||
|
||||
bool PreviewSupported() {
|
||||
return NativeSupported()
|
||||
&& (Libs::gdk_pixbuf_new_from_file_at_size != nullptr);
|
||||
}
|
||||
|
||||
bool GetNative(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
internal::GtkFileDialog dialog(parent, caption, QString(), filter);
|
||||
|
||||
dialog.setModal(true);
|
||||
if (type == Type::ReadFile || type == Type::ReadFiles) {
|
||||
dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
} else if (type == Type::ReadFolder) {
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly);
|
||||
} else {
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
}
|
||||
if (startFile.isEmpty() || startFile.at(0) != '/') {
|
||||
startFile = cDialogLastPath() + '/' + startFile;
|
||||
}
|
||||
dialog.selectFile(startFile);
|
||||
|
||||
int res = dialog.exec();
|
||||
|
||||
QString path = dialog.directory().absolutePath();
|
||||
if (path != cDialogLastPath()) {
|
||||
cSetDialogLastPath(path);
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
if (res == QDialog::Accepted) {
|
||||
if (type == Type::ReadFiles) {
|
||||
files = dialog.selectedFiles();
|
||||
} else {
|
||||
files = dialog.selectedFiles().mid(0, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
files = QStringList();
|
||||
remoteContent = QByteArray();
|
||||
return false;
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
@@ -350,23 +72,24 @@ bool Get(
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile) {
|
||||
if (parent) {
|
||||
parent = parent->window();
|
||||
}
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (UseNative(type) && NativeSupported()) {
|
||||
return GetNative(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
if (integration->fileDialogSupported()
|
||||
&& integration->useFileDialog(type)) {
|
||||
return integration->getFileDialog(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
}
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
return ::FileDialog::internal::GetDefault(
|
||||
parent,
|
||||
files,
|
||||
@@ -377,448 +100,5 @@ bool Get(
|
||||
startFile);
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
namespace internal {
|
||||
|
||||
QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) {
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(Libs::gtk_widget_hide_on_delete), nullptr);
|
||||
if (PreviewSupported()) {
|
||||
_preview = Libs::gtk_image_new();
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "update-preview", G_CALLBACK(onUpdatePreview), this);
|
||||
Libs::gtk_file_chooser_set_preview_widget(Libs::gtk_file_chooser_cast(gtkWidget), _preview);
|
||||
}
|
||||
}
|
||||
|
||||
QGtkDialog::~QGtkDialog() {
|
||||
Libs::gtk_clipboard_store(Libs::gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
|
||||
Libs::gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
GtkDialog *QGtkDialog::gtkDialog() const {
|
||||
return Libs::gtk_dialog_cast(gtkWidget);
|
||||
}
|
||||
|
||||
void QGtkDialog::exec() {
|
||||
if (modality() == Qt::ApplicationModal) {
|
||||
// block input to the whole app, including other GTK dialogs
|
||||
Libs::gtk_dialog_run(gtkDialog());
|
||||
} else {
|
||||
// block input to the window, allow input to other GTK dialogs
|
||||
QEventLoop loop;
|
||||
connect(this, SIGNAL(accept()), &loop, SLOT(quit()));
|
||||
connect(this, SIGNAL(reject()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
connect(parent, &QWindow::destroyed, this, &QGtkDialog::onParentWindowDestroyed,
|
||||
Qt::UniqueConnection);
|
||||
setParent(parent);
|
||||
setFlags(flags);
|
||||
setModality(modality);
|
||||
|
||||
Libs::gtk_widget_realize(gtkWidget); // creates X window
|
||||
|
||||
if (parent) {
|
||||
Platform::internal::XSetTransientForHint(Libs::gtk_widget_get_window(gtkWidget), parent->winId());
|
||||
}
|
||||
|
||||
if (modality != Qt::NonModal) {
|
||||
Libs::gdk_window_set_modal_hint(Libs::gtk_widget_get_window(gtkWidget), true);
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
}
|
||||
|
||||
Libs::gtk_widget_show(gtkWidget);
|
||||
Libs::gdk_window_focus(Libs::gtk_widget_get_window(gtkWidget), 0);
|
||||
}
|
||||
|
||||
void QGtkDialog::hide() {
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
Libs::gtk_widget_hide(gtkWidget);
|
||||
}
|
||||
|
||||
void QGtkDialog::onResponse(QGtkDialog *dialog, int response) {
|
||||
if (response == GTK_RESPONSE_OK)
|
||||
emit dialog->accept();
|
||||
else
|
||||
emit dialog->reject();
|
||||
}
|
||||
|
||||
void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) {
|
||||
auto filename = Libs::gtk_file_chooser_get_preview_filename(Libs::gtk_file_chooser_cast(dialog->gtkWidget));
|
||||
if (!filename) {
|
||||
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't attempt to open anything which isn't a regular file. If a named pipe,
|
||||
// this may hang. See https://crbug.com/534754.
|
||||
struct stat stat_buf;
|
||||
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
|
||||
g_free(filename);
|
||||
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will preserve the image's aspect ratio.
|
||||
auto pixbuf = Libs::gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr);
|
||||
g_free(filename);
|
||||
if (pixbuf) {
|
||||
Libs::gtk_image_set_from_pixbuf(Libs::gtk_image_cast(dialog->_preview), pixbuf);
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false);
|
||||
}
|
||||
|
||||
void QGtkDialog::onParentWindowDestroyed() {
|
||||
// The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it.
|
||||
setParent(nullptr);
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
namespace {
|
||||
|
||||
const char *filterRegExp =
|
||||
"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
|
||||
|
||||
QStringList makeFilterList(const QString &filter) {
|
||||
QString f(filter);
|
||||
|
||||
if (f.isEmpty())
|
||||
return QStringList();
|
||||
|
||||
QString sep(QLatin1String(";;"));
|
||||
int i = f.indexOf(sep, 0);
|
||||
if (i == -1) {
|
||||
if (f.indexOf(QLatin1Char('\n'), 0) != -1) {
|
||||
sep = QLatin1Char('\n');
|
||||
i = f.indexOf(sep, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return f.split(sep);
|
||||
}
|
||||
|
||||
// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)"
|
||||
QStringList cleanFilterList(const QString &filter) {
|
||||
QRegExp regexp(QString::fromLatin1(filterRegExp));
|
||||
Q_ASSERT(regexp.isValid());
|
||||
QString f = filter;
|
||||
int i = regexp.indexIn(f);
|
||||
if (i >= 0)
|
||||
f = regexp.cap(2);
|
||||
return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent)
|
||||
, _windowTitle(caption)
|
||||
, _initialDirectory(directory) {
|
||||
auto filters = makeFilterList(filter);
|
||||
const int numFilters = filters.count();
|
||||
_nameFilters.reserve(numFilters);
|
||||
for (int i = 0; i < numFilters; ++i) {
|
||||
_nameFilters << filters[i].simplified();
|
||||
}
|
||||
|
||||
d.reset(new QGtkDialog(Libs::gtk_file_chooser_dialog_new("", nullptr,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
// https://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html#gtk-file-chooser-dialog-new
|
||||
// first_button_text doesn't need explicit conversion to char*, while all others are vardict
|
||||
tr::lng_cancel(tr::now).toUtf8(), GTK_RESPONSE_CANCEL,
|
||||
tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr)));
|
||||
connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted()));
|
||||
connect(d.data(), SIGNAL(reject()), this, SLOT(onRejected()));
|
||||
|
||||
g_signal_connect(Libs::gtk_file_chooser_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this);
|
||||
g_signal_connect_swapped(Libs::gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this);
|
||||
}
|
||||
|
||||
GtkFileDialog::~GtkFileDialog() {
|
||||
}
|
||||
|
||||
void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
_dir.clear();
|
||||
_selection.clear();
|
||||
|
||||
applyOptions();
|
||||
return d->show(flags, modality, parent);
|
||||
}
|
||||
|
||||
void GtkFileDialog::setVisible(bool visible) {
|
||||
if (visible) {
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr);
|
||||
} else {
|
||||
hideHelper();
|
||||
}
|
||||
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen);
|
||||
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
int GtkFileDialog::exec() {
|
||||
d->setModality(windowModality());
|
||||
|
||||
bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
bool wasShowModal = testAttribute(Qt::WA_ShowModal);
|
||||
setAttribute(Qt::WA_ShowModal, true);
|
||||
setResult(0);
|
||||
|
||||
show();
|
||||
|
||||
QPointer<QDialog> guard = this;
|
||||
d->exec();
|
||||
if (guard.isNull())
|
||||
return QDialog::Rejected;
|
||||
|
||||
setAttribute(Qt::WA_ShowModal, wasShowModal);
|
||||
|
||||
return result();
|
||||
}
|
||||
|
||||
void GtkFileDialog::hideHelper() {
|
||||
// After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder()
|
||||
// & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual
|
||||
// values before hiding the dialog
|
||||
_dir = directory().absolutePath();
|
||||
_selection = selectedFiles();
|
||||
|
||||
d->hide();
|
||||
}
|
||||
|
||||
bool GtkFileDialog::defaultNameFilterDisables() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setDirectory(const QString &directory) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), directory.toUtf8());
|
||||
}
|
||||
|
||||
QDir GtkFileDialog::directory() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_dir.isEmpty())
|
||||
return _dir;
|
||||
|
||||
QString ret;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gchar *folder = Libs::gtk_file_chooser_get_current_folder(Libs::gtk_file_chooser_cast(gtkDialog));
|
||||
if (folder) {
|
||||
ret = QString::fromUtf8(folder);
|
||||
g_free(folder);
|
||||
}
|
||||
return QDir(ret);
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectFile(const QString &filename) {
|
||||
_initialFiles.clear();
|
||||
_initialFiles.append(filename);
|
||||
}
|
||||
|
||||
QStringList GtkFileDialog::selectedFiles() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_selection.isEmpty())
|
||||
return _selection;
|
||||
|
||||
QStringList selection;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GSList *filenames = Libs::gtk_file_chooser_get_filenames(Libs::gtk_file_chooser_cast(gtkDialog));
|
||||
for (GSList *it = filenames; it; it = it->next)
|
||||
selection += QString::fromUtf8((const char*)it->data);
|
||||
g_slist_free(filenames);
|
||||
return selection;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setFilter() {
|
||||
applyOptions();
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectNameFilter(const QString &filter) {
|
||||
GtkFileFilter *gtkFilter = _filters.value(filter);
|
||||
if (gtkFilter) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
Libs::gtk_file_chooser_set_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter);
|
||||
}
|
||||
}
|
||||
|
||||
QString GtkFileDialog::selectedNameFilter() const {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GtkFileFilter *gtkFilter = Libs::gtk_file_chooser_get_filter(Libs::gtk_file_chooser_cast(gtkDialog));
|
||||
return _filterNames.value(gtkFilter);
|
||||
}
|
||||
|
||||
void GtkFileDialog::onAccepted() {
|
||||
emit accept();
|
||||
|
||||
// QString filter = selectedNameFilter();
|
||||
// if (filter.isEmpty())
|
||||
// emit filterSelected(filter);
|
||||
|
||||
// QList<QUrl> files = selectedFiles();
|
||||
// emit filesSelected(files);
|
||||
// if (files.count() == 1)
|
||||
// emit fileSelected(files.first());
|
||||
}
|
||||
|
||||
void GtkFileDialog::onRejected() {
|
||||
emit reject();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) {
|
||||
// QString selection;
|
||||
// gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog));
|
||||
// if (filename) {
|
||||
// selection = QString::fromUtf8(filename);
|
||||
// g_free(filename);
|
||||
// }
|
||||
// emit helper->currentChanged(QUrl::fromLocalFile(selection));
|
||||
}
|
||||
|
||||
void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) {
|
||||
// emit dialog->directoryEntered(dialog->directory());
|
||||
}
|
||||
|
||||
GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) {
|
||||
switch (fileMode) {
|
||||
case QFileDialog::AnyFile:
|
||||
case QFileDialog::ExistingFile:
|
||||
case QFileDialog::ExistingFiles:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
case QFileDialog::Directory:
|
||||
default:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
bool CustomButtonsSupported() {
|
||||
return (Libs::gtk_dialog_get_widget_for_response != nullptr)
|
||||
&& (Libs::gtk_button_set_label != nullptr)
|
||||
&& (Libs::gtk_button_get_type != nullptr);
|
||||
}
|
||||
|
||||
void GtkFileDialog::applyOptions() {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
|
||||
Libs::gtk_window_set_title(Libs::gtk_window_cast(gtkDialog), _windowTitle.toUtf8());
|
||||
Libs::gtk_file_chooser_set_local_only(Libs::gtk_file_chooser_cast(gtkDialog), true);
|
||||
|
||||
const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode);
|
||||
Libs::gtk_file_chooser_set_action(Libs::gtk_file_chooser_cast(gtkDialog), action);
|
||||
|
||||
const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles);
|
||||
Libs::gtk_file_chooser_set_select_multiple(Libs::gtk_file_chooser_cast(gtkDialog), selectMultiple);
|
||||
|
||||
const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite);
|
||||
Libs::gtk_file_chooser_set_do_overwrite_confirmation(Libs::gtk_file_chooser_cast(gtkDialog), confirmOverwrite);
|
||||
|
||||
if (!_nameFilters.isEmpty())
|
||||
setNameFilters(_nameFilters);
|
||||
|
||||
if (!_initialDirectory.isEmpty())
|
||||
setDirectory(_initialDirectory);
|
||||
|
||||
for_const (const auto &filename, _initialFiles) {
|
||||
if (_acceptMode == QFileDialog::AcceptSave) {
|
||||
QFileInfo fi(filename);
|
||||
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), fi.path().toUtf8());
|
||||
Libs::gtk_file_chooser_set_current_name(Libs::gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8());
|
||||
} else if (filename.endsWith('/')) {
|
||||
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
|
||||
} else {
|
||||
Libs::gtk_file_chooser_select_filename(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front();
|
||||
if (!initialNameFilter.isEmpty())
|
||||
selectNameFilter(initialNameFilter);
|
||||
|
||||
if (CustomButtonsSupported()) {
|
||||
GtkWidget *acceptButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK);
|
||||
if (acceptButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8());
|
||||
else*/ if (_acceptMode == QFileDialog::AcceptOpen)
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8());
|
||||
else
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_settings_save(tr::now).toUtf8());
|
||||
}
|
||||
|
||||
GtkWidget *rejectButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL);
|
||||
if (rejectButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8());
|
||||
else*/
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), tr::lng_cancel(tr::now).toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GtkFileDialog::setNameFilters(const QStringList &filters) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
foreach (GtkFileFilter *filter, _filters)
|
||||
Libs::gtk_file_chooser_remove_filter(Libs::gtk_file_chooser_cast(gtkDialog), filter);
|
||||
|
||||
_filters.clear();
|
||||
_filterNames.clear();
|
||||
|
||||
for_const (auto &filter, filters) {
|
||||
GtkFileFilter *gtkFilter = Libs::gtk_file_filter_new();
|
||||
auto name = filter;//.left(filter.indexOf(QLatin1Char('(')));
|
||||
auto extensions = cleanFilterList(filter);
|
||||
|
||||
Libs::gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8());
|
||||
for_const (auto &ext, extensions) {
|
||||
auto caseInsensitiveExt = QString();
|
||||
caseInsensitiveExt.reserve(4 * ext.size());
|
||||
for_const (auto ch, ext) {
|
||||
auto chLower = ch.toLower();
|
||||
auto chUpper = ch.toUpper();
|
||||
if (chLower != chUpper) {
|
||||
caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']');
|
||||
} else {
|
||||
caseInsensitiveExt.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
Libs::gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8());
|
||||
}
|
||||
|
||||
Libs::gtk_file_chooser_add_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter);
|
||||
|
||||
_filters.insert(filter, gtkFilter);
|
||||
_filterNames.insert(gtkFilter, filter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
|
||||
@@ -9,22 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/platform_file_utilities.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
typedef struct _GtkWidget GtkWidget;
|
||||
typedef struct _GtkDialog GtkDialog;
|
||||
typedef struct _GtkFileFilter GtkFileFilter;
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
namespace Platform {
|
||||
namespace File {
|
||||
namespace internal {
|
||||
|
||||
QByteArray EscapeShell(const QByteArray &content);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
inline QString UrlToLocal(const QUrl &url) {
|
||||
return ::File::internal::UrlToLocalDefault(url);
|
||||
@@ -45,114 +31,5 @@ inline void InitLastPath() {
|
||||
::FileDialog::internal::InitLastPathDefault();
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
// This is a patched copy of qgtk2 theme plugin.
|
||||
// We need to use our own gtk file dialog instead of
|
||||
// styling Qt file dialog, because Qt only works with gtk2.
|
||||
// We need to be able to work with gtk2 and gtk3, because
|
||||
// we use gtk3 to work with appindicator3.
|
||||
class QGtkDialog : public QWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QGtkDialog(GtkWidget *gtkWidget);
|
||||
~QGtkDialog();
|
||||
|
||||
GtkDialog *gtkDialog() const;
|
||||
|
||||
void exec();
|
||||
void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hide();
|
||||
|
||||
signals:
|
||||
void accept();
|
||||
void reject();
|
||||
|
||||
protected:
|
||||
static void onResponse(QGtkDialog *dialog, int response);
|
||||
static void onUpdatePreview(QGtkDialog *dialog);
|
||||
|
||||
private slots:
|
||||
void onParentWindowDestroyed();
|
||||
|
||||
private:
|
||||
GtkWidget *gtkWidget;
|
||||
GtkWidget *_preview = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class GtkFileDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GtkFileDialog(QWidget *parent = Q_NULLPTR,
|
||||
const QString &caption = QString(),
|
||||
const QString &directory = QString(),
|
||||
const QString &filter = QString());
|
||||
~GtkFileDialog();
|
||||
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
void setWindowTitle(const QString &windowTitle) {
|
||||
_windowTitle = windowTitle;
|
||||
}
|
||||
void setAcceptMode(QFileDialog::AcceptMode acceptMode) {
|
||||
_acceptMode = acceptMode;
|
||||
}
|
||||
void setFileMode(QFileDialog::FileMode fileMode) {
|
||||
_fileMode = fileMode;
|
||||
}
|
||||
void setOption(QFileDialog::Option option, bool on = true) {
|
||||
if (on) {
|
||||
_options |= option;
|
||||
} else {
|
||||
_options &= ~option;
|
||||
}
|
||||
}
|
||||
|
||||
int exec() override;
|
||||
|
||||
bool defaultNameFilterDisables() const;
|
||||
void setDirectory(const QString &directory);
|
||||
QDir directory() const;
|
||||
void selectFile(const QString &filename);
|
||||
QStringList selectedFiles() const;
|
||||
void setFilter();
|
||||
void selectNameFilter(const QString &filter);
|
||||
QString selectedNameFilter() const;
|
||||
|
||||
private slots:
|
||||
void onAccepted();
|
||||
void onRejected();
|
||||
|
||||
private:
|
||||
static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper);
|
||||
static void onCurrentFolderChanged(GtkFileDialog *helper);
|
||||
void applyOptions();
|
||||
void setNameFilters(const QStringList &filters);
|
||||
|
||||
void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hideHelper();
|
||||
|
||||
// Options
|
||||
QFileDialog::Options _options;
|
||||
QString _windowTitle = "Choose file";
|
||||
QString _initialDirectory;
|
||||
QStringList _initialFiles;
|
||||
QStringList _nameFilters;
|
||||
QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen;
|
||||
QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile;
|
||||
|
||||
QString _dir;
|
||||
QStringList _selection;
|
||||
QHash<QString, GtkFileFilter*> _filters;
|
||||
QHash<GtkFileFilter*, QString> _filterNames;
|
||||
QScopedPointer<QGtkDialog> d;
|
||||
};
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace internal
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "base/qt_adapters.h"
|
||||
|
||||
namespace Platform {
|
||||
|
||||
@@ -5,10 +5,9 @@ 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
|
||||
*/
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
|
||||
#include "platform/linux/linux_libs.h"
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
@@ -19,6 +18,8 @@ extern "C" {
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
enum class GtkLoaded {
|
||||
GtkNone,
|
||||
Gtk2,
|
||||
@@ -44,7 +45,7 @@ f_gdk_x11_window_get_type gdk_x11_window_get_type = nullptr;
|
||||
// To be able to compile with gtk-2.0 headers as well
|
||||
template <typename Object>
|
||||
inline bool gdk_is_x11_window_check(Object *obj) {
|
||||
return Libs::g_type_cit_helper(obj, gdk_x11_window_get_type());
|
||||
return g_type_cit_helper(obj, gdk_x11_window_get_type());
|
||||
}
|
||||
|
||||
using f_gdk_window_get_display = GdkDisplay*(*)(GdkWindow *window);
|
||||
@@ -57,20 +58,20 @@ using f_gdk_x11_window_get_xid = Window(*)(GdkWindow *window);
|
||||
f_gdk_x11_window_get_xid gdk_x11_window_get_xid = nullptr;
|
||||
|
||||
bool GdkHelperLoadGtk2(QLibrary &lib) {
|
||||
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
#ifdef LINK_TO_GTK
|
||||
return false;
|
||||
#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false;
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false;
|
||||
#else // LINK_TO_GTK
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false;
|
||||
return true;
|
||||
#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
#endif // !LINK_TO_GTK
|
||||
}
|
||||
|
||||
bool GdkHelperLoadGtk3(QLibrary &lib) {
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false;
|
||||
if (!LOAD_SYMBOL(lib, "gdk_window_get_display", gdk_window_get_display)) return false;
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false;
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_window_get_display", gdk_window_get_display)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -103,4 +104,3 @@ void XSetTransientForHint(GdkWindow *window, quintptr winId) {
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
@@ -7,11 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class QLibrary;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gtk/gtk.h>
|
||||
@@ -28,4 +25,3 @@ void XSetTransientForHint(GdkWindow *window, quintptr winId);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace Platform {
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDBusTimeout = 30000;
|
||||
constexpr auto kService = "org.gnome.SettingsDaemon.MediaKeys"_cs;
|
||||
constexpr auto kOldService = "org.gnome.SettingsDaemon"_cs;
|
||||
constexpr auto kMATEService = "org.mate.SettingsDaemon"_cs;
|
||||
@@ -114,7 +113,7 @@ GSDMediaKeys::GSDMediaKeys() {
|
||||
0),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
kDBusTimeout,
|
||||
-1,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
@@ -160,7 +159,7 @@ GSDMediaKeys::~GSDMediaKeys() {
|
||||
QCoreApplication::applicationName().toUtf8().constData()),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
kDBusTimeout,
|
||||
-1,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
|
||||
714
Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp
Normal file
714
Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp
Normal file
@@ -0,0 +1,714 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_gtk_file_dialog.h"
|
||||
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/qt_adapters.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <private/qguiapplication_p.h>
|
||||
|
||||
namespace Platform {
|
||||
namespace FileDialog {
|
||||
namespace Gtk {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
namespace {
|
||||
|
||||
// GTK file chooser image preview: thanks to Chromium
|
||||
|
||||
// The size of the preview we display for selected image files. We set height
|
||||
// larger than width because generally there is more free space vertically
|
||||
// than horiztonally (setting the preview image will alway expand the width of
|
||||
// the dialog, but usually not the height). The image's aspect ratio will always
|
||||
// be preserved.
|
||||
constexpr auto kPreviewWidth = 256;
|
||||
constexpr auto kPreviewHeight = 512;
|
||||
|
||||
const char *filterRegExp =
|
||||
"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
|
||||
|
||||
QStringList makeFilterList(const QString &filter) {
|
||||
QString f(filter);
|
||||
|
||||
if (f.isEmpty())
|
||||
return QStringList();
|
||||
|
||||
QString sep(QLatin1String(";;"));
|
||||
int i = f.indexOf(sep, 0);
|
||||
if (i == -1) {
|
||||
if (f.indexOf(QLatin1Char('\n'), 0) != -1) {
|
||||
sep = QLatin1Char('\n');
|
||||
i = f.indexOf(sep, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return f.split(sep);
|
||||
}
|
||||
|
||||
// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)"
|
||||
QStringList cleanFilterList(const QString &filter) {
|
||||
QRegExp regexp(QString::fromLatin1(filterRegExp));
|
||||
Assert(regexp.isValid());
|
||||
QString f = filter;
|
||||
int i = regexp.indexIn(f);
|
||||
if (i >= 0)
|
||||
f = regexp.cap(2);
|
||||
return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts);
|
||||
}
|
||||
|
||||
bool PreviewSupported() {
|
||||
return (gdk_pixbuf_new_from_file_at_size != nullptr);
|
||||
}
|
||||
|
||||
bool CustomButtonsSupported() {
|
||||
return (gtk_dialog_get_widget_for_response != nullptr)
|
||||
&& (gtk_button_set_label != nullptr)
|
||||
&& (gtk_button_get_type != nullptr);
|
||||
}
|
||||
|
||||
// This is a patched copy of qgtk2 theme plugin.
|
||||
// We need to use our own gtk file dialog instead of
|
||||
// styling Qt file dialog, because Qt only works with gtk2.
|
||||
// We need to be able to work with gtk2 and gtk3, because
|
||||
// we use gtk3 to work with appindicator3.
|
||||
class QGtkDialog : public QWindow {
|
||||
public:
|
||||
QGtkDialog(GtkWidget *gtkWidget);
|
||||
~QGtkDialog();
|
||||
|
||||
GtkDialog *gtkDialog() const;
|
||||
|
||||
void exec();
|
||||
void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hide();
|
||||
|
||||
rpl::producer<> accept();
|
||||
rpl::producer<> reject();
|
||||
|
||||
protected:
|
||||
static void onResponse(QGtkDialog *dialog, int response);
|
||||
static void onUpdatePreview(QGtkDialog *dialog);
|
||||
|
||||
private:
|
||||
void onParentWindowDestroyed();
|
||||
|
||||
GtkWidget *gtkWidget = nullptr;
|
||||
GtkWidget *_preview = nullptr;
|
||||
|
||||
rpl::event_stream<> _accept;
|
||||
rpl::event_stream<> _reject;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class GtkFileDialog : public QDialog {
|
||||
public:
|
||||
GtkFileDialog(QWidget *parent = nullptr,
|
||||
const QString &caption = QString(),
|
||||
const QString &directory = QString(),
|
||||
const QString &filter = QString());
|
||||
~GtkFileDialog();
|
||||
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
void setWindowTitle(const QString &windowTitle) {
|
||||
_windowTitle = windowTitle;
|
||||
}
|
||||
void setAcceptMode(QFileDialog::AcceptMode acceptMode) {
|
||||
_acceptMode = acceptMode;
|
||||
}
|
||||
void setFileMode(QFileDialog::FileMode fileMode) {
|
||||
_fileMode = fileMode;
|
||||
}
|
||||
void setOption(QFileDialog::Option option, bool on = true) {
|
||||
if (on) {
|
||||
_options |= option;
|
||||
} else {
|
||||
_options &= ~option;
|
||||
}
|
||||
}
|
||||
|
||||
int exec() override;
|
||||
|
||||
bool defaultNameFilterDisables() const;
|
||||
void setDirectory(const QString &directory);
|
||||
QDir directory() const;
|
||||
void selectFile(const QString &filename);
|
||||
QStringList selectedFiles() const;
|
||||
void setFilter();
|
||||
void selectNameFilter(const QString &filter);
|
||||
QString selectedNameFilter() const;
|
||||
|
||||
private:
|
||||
static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper);
|
||||
static void onCurrentFolderChanged(GtkFileDialog *helper);
|
||||
void applyOptions();
|
||||
void setNameFilters(const QStringList &filters);
|
||||
|
||||
void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hideHelper();
|
||||
|
||||
void onAccepted();
|
||||
void onRejected();
|
||||
|
||||
// Options
|
||||
QFileDialog::Options _options;
|
||||
QString _windowTitle = "Choose file";
|
||||
QString _initialDirectory;
|
||||
QStringList _initialFiles;
|
||||
QStringList _nameFilters;
|
||||
QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen;
|
||||
QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile;
|
||||
|
||||
QString _dir;
|
||||
QStringList _selection;
|
||||
QHash<QString, GtkFileFilter*> _filters;
|
||||
QHash<GtkFileFilter*, QString> _filterNames;
|
||||
QScopedPointer<QGtkDialog> d;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
};
|
||||
|
||||
QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) {
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
|
||||
if (PreviewSupported()) {
|
||||
_preview = gtk_image_new();
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "update-preview", G_CALLBACK(onUpdatePreview), this);
|
||||
gtk_file_chooser_set_preview_widget(gtk_file_chooser_cast(gtkWidget), _preview);
|
||||
}
|
||||
}
|
||||
|
||||
QGtkDialog::~QGtkDialog() {
|
||||
gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
GtkDialog *QGtkDialog::gtkDialog() const {
|
||||
return gtk_dialog_cast(gtkWidget);
|
||||
}
|
||||
|
||||
void QGtkDialog::exec() {
|
||||
if (modality() == Qt::ApplicationModal) {
|
||||
// block input to the whole app, including other GTK dialogs
|
||||
gtk_dialog_run(gtkDialog());
|
||||
} else {
|
||||
// block input to the window, allow input to other GTK dialogs
|
||||
QEventLoop loop;
|
||||
|
||||
accept(
|
||||
) | rpl::start_with_next([=, &loop] {
|
||||
loop.quit();
|
||||
}, _lifetime);
|
||||
|
||||
reject(
|
||||
) | rpl::start_with_next([=, &loop] {
|
||||
loop.quit();
|
||||
}, _lifetime);
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
connect(parent, &QWindow::destroyed, this, [=] { onParentWindowDestroyed(); },
|
||||
Qt::UniqueConnection);
|
||||
setParent(parent);
|
||||
setFlags(flags);
|
||||
setModality(modality);
|
||||
|
||||
gtk_widget_realize(gtkWidget); // creates X window
|
||||
|
||||
if (parent) {
|
||||
internal::XSetTransientForHint(gtk_widget_get_window(gtkWidget), parent->winId());
|
||||
}
|
||||
|
||||
if (modality != Qt::NonModal) {
|
||||
gdk_window_set_modal_hint(gtk_widget_get_window(gtkWidget), true);
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
}
|
||||
|
||||
gtk_widget_show(gtkWidget);
|
||||
gdk_window_focus(gtk_widget_get_window(gtkWidget), 0);
|
||||
}
|
||||
|
||||
void QGtkDialog::hide() {
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
gtk_widget_hide(gtkWidget);
|
||||
}
|
||||
|
||||
rpl::producer<> QGtkDialog::accept() {
|
||||
return _accept.events();
|
||||
}
|
||||
|
||||
rpl::producer<> QGtkDialog::reject() {
|
||||
return _reject.events();
|
||||
}
|
||||
|
||||
void QGtkDialog::onResponse(QGtkDialog *dialog, int response) {
|
||||
if (response == GTK_RESPONSE_OK)
|
||||
dialog->_accept.fire({});
|
||||
else
|
||||
dialog->_reject.fire({});
|
||||
}
|
||||
|
||||
void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) {
|
||||
auto filename = gtk_file_chooser_get_preview_filename(gtk_file_chooser_cast(dialog->gtkWidget));
|
||||
if (!filename) {
|
||||
gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't attempt to open anything which isn't a regular file. If a named pipe,
|
||||
// this may hang. See https://crbug.com/534754.
|
||||
struct stat stat_buf;
|
||||
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
|
||||
g_free(filename);
|
||||
gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will preserve the image's aspect ratio.
|
||||
auto pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr);
|
||||
g_free(filename);
|
||||
if (pixbuf) {
|
||||
gtk_image_set_from_pixbuf(gtk_image_cast(dialog->_preview), pixbuf);
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false);
|
||||
}
|
||||
|
||||
void QGtkDialog::onParentWindowDestroyed() {
|
||||
// The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it.
|
||||
setParent(nullptr);
|
||||
}
|
||||
|
||||
GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent)
|
||||
, _windowTitle(caption)
|
||||
, _initialDirectory(directory) {
|
||||
auto filters = makeFilterList(filter);
|
||||
const int numFilters = filters.count();
|
||||
_nameFilters.reserve(numFilters);
|
||||
for (int i = 0; i < numFilters; ++i) {
|
||||
_nameFilters << filters[i].simplified();
|
||||
}
|
||||
|
||||
d.reset(new QGtkDialog(gtk_file_chooser_dialog_new("", nullptr,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
// https://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html#gtk-file-chooser-dialog-new
|
||||
// first_button_text doesn't need explicit conversion to char*, while all others are vardict
|
||||
tr::lng_cancel(tr::now).toUtf8(), GTK_RESPONSE_CANCEL,
|
||||
tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr)));
|
||||
|
||||
d.data()->accept(
|
||||
) | rpl::start_with_next([=] {
|
||||
onAccepted();
|
||||
}, _lifetime);
|
||||
|
||||
d.data()->reject(
|
||||
) | rpl::start_with_next([=] {
|
||||
onRejected();
|
||||
}, _lifetime);
|
||||
|
||||
g_signal_connect(gtk_file_chooser_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this);
|
||||
g_signal_connect_swapped(gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this);
|
||||
}
|
||||
|
||||
GtkFileDialog::~GtkFileDialog() {
|
||||
}
|
||||
|
||||
void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
_dir.clear();
|
||||
_selection.clear();
|
||||
|
||||
applyOptions();
|
||||
return d->show(flags, modality, parent);
|
||||
}
|
||||
|
||||
void GtkFileDialog::setVisible(bool visible) {
|
||||
if (visible) {
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr);
|
||||
} else {
|
||||
hideHelper();
|
||||
}
|
||||
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen);
|
||||
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
int GtkFileDialog::exec() {
|
||||
d->setModality(windowModality());
|
||||
|
||||
bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
bool wasShowModal = testAttribute(Qt::WA_ShowModal);
|
||||
setAttribute(Qt::WA_ShowModal, true);
|
||||
setResult(0);
|
||||
|
||||
show();
|
||||
|
||||
QPointer<QDialog> guard = this;
|
||||
d->exec();
|
||||
if (guard.isNull())
|
||||
return QDialog::Rejected;
|
||||
|
||||
setAttribute(Qt::WA_ShowModal, wasShowModal);
|
||||
|
||||
return result();
|
||||
}
|
||||
|
||||
void GtkFileDialog::hideHelper() {
|
||||
// After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder()
|
||||
// & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual
|
||||
// values before hiding the dialog
|
||||
_dir = directory().absolutePath();
|
||||
_selection = selectedFiles();
|
||||
|
||||
d->hide();
|
||||
}
|
||||
|
||||
bool GtkFileDialog::defaultNameFilterDisables() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setDirectory(const QString &directory) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gtk_file_chooser_set_current_folder(gtk_file_chooser_cast(gtkDialog), directory.toUtf8());
|
||||
}
|
||||
|
||||
QDir GtkFileDialog::directory() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_dir.isEmpty())
|
||||
return _dir;
|
||||
|
||||
QString ret;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gchar *folder = gtk_file_chooser_get_current_folder(gtk_file_chooser_cast(gtkDialog));
|
||||
if (folder) {
|
||||
ret = QString::fromUtf8(folder);
|
||||
g_free(folder);
|
||||
}
|
||||
return QDir(ret);
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectFile(const QString &filename) {
|
||||
_initialFiles.clear();
|
||||
_initialFiles.append(filename);
|
||||
}
|
||||
|
||||
QStringList GtkFileDialog::selectedFiles() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_selection.isEmpty())
|
||||
return _selection;
|
||||
|
||||
QStringList selection;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GSList *filenames = gtk_file_chooser_get_filenames(gtk_file_chooser_cast(gtkDialog));
|
||||
for (GSList *it = filenames; it; it = it->next)
|
||||
selection += QString::fromUtf8((const char*)it->data);
|
||||
g_slist_free(filenames);
|
||||
return selection;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setFilter() {
|
||||
applyOptions();
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectNameFilter(const QString &filter) {
|
||||
GtkFileFilter *gtkFilter = _filters.value(filter);
|
||||
if (gtkFilter) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gtk_file_chooser_set_filter(gtk_file_chooser_cast(gtkDialog), gtkFilter);
|
||||
}
|
||||
}
|
||||
|
||||
QString GtkFileDialog::selectedNameFilter() const {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GtkFileFilter *gtkFilter = gtk_file_chooser_get_filter(gtk_file_chooser_cast(gtkDialog));
|
||||
return _filterNames.value(gtkFilter);
|
||||
}
|
||||
|
||||
void GtkFileDialog::onAccepted() {
|
||||
emit accept();
|
||||
|
||||
// QString filter = selectedNameFilter();
|
||||
// if (filter.isEmpty())
|
||||
// emit filterSelected(filter);
|
||||
|
||||
// QList<QUrl> files = selectedFiles();
|
||||
// emit filesSelected(files);
|
||||
// if (files.count() == 1)
|
||||
// emit fileSelected(files.first());
|
||||
}
|
||||
|
||||
void GtkFileDialog::onRejected() {
|
||||
emit reject();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) {
|
||||
// QString selection;
|
||||
// gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog));
|
||||
// if (filename) {
|
||||
// selection = QString::fromUtf8(filename);
|
||||
// g_free(filename);
|
||||
// }
|
||||
// emit helper->currentChanged(QUrl::fromLocalFile(selection));
|
||||
}
|
||||
|
||||
void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) {
|
||||
// emit dialog->directoryEntered(dialog->directory());
|
||||
}
|
||||
|
||||
GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) {
|
||||
switch (fileMode) {
|
||||
case QFileDialog::AnyFile:
|
||||
case QFileDialog::ExistingFile:
|
||||
case QFileDialog::ExistingFiles:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
case QFileDialog::Directory:
|
||||
default:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
void GtkFileDialog::applyOptions() {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
|
||||
gtk_window_set_title(gtk_window_cast(gtkDialog), _windowTitle.toUtf8());
|
||||
gtk_file_chooser_set_local_only(gtk_file_chooser_cast(gtkDialog), true);
|
||||
|
||||
const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode);
|
||||
gtk_file_chooser_set_action(gtk_file_chooser_cast(gtkDialog), action);
|
||||
|
||||
const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles);
|
||||
gtk_file_chooser_set_select_multiple(gtk_file_chooser_cast(gtkDialog), selectMultiple);
|
||||
|
||||
const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(gtk_file_chooser_cast(gtkDialog), confirmOverwrite);
|
||||
|
||||
if (!_nameFilters.isEmpty())
|
||||
setNameFilters(_nameFilters);
|
||||
|
||||
if (!_initialDirectory.isEmpty())
|
||||
setDirectory(_initialDirectory);
|
||||
|
||||
for_const (const auto &filename, _initialFiles) {
|
||||
if (_acceptMode == QFileDialog::AcceptSave) {
|
||||
QFileInfo fi(filename);
|
||||
gtk_file_chooser_set_current_folder(gtk_file_chooser_cast(gtkDialog), fi.path().toUtf8());
|
||||
gtk_file_chooser_set_current_name(gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8());
|
||||
} else if (filename.endsWith('/')) {
|
||||
gtk_file_chooser_set_current_folder(gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
|
||||
} else {
|
||||
gtk_file_chooser_select_filename(gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front();
|
||||
if (!initialNameFilter.isEmpty())
|
||||
selectNameFilter(initialNameFilter);
|
||||
|
||||
if (CustomButtonsSupported()) {
|
||||
GtkWidget *acceptButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK);
|
||||
if (acceptButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
|
||||
gtk_button_set_label(gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8());
|
||||
else*/ if (_acceptMode == QFileDialog::AcceptOpen)
|
||||
gtk_button_set_label(gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8());
|
||||
else
|
||||
gtk_button_set_label(gtk_button_cast(acceptButton), tr::lng_settings_save(tr::now).toUtf8());
|
||||
}
|
||||
|
||||
GtkWidget *rejectButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL);
|
||||
if (rejectButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
|
||||
gtk_button_set_label(gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8());
|
||||
else*/
|
||||
gtk_button_set_label(gtk_button_cast(rejectButton), tr::lng_cancel(tr::now).toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GtkFileDialog::setNameFilters(const QStringList &filters) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
foreach (GtkFileFilter *filter, _filters)
|
||||
gtk_file_chooser_remove_filter(gtk_file_chooser_cast(gtkDialog), filter);
|
||||
|
||||
_filters.clear();
|
||||
_filterNames.clear();
|
||||
|
||||
for_const (auto &filter, filters) {
|
||||
GtkFileFilter *gtkFilter = gtk_file_filter_new();
|
||||
auto name = filter;//.left(filter.indexOf(QLatin1Char('(')));
|
||||
auto extensions = cleanFilterList(filter);
|
||||
|
||||
gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8());
|
||||
for_const (auto &ext, extensions) {
|
||||
auto caseInsensitiveExt = QString();
|
||||
caseInsensitiveExt.reserve(4 * ext.size());
|
||||
for_const (auto ch, ext) {
|
||||
auto chLower = ch.toLower();
|
||||
auto chUpper = ch.toUpper();
|
||||
if (chLower != chUpper) {
|
||||
caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']');
|
||||
} else {
|
||||
caseInsensitiveExt.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8());
|
||||
}
|
||||
|
||||
gtk_file_chooser_add_filter(gtk_file_chooser_cast(gtkDialog), gtkFilter);
|
||||
|
||||
_filters.insert(filter, gtkFilter);
|
||||
_filterNames.insert(gtkFilter, filter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Supported() {
|
||||
return internal::GdkHelperLoaded()
|
||||
&& (gtk_widget_hide_on_delete != nullptr)
|
||||
&& (gtk_clipboard_store != nullptr)
|
||||
&& (gtk_clipboard_get != nullptr)
|
||||
&& (gtk_widget_destroy != nullptr)
|
||||
&& (gtk_dialog_get_type != nullptr)
|
||||
&& (gtk_dialog_run != nullptr)
|
||||
&& (gtk_widget_realize != nullptr)
|
||||
&& (gdk_window_set_modal_hint != nullptr)
|
||||
&& (gtk_widget_show != nullptr)
|
||||
&& (gdk_window_focus != nullptr)
|
||||
&& (gtk_widget_hide != nullptr)
|
||||
&& (gtk_widget_hide_on_delete != nullptr)
|
||||
&& (gtk_file_chooser_dialog_new != nullptr)
|
||||
&& (gtk_file_chooser_get_type != nullptr)
|
||||
&& (gtk_file_chooser_set_current_folder != nullptr)
|
||||
&& (gtk_file_chooser_get_current_folder != nullptr)
|
||||
&& (gtk_file_chooser_set_current_name != nullptr)
|
||||
&& (gtk_file_chooser_select_filename != nullptr)
|
||||
&& (gtk_file_chooser_get_filenames != nullptr)
|
||||
&& (gtk_file_chooser_set_filter != nullptr)
|
||||
&& (gtk_file_chooser_get_filter != nullptr)
|
||||
&& (gtk_window_get_type != nullptr)
|
||||
&& (gtk_window_set_title != nullptr)
|
||||
&& (gtk_file_chooser_set_local_only != nullptr)
|
||||
&& (gtk_file_chooser_set_action != nullptr)
|
||||
&& (gtk_file_chooser_set_select_multiple != nullptr)
|
||||
&& (gtk_file_chooser_set_do_overwrite_confirmation != nullptr)
|
||||
&& (gtk_file_chooser_remove_filter != nullptr)
|
||||
&& (gtk_file_filter_set_name != nullptr)
|
||||
&& (gtk_file_filter_add_pattern != nullptr)
|
||||
&& (gtk_file_chooser_add_filter != nullptr)
|
||||
&& (gtk_file_filter_new != nullptr);
|
||||
}
|
||||
|
||||
bool Use(Type type) {
|
||||
// use gtk file dialog on gtk-based desktop environments
|
||||
// or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3)
|
||||
// or if portals are used and operation is to open folder
|
||||
// and portal doesn't support folder choosing
|
||||
const auto sandboxedOrCustomPortal = InFlatpak()
|
||||
|| InSnap()
|
||||
|| UseXDGDesktopPortal();
|
||||
|
||||
const auto neededForPortal = (type == Type::ReadFolder)
|
||||
&& !CanOpenDirectoryWithPortal();
|
||||
|
||||
const auto neededNonForced = DesktopEnvironment::IsGtkBased()
|
||||
|| (sandboxedOrCustomPortal && neededForPortal);
|
||||
|
||||
const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal;
|
||||
|
||||
return IsGtkIntegrationForced()
|
||||
|| (neededNonForced && !excludeNonForced);
|
||||
}
|
||||
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
GtkFileDialog dialog(parent, caption, QString(), filter);
|
||||
|
||||
dialog.setModal(true);
|
||||
if (type == Type::ReadFile || type == Type::ReadFiles) {
|
||||
dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
} else if (type == Type::ReadFolder) {
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly);
|
||||
} else {
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
}
|
||||
if (startFile.isEmpty() || startFile.at(0) != '/') {
|
||||
startFile = cDialogLastPath() + '/' + startFile;
|
||||
}
|
||||
dialog.selectFile(startFile);
|
||||
|
||||
int res = dialog.exec();
|
||||
|
||||
QString path = dialog.directory().absolutePath();
|
||||
if (path != cDialogLastPath()) {
|
||||
cSetDialogLastPath(path);
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
if (res == QDialog::Accepted) {
|
||||
if (type == Type::ReadFiles) {
|
||||
files = dialog.selectedFiles();
|
||||
} else {
|
||||
files = dialog.selectedFiles().mid(0, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
files = QStringList();
|
||||
remoteContent = QByteArray();
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Gtk
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
31
Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.h
Normal file
31
Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace FileDialog {
|
||||
namespace Gtk {
|
||||
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
bool Supported();
|
||||
bool Use(Type type = Type::ReadFile);
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile);
|
||||
|
||||
} // namespace Gtk
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
523
Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp
Normal file
523
Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/linux/linux_xlib_helper.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "platform/linux/linux_gtk_file_dialog.h"
|
||||
#include "platform/linux/linux_open_with_dialog.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
namespace {
|
||||
|
||||
bool GtkTriedToInit = false;
|
||||
bool GtkLoaded = false;
|
||||
|
||||
bool LoadLibrary(QLibrary &lib, const char *name, int version) {
|
||||
#ifdef LINK_TO_GTK
|
||||
return true;
|
||||
#else // LINK_TO_GTK
|
||||
DEBUG_LOG(("Loading '%1' with version %2...").arg(
|
||||
QLatin1String(name)).arg(version));
|
||||
lib.setFileNameAndVersion(QLatin1String(name), version);
|
||||
if (lib.load()) {
|
||||
DEBUG_LOG(("Loaded '%1' with version %2!").arg(
|
||||
QLatin1String(name)).arg(version));
|
||||
return true;
|
||||
}
|
||||
lib.setFileNameAndVersion(QLatin1String(name), QString());
|
||||
if (lib.load()) {
|
||||
DEBUG_LOG(("Loaded '%1' without version!").arg(QLatin1String(name)));
|
||||
return true;
|
||||
}
|
||||
LOG(("Could not load '%1' with version %2 :(").arg(
|
||||
QLatin1String(name)).arg(version));
|
||||
return false;
|
||||
#endif // !LINK_TO_GTK
|
||||
}
|
||||
|
||||
void GtkMessageHandler(
|
||||
const gchar *log_domain,
|
||||
GLogLevelFlags log_level,
|
||||
const gchar *message,
|
||||
gpointer unused_data) {
|
||||
// Silence false-positive Gtk warnings (we are using Xlib to set
|
||||
// the WM_TRANSIENT_FOR hint).
|
||||
if (message != qstr("GtkDialog mapped without a transient parent. "
|
||||
"This is discouraged.")) {
|
||||
// For other messages, call the default handler.
|
||||
g_log_default_handler(log_domain, log_level, message, unused_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool SetupGtkBase(QLibrary &lib_gtk) {
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_init_check", gtk_init_check)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_check_version", gtk_check_version)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_settings_get_default", gtk_settings_get_default)) return false;
|
||||
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_show", gtk_widget_show)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_hide", gtk_widget_hide)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_get_window", gtk_widget_get_window)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_realize", gtk_widget_realize)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_hide_on_delete", gtk_widget_hide_on_delete)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_destroy", gtk_widget_destroy)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_get", gtk_clipboard_get)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_contents", gtk_clipboard_wait_for_contents)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_image", gtk_clipboard_wait_for_image)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_selection_data_targets_include_image", gtk_selection_data_targets_include_image)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_selection_data_free", gtk_selection_data_free)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_dialog_new", gtk_file_chooser_dialog_new)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_type", gtk_file_chooser_get_type)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_image_get_type", gtk_image_get_type)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_folder", gtk_file_chooser_set_current_folder)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_current_folder", gtk_file_chooser_get_current_folder)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_name", gtk_file_chooser_set_current_name)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_select_filename", gtk_file_chooser_select_filename)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_filenames", gtk_file_chooser_get_filenames)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_filter", gtk_file_chooser_set_filter)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_filter", gtk_file_chooser_get_filter)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_window_get_type", gtk_window_get_type)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_window_set_title", gtk_window_set_title)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_local_only", gtk_file_chooser_set_local_only)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_action", gtk_file_chooser_set_action)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_select_multiple", gtk_file_chooser_set_select_multiple)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_do_overwrite_confirmation", gtk_file_chooser_set_do_overwrite_confirmation)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_remove_filter", gtk_file_chooser_remove_filter)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_filter_set_name", gtk_file_filter_set_name)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_filter_add_pattern", gtk_file_filter_add_pattern)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_add_filter", gtk_file_chooser_add_filter)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget", gtk_file_chooser_set_preview_widget)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_preview_filename", gtk_file_chooser_get_preview_filename)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget_active", gtk_file_chooser_set_preview_widget_active)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_filter_new", gtk_file_filter_new)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_image_new", gtk_image_new)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_image_set_from_pixbuf", gtk_image_set_from_pixbuf)) return false;
|
||||
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gdk_window_set_modal_hint", gdk_window_set_modal_hint)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gdk_window_focus", gdk_window_focus)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_dialog_get_type", gtk_dialog_get_type)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_dialog_run", gtk_dialog_run)) return false;
|
||||
|
||||
if (!LOAD_GTK_SYMBOL(lib_gtk, "gdk_atom_intern", gdk_atom_intern)) return false;
|
||||
|
||||
if (LOAD_GTK_SYMBOL(lib_gtk, "gdk_set_allowed_backends", gdk_set_allowed_backends)) {
|
||||
// We work only with Wayland and X11 GDK backends.
|
||||
// Otherwise we get segfault in Ubuntu 17.04 in gtk_init_check() call.
|
||||
// See https://github.com/telegramdesktop/tdesktop/issues/3176
|
||||
// See https://github.com/telegramdesktop/tdesktop/issues/3162
|
||||
if(IsWayland()) {
|
||||
DEBUG_LOG(("Limit allowed GDK backends to wayland,x11"));
|
||||
gdk_set_allowed_backends("wayland,x11");
|
||||
} else {
|
||||
DEBUG_LOG(("Limit allowed GDK backends to x11,wayland"));
|
||||
gdk_set_allowed_backends("x11,wayland");
|
||||
}
|
||||
}
|
||||
|
||||
// gtk_init will reset the Xlib error handler,
|
||||
// and that causes Qt applications to quit on X errors.
|
||||
// Therefore, we need to manually restore it.
|
||||
XErrorHandlerRestorer handlerRestorer;
|
||||
|
||||
DEBUG_LOG(("Library gtk functions loaded!"));
|
||||
GtkTriedToInit = true;
|
||||
if (!gtk_init_check(0, 0)) {
|
||||
gtk_init_check = nullptr;
|
||||
DEBUG_LOG(("Failed to gtk_init_check(0, 0)!"));
|
||||
return false;
|
||||
}
|
||||
DEBUG_LOG(("Checked gtk with gtk_init_check!"));
|
||||
|
||||
// Use our custom log handler.
|
||||
g_log_set_handler("Gtk", G_LOG_LEVEL_MESSAGE, GtkMessageHandler, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetImageFromClipboardSupported() {
|
||||
return (gtk_clipboard_get != nullptr)
|
||||
&& (gtk_clipboard_wait_for_contents != nullptr)
|
||||
&& (gtk_clipboard_wait_for_image != nullptr)
|
||||
&& (gtk_selection_data_targets_include_image != nullptr)
|
||||
&& (gtk_selection_data_free != nullptr)
|
||||
&& (gdk_pixbuf_get_pixels != nullptr)
|
||||
&& (gdk_pixbuf_get_width != nullptr)
|
||||
&& (gdk_pixbuf_get_height != nullptr)
|
||||
&& (gdk_pixbuf_get_rowstride != nullptr)
|
||||
&& (gdk_pixbuf_get_has_alpha != nullptr)
|
||||
&& (gdk_atom_intern != nullptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> GtkSetting(const QString &propertyName) {
|
||||
const auto integration = GtkIntegration::Instance();
|
||||
if (!integration
|
||||
|| !integration->loaded()
|
||||
|| gtk_settings_get_default == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto settings = gtk_settings_get_default();
|
||||
T value;
|
||||
g_object_get(settings, propertyName.toUtf8(), &value, nullptr);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool IconThemeShouldBeSet() {
|
||||
// change the icon theme only if
|
||||
// it isn't already set by a platformtheme plugin
|
||||
// if QT_QPA_PLATFORMTHEME=(gtk2|gtk3), then force-apply the icon theme
|
||||
static const auto Result =
|
||||
// QGenericUnixTheme
|
||||
(QIcon::themeName() == qstr("hicolor")
|
||||
&& QIcon::fallbackThemeName() == qstr("hicolor"))
|
||||
// QGnomeTheme
|
||||
|| (QIcon::themeName() == qstr("Adwaita")
|
||||
&& QIcon::fallbackThemeName() == qstr("gnome"))
|
||||
// qt5ct
|
||||
|| (QIcon::themeName().isEmpty()
|
||||
&& QIcon::fallbackThemeName().isEmpty())
|
||||
|| IsGtkIntegrationForced();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool CursorSizeShouldBeSet() {
|
||||
// change the cursor size only on Wayland and if it wasn't already set
|
||||
static const auto Result = IsWayland()
|
||||
&& qEnvironmentVariableIsEmpty("XCURSOR_SIZE");
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
void SetScaleFactor() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
const auto integration = GtkIntegration::Instance();
|
||||
const auto ratio = Core::Sandbox::Instance().devicePixelRatio();
|
||||
if (!integration || ratio > 1.) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scaleFactor = integration->scaleFactor().value_or(1);
|
||||
if (scaleFactor == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("GTK scale factor: %1").arg(scaleFactor));
|
||||
cSetScreenScale(std::clamp(scaleFactor * 100, 100, 300));
|
||||
});
|
||||
}
|
||||
|
||||
void SetIconTheme() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
const auto integration = GtkIntegration::Instance();
|
||||
if (!integration || !IconThemeShouldBeSet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto themeName = integration->getStringSetting(
|
||||
qsl("gtk-icon-theme-name"));
|
||||
|
||||
const auto fallbackThemeName = integration->getStringSetting(
|
||||
qsl("gtk-fallback-icon-theme"));
|
||||
|
||||
if (!themeName.has_value() || !fallbackThemeName.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("Setting GTK icon theme"));
|
||||
|
||||
QIcon::setThemeName(*themeName);
|
||||
QIcon::setFallbackThemeName(*fallbackThemeName);
|
||||
|
||||
DEBUG_LOG(("New icon theme: %1").arg(QIcon::themeName()));
|
||||
DEBUG_LOG(("New fallback icon theme: %1").arg(
|
||||
QIcon::fallbackThemeName()));
|
||||
|
||||
SetApplicationIcon(Window::CreateIcon());
|
||||
if (App::wnd()) {
|
||||
App::wnd()->setWindowIcon(Window::CreateIcon());
|
||||
}
|
||||
|
||||
Core::App().domain().notifyUnreadBadgeChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void SetCursorSize() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
const auto integration = GtkIntegration::Instance();
|
||||
if (!integration || !CursorSizeShouldBeSet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto newCursorSize = integration->getIntSetting(
|
||||
qsl("gtk-cursor-theme-size"));
|
||||
|
||||
if (!newCursorSize.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("Setting GTK cursor size"));
|
||||
qputenv("XCURSOR_SIZE", QByteArray::number(*newCursorSize));
|
||||
DEBUG_LOG(("New cursor size: %1").arg(*newCursorSize));
|
||||
});
|
||||
}
|
||||
|
||||
void DarkModeChanged() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
Core::App().settings().setSystemDarkMode(IsDarkMode());
|
||||
});
|
||||
}
|
||||
|
||||
void DecorationLayoutChanged() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
Core::App().settings().setWindowControlsLayout(
|
||||
WindowControlsLayout());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GtkIntegration::GtkIntegration() {
|
||||
}
|
||||
|
||||
GtkIntegration *GtkIntegration::Instance() {
|
||||
static const auto useGtkIntegration = !qEnvironmentVariableIsSet(
|
||||
kDisableGtkIntegration.utf8());
|
||||
|
||||
if (!useGtkIntegration) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static GtkIntegration instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void GtkIntegration::load() {
|
||||
Expects(!GtkLoaded);
|
||||
DEBUG_LOG(("Loading GTK"));
|
||||
|
||||
QLibrary lib_gtk;
|
||||
lib_gtk.setLoadHints(QLibrary::DeepBindHint);
|
||||
|
||||
if (LoadLibrary(lib_gtk, "gtk-3", 0)) {
|
||||
GtkLoaded = SetupGtkBase(lib_gtk);
|
||||
}
|
||||
if (!GtkLoaded
|
||||
&& !GtkTriedToInit
|
||||
&& LoadLibrary(lib_gtk, "gtk-x11-2.0", 0)) {
|
||||
GtkLoaded = SetupGtkBase(lib_gtk);
|
||||
}
|
||||
|
||||
if (GtkLoaded) {
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_display_get_default", gdk_display_get_default);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_display_get_primary_monitor", gdk_display_get_primary_monitor);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_monitor_get_scale_factor", gdk_monitor_get_scale_factor);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_new_from_file_at_size", gdk_pixbuf_new_from_file_at_size);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_has_alpha", gdk_pixbuf_get_has_alpha);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_pixels", gdk_pixbuf_get_pixels);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_width", gdk_pixbuf_get_width);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_height", gdk_pixbuf_get_height);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_rowstride", gdk_pixbuf_get_rowstride);
|
||||
|
||||
GdkHelperLoad(lib_gtk);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gtk_dialog_get_widget_for_response", gtk_dialog_get_widget_for_response);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gtk_button_set_label", gtk_button_set_label);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gtk_button_get_type", gtk_button_get_type);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gtk_app_chooser_dialog_new", gtk_app_chooser_dialog_new);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gtk_app_chooser_get_app_info", gtk_app_chooser_get_app_info);
|
||||
LOAD_GTK_SYMBOL(lib_gtk, "gtk_app_chooser_get_type", gtk_app_chooser_get_type);
|
||||
|
||||
SetScaleFactor();
|
||||
SetIconTheme();
|
||||
SetCursorSize();
|
||||
|
||||
const auto settings = gtk_settings_get_default();
|
||||
|
||||
g_signal_connect(
|
||||
settings,
|
||||
"notify::gtk-icon-theme-name",
|
||||
G_CALLBACK(SetIconTheme),
|
||||
nullptr);
|
||||
|
||||
g_signal_connect(
|
||||
settings,
|
||||
"notify::gtk-theme-name",
|
||||
G_CALLBACK(DarkModeChanged),
|
||||
nullptr);
|
||||
|
||||
g_signal_connect(
|
||||
settings,
|
||||
"notify::gtk-cursor-theme-size",
|
||||
G_CALLBACK(SetCursorSize),
|
||||
nullptr);
|
||||
|
||||
if (checkVersion(3, 0, 0)) {
|
||||
g_signal_connect(
|
||||
settings,
|
||||
"notify::gtk-application-prefer-dark-theme",
|
||||
G_CALLBACK(DarkModeChanged),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
if (checkVersion(3, 12, 0)) {
|
||||
g_signal_connect(
|
||||
settings,
|
||||
"notify::gtk-decoration-layout",
|
||||
G_CALLBACK(DecorationLayoutChanged),
|
||||
nullptr);
|
||||
}
|
||||
} else {
|
||||
LOG(("Could not load gtk-3 or gtk-x11-2.0!"));
|
||||
}
|
||||
}
|
||||
|
||||
bool GtkIntegration::loaded() const {
|
||||
return GtkLoaded;
|
||||
}
|
||||
|
||||
bool GtkIntegration::checkVersion(uint major, uint minor, uint micro) const {
|
||||
return (loaded() && gtk_check_version != nullptr)
|
||||
? !gtk_check_version(major, minor, micro)
|
||||
: false;
|
||||
}
|
||||
|
||||
std::optional<bool> GtkIntegration::getBoolSetting(
|
||||
const QString &propertyName) const {
|
||||
const auto value = GtkSetting<gboolean>(propertyName);
|
||||
if (!value.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
DEBUG_LOG(("Getting GTK setting, %1: %2")
|
||||
.arg(propertyName)
|
||||
.arg(Logs::b(*value)));
|
||||
return *value;
|
||||
}
|
||||
|
||||
std::optional<int> GtkIntegration::getIntSetting(
|
||||
const QString &propertyName) const {
|
||||
const auto value = GtkSetting<gint>(propertyName);
|
||||
if (value.has_value()) {
|
||||
DEBUG_LOG(("Getting GTK setting, %1: %2")
|
||||
.arg(propertyName)
|
||||
.arg(*value));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<QString> GtkIntegration::getStringSetting(
|
||||
const QString &propertyName) const {
|
||||
auto value = GtkSetting<gchararray>(propertyName);
|
||||
if (!value.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto str = QString::fromUtf8(*value);
|
||||
g_free(*value);
|
||||
DEBUG_LOG(("Getting GTK setting, %1: '%2'").arg(propertyName).arg(str));
|
||||
return str;
|
||||
}
|
||||
|
||||
std::optional<int> GtkIntegration::scaleFactor() const {
|
||||
if (!loaded()
|
||||
|| (gdk_display_get_default == nullptr)
|
||||
|| (gdk_display_get_primary_monitor == nullptr)
|
||||
|| (gdk_monitor_get_scale_factor == nullptr)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return gdk_monitor_get_scale_factor(
|
||||
gdk_display_get_primary_monitor(gdk_display_get_default()));
|
||||
}
|
||||
|
||||
bool GtkIntegration::fileDialogSupported() const {
|
||||
return FileDialog::Gtk::Supported();
|
||||
}
|
||||
|
||||
bool GtkIntegration::useFileDialog(FileDialogType type) const {
|
||||
return FileDialog::Gtk::Use(type);
|
||||
}
|
||||
|
||||
bool GtkIntegration::getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
FileDialogType type,
|
||||
QString startFile) const {
|
||||
return FileDialog::Gtk::Get(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
}
|
||||
|
||||
bool GtkIntegration::showOpenWithDialog(const QString &filepath) const {
|
||||
return File::internal::ShowOpenWithDialog(filepath);
|
||||
}
|
||||
|
||||
QImage GtkIntegration::getImageFromClipboard() const {
|
||||
QImage data;
|
||||
|
||||
if (!GetImageFromClipboardSupported()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const auto clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
if (!clipboard) {
|
||||
return data;
|
||||
}
|
||||
|
||||
auto gsel = gtk_clipboard_wait_for_contents(
|
||||
clipboard,
|
||||
gdk_atom_intern("TARGETS", true));
|
||||
|
||||
if (gsel) {
|
||||
if (gtk_selection_data_targets_include_image(gsel, false)) {
|
||||
auto img = gtk_clipboard_wait_for_image(clipboard);
|
||||
|
||||
if (img) {
|
||||
data = QImage(
|
||||
gdk_pixbuf_get_pixels(img),
|
||||
gdk_pixbuf_get_width(img),
|
||||
gdk_pixbuf_get_height(img),
|
||||
gdk_pixbuf_get_rowstride(img),
|
||||
gdk_pixbuf_get_has_alpha(img)
|
||||
? QImage::Format_RGBA8888
|
||||
: QImage::Format_RGB888).copy();
|
||||
|
||||
g_object_unref(img);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_selection_data_free(gsel);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
61
Telegram/SourceFiles/platform/linux/linux_gtk_integration.h
Normal file
61
Telegram/SourceFiles/platform/linux/linux_gtk_integration.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
inline constexpr auto kDisableGtkIntegration = "TDESKTOP_DISABLE_GTK_INTEGRATION"_cs;
|
||||
|
||||
class GtkIntegration {
|
||||
public:
|
||||
static GtkIntegration *Instance();
|
||||
|
||||
void load();
|
||||
[[nodiscard]] bool loaded() const;
|
||||
[[nodiscard]] bool checkVersion(
|
||||
uint major,
|
||||
uint minor,
|
||||
uint micro) const;
|
||||
|
||||
[[nodiscard]] std::optional<bool> getBoolSetting(
|
||||
const QString &propertyName) const;
|
||||
|
||||
[[nodiscard]] std::optional<int> getIntSetting(
|
||||
const QString &propertyName) const;
|
||||
|
||||
[[nodiscard]] std::optional<QString> getStringSetting(
|
||||
const QString &propertyName) const;
|
||||
|
||||
[[nodiscard]] std::optional<int> scaleFactor() const;
|
||||
|
||||
using FileDialogType = ::FileDialog::internal::Type;
|
||||
[[nodiscard]] bool fileDialogSupported() const;
|
||||
[[nodiscard]] bool useFileDialog(
|
||||
FileDialogType type = FileDialogType::ReadFile) const;
|
||||
[[nodiscard]] bool getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
FileDialogType type,
|
||||
QString startFile) const;
|
||||
|
||||
[[nodiscard]] bool showOpenWithDialog(const QString &filepath) const;
|
||||
|
||||
[[nodiscard]] QImage getImageFromClipboard() const;
|
||||
|
||||
private:
|
||||
GtkIntegration();
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
GtkIntegration::GtkIntegration() {
|
||||
}
|
||||
|
||||
GtkIntegration *GtkIntegration::Instance() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GtkIntegration::load() {
|
||||
}
|
||||
|
||||
bool GtkIntegration::loaded() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GtkIntegration::checkVersion(uint major, uint minor, uint micro) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<bool> GtkIntegration::getBoolSetting(
|
||||
const QString &propertyName) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> GtkIntegration::getIntSetting(
|
||||
const QString &propertyName) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<QString> GtkIntegration::getStringSetting(
|
||||
const QString &propertyName) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> GtkIntegration::scaleFactor() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool GtkIntegration::fileDialogSupported() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GtkIntegration::useFileDialog(FileDialogType type) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GtkIntegration::getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
FileDialogType type,
|
||||
QString startFile) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GtkIntegration::showOpenWithDialog(const QString &filepath) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage GtkIntegration::getImageFromClipboard() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
162
Telegram/SourceFiles/platform/linux/linux_gtk_integration_p.h
Normal file
162
Telegram/SourceFiles/platform/linux/linux_gtk_integration_p.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
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 <QtCore/QLibrary>
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
|
||||
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
#define LINK_TO_GTK
|
||||
#endif // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
|
||||
#ifdef LINK_TO_GTK
|
||||
#define LOAD_GTK_SYMBOL(lib, name, func) (func = ::func)
|
||||
#else // LINK_TO_GTK
|
||||
#define LOAD_GTK_SYMBOL Platform::Gtk::LoadSymbol
|
||||
#endif // !LINK_TO_GTK
|
||||
|
||||
// To be able to compile with gtk-2.0 headers as well
|
||||
#define GdkMonitor GdkScreen
|
||||
typedef struct _GtkAppChooser GtkAppChooser;
|
||||
|
||||
namespace Platform {
|
||||
namespace Gtk {
|
||||
|
||||
template <typename Function>
|
||||
bool LoadSymbol(QLibrary &lib, const char *name, Function &func) {
|
||||
func = nullptr;
|
||||
if (!lib.isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
func = reinterpret_cast<Function>(lib.resolve(name));
|
||||
if (func) {
|
||||
return true;
|
||||
}
|
||||
LOG(("Error: failed to load '%1' function!").arg(name));
|
||||
return false;
|
||||
}
|
||||
|
||||
inline gboolean (*gtk_init_check)(int *argc, char ***argv) = nullptr;
|
||||
inline const gchar* (*gtk_check_version)(guint required_major, guint required_minor, guint required_micro) = nullptr;
|
||||
inline GtkSettings* (*gtk_settings_get_default)(void) = nullptr;
|
||||
inline void (*gtk_widget_show)(GtkWidget *widget) = nullptr;
|
||||
inline void (*gtk_widget_hide)(GtkWidget *widget) = nullptr;
|
||||
inline GdkWindow* (*gtk_widget_get_window)(GtkWidget *widget) = nullptr;
|
||||
inline void (*gtk_widget_realize)(GtkWidget *widget) = nullptr;
|
||||
inline gboolean (*gtk_widget_hide_on_delete)(GtkWidget *widget) = nullptr;
|
||||
inline void (*gtk_widget_destroy)(GtkWidget *widget) = nullptr;
|
||||
inline GtkClipboard* (*gtk_clipboard_get)(GdkAtom selection) = nullptr;
|
||||
inline void (*gtk_clipboard_store)(GtkClipboard *clipboard) = nullptr;
|
||||
inline GtkSelectionData* (*gtk_clipboard_wait_for_contents)(GtkClipboard *clipboard, GdkAtom target) = nullptr;
|
||||
inline GdkPixbuf* (*gtk_clipboard_wait_for_image)(GtkClipboard *clipboard) = nullptr;
|
||||
inline gboolean (*gtk_selection_data_targets_include_image)(const GtkSelectionData *selection_data, gboolean writable) = nullptr;
|
||||
inline void (*gtk_selection_data_free)(GtkSelectionData *data) = nullptr;
|
||||
inline GtkWidget* (*gtk_file_chooser_dialog_new)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...) G_GNUC_NULL_TERMINATED = nullptr;
|
||||
inline gboolean (*gtk_file_chooser_set_current_folder)(GtkFileChooser *chooser, const gchar *filename) = nullptr;
|
||||
inline gchar* (*gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_current_name)(GtkFileChooser *chooser, const gchar *name) = nullptr;
|
||||
inline gboolean (*gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const gchar *filename) = nullptr;
|
||||
inline GSList* (*gtk_file_chooser_get_filenames)(GtkFileChooser *chooser) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr;
|
||||
inline GtkFileFilter* (*gtk_file_chooser_get_filter)(GtkFileChooser *chooser) = nullptr;
|
||||
inline void (*gtk_window_set_title)(GtkWindow *window, const gchar *title) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_local_only)(GtkFileChooser *chooser, gboolean local_only) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_action)(GtkFileChooser *chooser, GtkFileChooserAction action) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_select_multiple)(GtkFileChooser *chooser, gboolean select_multiple) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_do_overwrite_confirmation)(GtkFileChooser *chooser, gboolean do_overwrite_confirmation) = nullptr;
|
||||
inline GtkWidget* (*gtk_dialog_get_widget_for_response)(GtkDialog *dialog, gint response_id) = nullptr;
|
||||
inline void (*gtk_button_set_label)(GtkButton *button, const gchar *label) = nullptr;
|
||||
inline void (*gtk_file_chooser_remove_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr;
|
||||
inline void (*gtk_file_filter_set_name)(GtkFileFilter *filter, const gchar *name) = nullptr;
|
||||
inline void (*gtk_file_filter_add_pattern)(GtkFileFilter *filter, const gchar *pattern) = nullptr;
|
||||
inline void (*gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_preview_widget)(GtkFileChooser *chooser, GtkWidget *preview_widget) = nullptr;
|
||||
inline gchar* (*gtk_file_chooser_get_preview_filename)(GtkFileChooser *chooser) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_preview_widget_active)(GtkFileChooser *chooser, gboolean active) = nullptr;
|
||||
inline GtkFileFilter* (*gtk_file_filter_new)(void) = nullptr;
|
||||
inline GtkWidget* (*gtk_image_new)(void) = nullptr;
|
||||
inline void (*gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf) = nullptr;
|
||||
inline GtkWidget* (*gtk_app_chooser_dialog_new)(GtkWindow *parent, GtkDialogFlags flags, GFile *file) = nullptr;
|
||||
inline GAppInfo* (*gtk_app_chooser_get_app_info)(GtkAppChooser *self) = nullptr;
|
||||
inline void (*gdk_set_allowed_backends)(const gchar *backends) = nullptr;
|
||||
inline void (*gdk_window_set_modal_hint)(GdkWindow *window, gboolean modal) = nullptr;
|
||||
inline void (*gdk_window_focus)(GdkWindow *window, guint32 timestamp) = nullptr;
|
||||
|
||||
template <typename Result, typename Object>
|
||||
inline Result *g_type_cic_helper(Object *instance, GType iface_type) {
|
||||
return reinterpret_cast<Result*>(g_type_check_instance_cast(reinterpret_cast<GTypeInstance*>(instance), iface_type));
|
||||
}
|
||||
|
||||
inline GType (*gtk_dialog_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkDialog *gtk_dialog_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkDialog, Object>(obj, gtk_dialog_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_file_chooser_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkFileChooser, Object>(obj, gtk_file_chooser_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_image_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkImage *gtk_image_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkImage, Object>(obj, gtk_image_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_button_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkButton *gtk_button_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkButton, Object>(obj, gtk_button_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_window_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkWindow *gtk_window_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkWindow, Object>(obj, gtk_window_get_type());
|
||||
}
|
||||
|
||||
inline GType (*gtk_app_chooser_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
template <typename Object>
|
||||
inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkAppChooser, Object>(obj, gtk_app_chooser_get_type());
|
||||
}
|
||||
|
||||
template <typename Object>
|
||||
inline bool g_type_cit_helper(Object *instance, GType iface_type) {
|
||||
if (!instance) return false;
|
||||
|
||||
auto ginstance = reinterpret_cast<GTypeInstance*>(instance);
|
||||
if (ginstance->g_class && ginstance->g_class->g_type == iface_type) {
|
||||
return true;
|
||||
}
|
||||
return g_type_check_instance_is_a(ginstance, iface_type);
|
||||
}
|
||||
|
||||
inline gint (*gtk_dialog_run)(GtkDialog *dialog) = nullptr;
|
||||
inline GdkAtom (*gdk_atom_intern)(const gchar *atom_name, gboolean only_if_exists) = nullptr;
|
||||
inline GdkDisplay* (*gdk_display_get_default)(void) = nullptr;
|
||||
inline GdkMonitor* (*gdk_display_get_primary_monitor)(GdkDisplay *display) = nullptr;
|
||||
inline int (*gdk_monitor_get_scale_factor)(GdkMonitor *monitor) = nullptr;
|
||||
inline GdkPixbuf* (*gdk_pixbuf_new_from_file_at_size)(const gchar *filename, int width, int height, GError **error) = nullptr;
|
||||
inline gboolean (*gdk_pixbuf_get_has_alpha)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
inline guchar* (*gdk_pixbuf_get_pixels)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
inline int (*gdk_pixbuf_get_width)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
inline int (*gdk_pixbuf_get_height)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
inline int (*gdk_pixbuf_get_rowstride)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
|
||||
} // namespace Gtk
|
||||
} // namespace Platform
|
||||
@@ -1,360 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_libs.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/linux/linux_xlib_helper.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
using Platform::internal::XErrorHandlerRestorer;
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
namespace Platform {
|
||||
namespace Libs {
|
||||
namespace {
|
||||
|
||||
bool gtkTriedToInit = false;
|
||||
bool gtkLoaded = false;
|
||||
|
||||
bool loadLibrary(QLibrary &lib, const char *name, int version) {
|
||||
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
return true;
|
||||
#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
DEBUG_LOG(("Loading '%1' with version %2...").arg(QLatin1String(name)).arg(version));
|
||||
lib.setFileNameAndVersion(QLatin1String(name), version);
|
||||
if (lib.load()) {
|
||||
DEBUG_LOG(("Loaded '%1' with version %2!").arg(QLatin1String(name)).arg(version));
|
||||
return true;
|
||||
}
|
||||
lib.setFileNameAndVersion(QLatin1String(name), QString());
|
||||
if (lib.load()) {
|
||||
DEBUG_LOG(("Loaded '%1' without version!").arg(QLatin1String(name)));
|
||||
return true;
|
||||
}
|
||||
LOG(("Could not load '%1' with version %2 :(").arg(QLatin1String(name)).arg(version));
|
||||
return false;
|
||||
#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
void gtkMessageHandler(
|
||||
const gchar *log_domain,
|
||||
GLogLevelFlags log_level,
|
||||
const gchar *message,
|
||||
gpointer unused_data) {
|
||||
// Silence false-positive Gtk warnings (we are using Xlib to set
|
||||
// the WM_TRANSIENT_FOR hint).
|
||||
if (message != qstr("GtkDialog mapped without a transient parent. "
|
||||
"This is discouraged.")) {
|
||||
// For other messages, call the default handler.
|
||||
g_log_default_handler(log_domain, log_level, message, unused_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool setupGtkBase(QLibrary &lib_gtk) {
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_init_check", gtk_init_check)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_check_version", gtk_check_version)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_settings_get_default", gtk_settings_get_default)) return false;
|
||||
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_show", gtk_widget_show)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_hide", gtk_widget_hide)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_get_window", gtk_widget_get_window)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_realize", gtk_widget_realize)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_hide_on_delete", gtk_widget_hide_on_delete)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_destroy", gtk_widget_destroy)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_get", gtk_clipboard_get)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_contents", gtk_clipboard_wait_for_contents)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_image", gtk_clipboard_wait_for_image)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_selection_data_targets_include_image", gtk_selection_data_targets_include_image)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_selection_data_free", gtk_selection_data_free)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_dialog_new", gtk_file_chooser_dialog_new)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_type", gtk_file_chooser_get_type)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_image_get_type", gtk_image_get_type)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_folder", gtk_file_chooser_set_current_folder)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_current_folder", gtk_file_chooser_get_current_folder)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_name", gtk_file_chooser_set_current_name)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_select_filename", gtk_file_chooser_select_filename)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_filenames", gtk_file_chooser_get_filenames)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_filter", gtk_file_chooser_set_filter)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_filter", gtk_file_chooser_get_filter)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_window_get_type", gtk_window_get_type)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_window_set_title", gtk_window_set_title)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_local_only", gtk_file_chooser_set_local_only)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_action", gtk_file_chooser_set_action)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_select_multiple", gtk_file_chooser_set_select_multiple)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_do_overwrite_confirmation", gtk_file_chooser_set_do_overwrite_confirmation)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_remove_filter", gtk_file_chooser_remove_filter)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_filter_set_name", gtk_file_filter_set_name)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_filter_add_pattern", gtk_file_filter_add_pattern)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_add_filter", gtk_file_chooser_add_filter)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget", gtk_file_chooser_set_preview_widget)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_preview_filename", gtk_file_chooser_get_preview_filename)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget_active", gtk_file_chooser_set_preview_widget_active)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_file_filter_new", gtk_file_filter_new)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_image_new", gtk_image_new)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_image_set_from_pixbuf", gtk_image_set_from_pixbuf)) return false;
|
||||
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gdk_window_set_modal_hint", gdk_window_set_modal_hint)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gdk_window_focus", gdk_window_focus)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_dialog_get_type", gtk_dialog_get_type)) return false;
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gtk_dialog_run", gtk_dialog_run)) return false;
|
||||
|
||||
if (!LOAD_SYMBOL(lib_gtk, "gdk_atom_intern", gdk_atom_intern)) return false;
|
||||
|
||||
if (LOAD_SYMBOL(lib_gtk, "gdk_set_allowed_backends", gdk_set_allowed_backends)) {
|
||||
// We work only with Wayland and X11 GDK backends.
|
||||
// Otherwise we get segfault in Ubuntu 17.04 in gtk_init_check() call.
|
||||
// See https://github.com/telegramdesktop/tdesktop/issues/3176
|
||||
// See https://github.com/telegramdesktop/tdesktop/issues/3162
|
||||
if(IsWayland()) {
|
||||
DEBUG_LOG(("Limit allowed GDK backends to wayland,x11"));
|
||||
gdk_set_allowed_backends("wayland,x11");
|
||||
} else {
|
||||
DEBUG_LOG(("Limit allowed GDK backends to x11,wayland"));
|
||||
gdk_set_allowed_backends("x11,wayland");
|
||||
}
|
||||
}
|
||||
|
||||
// gtk_init will reset the Xlib error handler, and that causes
|
||||
// Qt applications to quit on X errors. Therefore, we need to manually restore it.
|
||||
XErrorHandlerRestorer handlerRestorer;
|
||||
|
||||
DEBUG_LOG(("Library gtk functions loaded!"));
|
||||
gtkTriedToInit = true;
|
||||
if (!gtk_init_check(0, 0)) {
|
||||
gtk_init_check = nullptr;
|
||||
DEBUG_LOG(("Failed to gtk_init_check(0, 0)!"));
|
||||
return false;
|
||||
}
|
||||
DEBUG_LOG(("Checked gtk with gtk_init_check!"));
|
||||
|
||||
// Use our custom log handler.
|
||||
g_log_set_handler("Gtk", G_LOG_LEVEL_MESSAGE, gtkMessageHandler, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IconThemeShouldBeSet() {
|
||||
// change the icon theme only if it isn't already set by a platformtheme plugin
|
||||
// if QT_QPA_PLATFORMTHEME=(gtk2|gtk3), then force-apply the icon theme
|
||||
static const auto Result =
|
||||
// QGenericUnixTheme
|
||||
(QIcon::themeName() == qstr("hicolor")
|
||||
&& QIcon::fallbackThemeName() == qstr("hicolor"))
|
||||
// QGnomeTheme
|
||||
|| (QIcon::themeName() == qstr("Adwaita")
|
||||
&& QIcon::fallbackThemeName() == qstr("gnome"))
|
||||
// qt5ct
|
||||
|| (QIcon::themeName().isEmpty()
|
||||
&& QIcon::fallbackThemeName().isEmpty())
|
||||
|| IsGtkIntegrationForced();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool CursorSizeShouldBeSet() {
|
||||
// change the cursor size only on Wayland and if it wasn't already set
|
||||
static const auto Result = IsWayland()
|
||||
&& qEnvironmentVariableIsEmpty("XCURSOR_SIZE");
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
void SetIconTheme() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
if (GtkSettingSupported()
|
||||
&& GtkLoaded()
|
||||
&& IconThemeShouldBeSet()) {
|
||||
DEBUG_LOG(("Setting GTK icon theme"));
|
||||
QIcon::setThemeName(GtkSetting("gtk-icon-theme-name"));
|
||||
QIcon::setFallbackThemeName(GtkSetting("gtk-fallback-icon-theme"));
|
||||
|
||||
DEBUG_LOG(("New icon theme: %1").arg(QIcon::themeName()));
|
||||
DEBUG_LOG(("New fallback icon theme: %1").arg(QIcon::fallbackThemeName()));
|
||||
|
||||
SetApplicationIcon(Window::CreateIcon());
|
||||
if (App::wnd()) {
|
||||
App::wnd()->setWindowIcon(Window::CreateIcon());
|
||||
}
|
||||
|
||||
Core::App().domain().notifyUnreadBadgeChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SetCursorSize() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
if (GtkSettingSupported()
|
||||
&& GtkLoaded()
|
||||
&& CursorSizeShouldBeSet()) {
|
||||
DEBUG_LOG(("Setting GTK cursor size"));
|
||||
|
||||
const auto newCursorSize = GtkSetting<gint>("gtk-cursor-theme-size");
|
||||
qputenv("XCURSOR_SIZE", QByteArray::number(newCursorSize));
|
||||
|
||||
DEBUG_LOG(("New cursor size: %1").arg(newCursorSize));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DarkModeChanged() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
Core::App().settings().setSystemDarkMode(IsDarkMode());
|
||||
});
|
||||
}
|
||||
|
||||
void DecorationLayoutChanged() {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
||||
Core::App().settings().setWindowControlsLayout(WindowControlsLayout());
|
||||
});
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
f_gtk_init_check gtk_init_check = nullptr;
|
||||
f_gtk_check_version gtk_check_version = nullptr;
|
||||
f_gtk_settings_get_default gtk_settings_get_default = nullptr;
|
||||
f_gtk_widget_show gtk_widget_show = nullptr;
|
||||
f_gtk_widget_hide gtk_widget_hide = nullptr;
|
||||
f_gtk_widget_get_window gtk_widget_get_window = nullptr;
|
||||
f_gtk_widget_realize gtk_widget_realize = nullptr;
|
||||
f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete = nullptr;
|
||||
f_gtk_widget_destroy gtk_widget_destroy = nullptr;
|
||||
f_gtk_clipboard_get gtk_clipboard_get = nullptr;
|
||||
f_gtk_clipboard_store gtk_clipboard_store = nullptr;
|
||||
f_gtk_clipboard_wait_for_contents gtk_clipboard_wait_for_contents = nullptr;
|
||||
f_gtk_clipboard_wait_for_image gtk_clipboard_wait_for_image = nullptr;
|
||||
f_gtk_selection_data_targets_include_image gtk_selection_data_targets_include_image = nullptr;
|
||||
f_gtk_selection_data_free gtk_selection_data_free = nullptr;
|
||||
f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new = nullptr;
|
||||
f_gtk_file_chooser_get_type gtk_file_chooser_get_type = nullptr;
|
||||
f_gtk_image_get_type gtk_image_get_type = nullptr;
|
||||
f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder = nullptr;
|
||||
f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder = nullptr;
|
||||
f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name = nullptr;
|
||||
f_gtk_file_chooser_select_filename gtk_file_chooser_select_filename = nullptr;
|
||||
f_gtk_file_chooser_get_filenames gtk_file_chooser_get_filenames = nullptr;
|
||||
f_gtk_file_chooser_set_filter gtk_file_chooser_set_filter = nullptr;
|
||||
f_gtk_file_chooser_get_filter gtk_file_chooser_get_filter = nullptr;
|
||||
f_gtk_window_get_type gtk_window_get_type = nullptr;
|
||||
f_gtk_window_set_title gtk_window_set_title = nullptr;
|
||||
f_gtk_file_chooser_set_local_only gtk_file_chooser_set_local_only = nullptr;
|
||||
f_gtk_file_chooser_set_action gtk_file_chooser_set_action = nullptr;
|
||||
f_gtk_file_chooser_set_select_multiple gtk_file_chooser_set_select_multiple = nullptr;
|
||||
f_gtk_file_chooser_set_do_overwrite_confirmation gtk_file_chooser_set_do_overwrite_confirmation = nullptr;
|
||||
f_gtk_file_chooser_remove_filter gtk_file_chooser_remove_filter = nullptr;
|
||||
f_gtk_file_filter_set_name gtk_file_filter_set_name = nullptr;
|
||||
f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern = nullptr;
|
||||
f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter = nullptr;
|
||||
f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget = nullptr;
|
||||
f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename = nullptr;
|
||||
f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active = nullptr;
|
||||
f_gtk_file_filter_new gtk_file_filter_new = nullptr;
|
||||
f_gtk_image_new gtk_image_new = nullptr;
|
||||
f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf = nullptr;
|
||||
f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response = nullptr;
|
||||
f_gtk_button_set_label gtk_button_set_label = nullptr;
|
||||
f_gtk_button_get_type gtk_button_get_type = nullptr;
|
||||
f_gtk_app_chooser_dialog_new gtk_app_chooser_dialog_new = nullptr;
|
||||
f_gtk_app_chooser_get_app_info gtk_app_chooser_get_app_info = nullptr;
|
||||
f_gtk_app_chooser_get_type gtk_app_chooser_get_type = nullptr;
|
||||
f_gdk_set_allowed_backends gdk_set_allowed_backends = nullptr;
|
||||
f_gdk_window_set_modal_hint gdk_window_set_modal_hint = nullptr;
|
||||
f_gdk_window_focus gdk_window_focus = nullptr;
|
||||
f_gtk_dialog_get_type gtk_dialog_get_type = nullptr;
|
||||
f_gtk_dialog_run gtk_dialog_run = nullptr;
|
||||
f_gdk_atom_intern gdk_atom_intern = nullptr;
|
||||
f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size = nullptr;
|
||||
f_gdk_pixbuf_get_has_alpha gdk_pixbuf_get_has_alpha = nullptr;
|
||||
f_gdk_pixbuf_get_pixels gdk_pixbuf_get_pixels = nullptr;
|
||||
f_gdk_pixbuf_get_width gdk_pixbuf_get_width = nullptr;
|
||||
f_gdk_pixbuf_get_height gdk_pixbuf_get_height = nullptr;
|
||||
f_gdk_pixbuf_get_rowstride gdk_pixbuf_get_rowstride = nullptr;
|
||||
|
||||
bool GtkLoaded() {
|
||||
return gtkLoaded;
|
||||
}
|
||||
|
||||
::GtkClipboard *GtkClipboard() {
|
||||
if (gtk_clipboard_get != nullptr) {
|
||||
return gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
void start() {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (!UseGtkIntegration()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("Loading libraries"));
|
||||
|
||||
QLibrary lib_gtk;
|
||||
lib_gtk.setLoadHints(QLibrary::DeepBindHint);
|
||||
|
||||
if (loadLibrary(lib_gtk, "gtk-3", 0)) {
|
||||
gtkLoaded = setupGtkBase(lib_gtk);
|
||||
}
|
||||
if (!gtkLoaded && !gtkTriedToInit && loadLibrary(lib_gtk, "gtk-x11-2.0", 0)) {
|
||||
gtkLoaded = setupGtkBase(lib_gtk);
|
||||
}
|
||||
|
||||
if (gtkLoaded) {
|
||||
LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_new_from_file_at_size", gdk_pixbuf_new_from_file_at_size);
|
||||
LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_has_alpha", gdk_pixbuf_get_has_alpha);
|
||||
LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_pixels", gdk_pixbuf_get_pixels);
|
||||
LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_width", gdk_pixbuf_get_width);
|
||||
LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_height", gdk_pixbuf_get_height);
|
||||
LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_rowstride", gdk_pixbuf_get_rowstride);
|
||||
|
||||
internal::GdkHelperLoad(lib_gtk);
|
||||
|
||||
LOAD_SYMBOL(lib_gtk, "gtk_dialog_get_widget_for_response", gtk_dialog_get_widget_for_response);
|
||||
LOAD_SYMBOL(lib_gtk, "gtk_button_set_label", gtk_button_set_label);
|
||||
LOAD_SYMBOL(lib_gtk, "gtk_button_get_type", gtk_button_get_type);
|
||||
|
||||
LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_dialog_new", gtk_app_chooser_dialog_new);
|
||||
LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_get_app_info", gtk_app_chooser_get_app_info);
|
||||
LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_get_type", gtk_app_chooser_get_type);
|
||||
|
||||
SetIconTheme();
|
||||
SetCursorSize();
|
||||
|
||||
const auto settings = gtk_settings_get_default();
|
||||
g_signal_connect(settings, "notify::gtk-icon-theme-name", G_CALLBACK(SetIconTheme), nullptr);
|
||||
g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(DarkModeChanged), nullptr);
|
||||
g_signal_connect(settings, "notify::gtk-cursor-theme-size", G_CALLBACK(SetCursorSize), nullptr);
|
||||
|
||||
if (!gtk_check_version(3, 0, 0)) {
|
||||
g_signal_connect(settings, "notify::gtk-application-prefer-dark-theme", G_CALLBACK(DarkModeChanged), nullptr);
|
||||
}
|
||||
|
||||
if (!gtk_check_version(3, 12, 0)) {
|
||||
g_signal_connect(settings, "notify::gtk-decoration-layout", G_CALLBACK(DecorationLayoutChanged), nullptr);
|
||||
}
|
||||
} else {
|
||||
LOG(("Could not load gtk-3 or gtk-x11-2.0!"));
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
}
|
||||
|
||||
} // namespace Libs
|
||||
} // namespace Platform
|
||||
@@ -1,303 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QLibrary>
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
|
||||
// present starting with gtk 3.0, we can build with gtk2 headers
|
||||
typedef struct _GtkAppChooser GtkAppChooser;
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
#define LOAD_SYMBOL(lib, name, func) (func = ::func)
|
||||
#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
#define LOAD_SYMBOL Platform::Libs::load
|
||||
#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
|
||||
namespace Platform {
|
||||
namespace Libs {
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
bool GtkLoaded();
|
||||
::GtkClipboard *GtkClipboard();
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
void start();
|
||||
|
||||
template <typename Function>
|
||||
bool load(QLibrary &lib, const char *name, Function &func) {
|
||||
func = nullptr;
|
||||
if (!lib.isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
func = reinterpret_cast<Function>(lib.resolve(name));
|
||||
if (func) {
|
||||
return true;
|
||||
}
|
||||
LOG(("Error: failed to load '%1' function!").arg(name));
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
typedef gboolean (*f_gtk_init_check)(int *argc, char ***argv);
|
||||
extern f_gtk_init_check gtk_init_check;
|
||||
|
||||
typedef const gchar* (*f_gtk_check_version)(guint required_major, guint required_minor, guint required_micro);
|
||||
extern f_gtk_check_version gtk_check_version;
|
||||
|
||||
typedef GtkSettings* (*f_gtk_settings_get_default)(void);
|
||||
extern f_gtk_settings_get_default gtk_settings_get_default;
|
||||
|
||||
typedef void (*f_gtk_widget_show)(GtkWidget *widget);
|
||||
extern f_gtk_widget_show gtk_widget_show;
|
||||
|
||||
typedef void (*f_gtk_widget_hide)(GtkWidget *widget);
|
||||
extern f_gtk_widget_hide gtk_widget_hide;
|
||||
|
||||
typedef GdkWindow* (*f_gtk_widget_get_window)(GtkWidget *widget);
|
||||
extern f_gtk_widget_get_window gtk_widget_get_window;
|
||||
|
||||
typedef void (*f_gtk_widget_realize)(GtkWidget *widget);
|
||||
extern f_gtk_widget_realize gtk_widget_realize;
|
||||
|
||||
typedef gboolean (*f_gtk_widget_hide_on_delete)(GtkWidget *widget);
|
||||
extern f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete;
|
||||
|
||||
typedef void (*f_gtk_widget_destroy)(GtkWidget *widget);
|
||||
extern f_gtk_widget_destroy gtk_widget_destroy;
|
||||
|
||||
typedef ::GtkClipboard* (*f_gtk_clipboard_get)(GdkAtom selection);
|
||||
extern f_gtk_clipboard_get gtk_clipboard_get;
|
||||
|
||||
typedef void (*f_gtk_clipboard_store)(::GtkClipboard *clipboard);
|
||||
extern f_gtk_clipboard_store gtk_clipboard_store;
|
||||
|
||||
typedef GtkSelectionData* (*f_gtk_clipboard_wait_for_contents)(::GtkClipboard *clipboard, GdkAtom target);
|
||||
extern f_gtk_clipboard_wait_for_contents gtk_clipboard_wait_for_contents;
|
||||
|
||||
typedef GdkPixbuf* (*f_gtk_clipboard_wait_for_image)(::GtkClipboard *clipboard);
|
||||
extern f_gtk_clipboard_wait_for_image gtk_clipboard_wait_for_image;
|
||||
|
||||
typedef gboolean (*f_gtk_selection_data_targets_include_image)(const GtkSelectionData *selection_data, gboolean writable);
|
||||
extern f_gtk_selection_data_targets_include_image gtk_selection_data_targets_include_image;
|
||||
|
||||
typedef void (*f_gtk_selection_data_free)(GtkSelectionData *data);
|
||||
extern f_gtk_selection_data_free gtk_selection_data_free;
|
||||
|
||||
typedef GtkWidget* (*f_gtk_file_chooser_dialog_new)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...) G_GNUC_NULL_TERMINATED;
|
||||
extern f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new;
|
||||
|
||||
typedef gboolean (*f_gtk_file_chooser_set_current_folder)(GtkFileChooser *chooser, const gchar *filename);
|
||||
extern f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder;
|
||||
|
||||
typedef gchar* (*f_gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser);
|
||||
extern f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_current_name)(GtkFileChooser *chooser, const gchar *name);
|
||||
extern f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name;
|
||||
|
||||
typedef gboolean (*f_gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const gchar *filename);
|
||||
extern f_gtk_file_chooser_select_filename gtk_file_chooser_select_filename;
|
||||
|
||||
typedef GSList* (*f_gtk_file_chooser_get_filenames)(GtkFileChooser *chooser);
|
||||
extern f_gtk_file_chooser_get_filenames gtk_file_chooser_get_filenames;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_filter)(GtkFileChooser *chooser, GtkFileFilter *filter);
|
||||
extern f_gtk_file_chooser_set_filter gtk_file_chooser_set_filter;
|
||||
|
||||
typedef GtkFileFilter* (*f_gtk_file_chooser_get_filter)(GtkFileChooser *chooser);
|
||||
extern f_gtk_file_chooser_get_filter gtk_file_chooser_get_filter;
|
||||
|
||||
typedef void (*f_gtk_window_set_title)(GtkWindow *window, const gchar *title);
|
||||
extern f_gtk_window_set_title gtk_window_set_title;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_local_only)(GtkFileChooser *chooser, gboolean local_only);
|
||||
extern f_gtk_file_chooser_set_local_only gtk_file_chooser_set_local_only;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_action)(GtkFileChooser *chooser, GtkFileChooserAction action);
|
||||
extern f_gtk_file_chooser_set_action gtk_file_chooser_set_action;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_select_multiple)(GtkFileChooser *chooser, gboolean select_multiple);
|
||||
extern f_gtk_file_chooser_set_select_multiple gtk_file_chooser_set_select_multiple;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_do_overwrite_confirmation)(GtkFileChooser *chooser, gboolean do_overwrite_confirmation);
|
||||
extern f_gtk_file_chooser_set_do_overwrite_confirmation gtk_file_chooser_set_do_overwrite_confirmation;
|
||||
|
||||
typedef GtkWidget* (*f_gtk_dialog_get_widget_for_response)(GtkDialog *dialog, gint response_id);
|
||||
extern f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response;
|
||||
|
||||
typedef void (*f_gtk_button_set_label)(GtkButton *button, const gchar *label);
|
||||
extern f_gtk_button_set_label gtk_button_set_label;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_remove_filter)(GtkFileChooser *chooser, GtkFileFilter *filter);
|
||||
extern f_gtk_file_chooser_remove_filter gtk_file_chooser_remove_filter;
|
||||
|
||||
typedef void (*f_gtk_file_filter_set_name)(GtkFileFilter *filter, const gchar *name);
|
||||
extern f_gtk_file_filter_set_name gtk_file_filter_set_name;
|
||||
|
||||
typedef void (*f_gtk_file_filter_add_pattern)(GtkFileFilter *filter, const gchar *pattern);
|
||||
extern f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter);
|
||||
extern f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_preview_widget)(GtkFileChooser *chooser, GtkWidget *preview_widget);
|
||||
extern f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget;
|
||||
|
||||
typedef gchar* (*f_gtk_file_chooser_get_preview_filename)(GtkFileChooser *chooser);
|
||||
extern f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename;
|
||||
|
||||
typedef void (*f_gtk_file_chooser_set_preview_widget_active)(GtkFileChooser *chooser, gboolean active);
|
||||
extern f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active;
|
||||
|
||||
typedef GtkFileFilter* (*f_gtk_file_filter_new)(void);
|
||||
extern f_gtk_file_filter_new gtk_file_filter_new;
|
||||
|
||||
typedef GtkWidget* (*f_gtk_image_new)(void);
|
||||
extern f_gtk_image_new gtk_image_new;
|
||||
|
||||
typedef void (*f_gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf);
|
||||
extern f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf;
|
||||
|
||||
typedef GtkWidget* (*f_gtk_app_chooser_dialog_new)(GtkWindow *parent, GtkDialogFlags flags, GFile *file);
|
||||
extern f_gtk_app_chooser_dialog_new gtk_app_chooser_dialog_new;
|
||||
|
||||
typedef GAppInfo* (*f_gtk_app_chooser_get_app_info)(GtkAppChooser *self);
|
||||
extern f_gtk_app_chooser_get_app_info gtk_app_chooser_get_app_info;
|
||||
|
||||
typedef void (*f_gdk_set_allowed_backends)(const gchar *backends);
|
||||
extern f_gdk_set_allowed_backends gdk_set_allowed_backends;
|
||||
|
||||
typedef void (*f_gdk_window_set_modal_hint)(GdkWindow *window, gboolean modal);
|
||||
extern f_gdk_window_set_modal_hint gdk_window_set_modal_hint;
|
||||
|
||||
typedef void (*f_gdk_window_focus)(GdkWindow *window, guint32 timestamp);
|
||||
extern f_gdk_window_focus gdk_window_focus;
|
||||
|
||||
template <typename Result, typename Object>
|
||||
inline Result *g_type_cic_helper(Object *instance, GType iface_type) {
|
||||
return reinterpret_cast<Result*>(g_type_check_instance_cast(reinterpret_cast<GTypeInstance*>(instance), iface_type));
|
||||
}
|
||||
|
||||
typedef GType (*f_gtk_dialog_get_type)(void) G_GNUC_CONST;
|
||||
extern f_gtk_dialog_get_type gtk_dialog_get_type;
|
||||
|
||||
template <typename Object>
|
||||
inline GtkDialog *gtk_dialog_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkDialog, Object>(obj, gtk_dialog_get_type());
|
||||
}
|
||||
|
||||
typedef GType (*f_gtk_file_chooser_get_type)(void) G_GNUC_CONST;
|
||||
extern f_gtk_file_chooser_get_type gtk_file_chooser_get_type;
|
||||
|
||||
template <typename Object>
|
||||
inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkFileChooser, Object>(obj, gtk_file_chooser_get_type());
|
||||
}
|
||||
|
||||
typedef GType (*f_gtk_image_get_type)(void) G_GNUC_CONST;
|
||||
extern f_gtk_image_get_type gtk_image_get_type;
|
||||
|
||||
template <typename Object>
|
||||
inline GtkImage *gtk_image_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkImage, Object>(obj, gtk_image_get_type());
|
||||
}
|
||||
|
||||
typedef GType (*f_gtk_button_get_type)(void) G_GNUC_CONST;
|
||||
extern f_gtk_button_get_type gtk_button_get_type;
|
||||
|
||||
template <typename Object>
|
||||
inline GtkButton *gtk_button_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkButton, Object>(obj, gtk_button_get_type());
|
||||
}
|
||||
|
||||
typedef GType (*f_gtk_window_get_type)(void) G_GNUC_CONST;
|
||||
extern f_gtk_window_get_type gtk_window_get_type;
|
||||
|
||||
template <typename Object>
|
||||
inline GtkWindow *gtk_window_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkWindow, Object>(obj, gtk_window_get_type());
|
||||
}
|
||||
|
||||
typedef GType (*f_gtk_app_chooser_get_type)(void) G_GNUC_CONST;
|
||||
extern f_gtk_app_chooser_get_type gtk_app_chooser_get_type;
|
||||
|
||||
template <typename Object>
|
||||
inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) {
|
||||
return g_type_cic_helper<GtkAppChooser, Object>(obj, gtk_app_chooser_get_type());
|
||||
}
|
||||
|
||||
template <typename Object>
|
||||
inline bool g_type_cit_helper(Object *instance, GType iface_type) {
|
||||
if (!instance) return false;
|
||||
|
||||
auto ginstance = reinterpret_cast<GTypeInstance*>(instance);
|
||||
if (ginstance->g_class && ginstance->g_class->g_type == iface_type) {
|
||||
return true;
|
||||
}
|
||||
return g_type_check_instance_is_a(ginstance, iface_type);
|
||||
}
|
||||
|
||||
typedef gint (*f_gtk_dialog_run)(GtkDialog *dialog);
|
||||
extern f_gtk_dialog_run gtk_dialog_run;
|
||||
|
||||
typedef GdkAtom (*f_gdk_atom_intern)(const gchar *atom_name, gboolean only_if_exists);
|
||||
extern f_gdk_atom_intern gdk_atom_intern;
|
||||
|
||||
typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_file_at_size)(const gchar *filename, int width, int height, GError **error);
|
||||
extern f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size;
|
||||
|
||||
typedef gboolean (*f_gdk_pixbuf_get_has_alpha)(const GdkPixbuf *pixbuf);
|
||||
extern f_gdk_pixbuf_get_has_alpha gdk_pixbuf_get_has_alpha;
|
||||
|
||||
typedef guchar* (*f_gdk_pixbuf_get_pixels)(const GdkPixbuf *pixbuf);
|
||||
extern f_gdk_pixbuf_get_pixels gdk_pixbuf_get_pixels;
|
||||
|
||||
typedef int (*f_gdk_pixbuf_get_width)(const GdkPixbuf *pixbuf);
|
||||
extern f_gdk_pixbuf_get_width gdk_pixbuf_get_width;
|
||||
|
||||
typedef int (*f_gdk_pixbuf_get_height)(const GdkPixbuf *pixbuf);
|
||||
extern f_gdk_pixbuf_get_height gdk_pixbuf_get_height;
|
||||
|
||||
typedef int (*f_gdk_pixbuf_get_rowstride)(const GdkPixbuf *pixbuf);
|
||||
extern f_gdk_pixbuf_get_rowstride gdk_pixbuf_get_rowstride;
|
||||
|
||||
inline bool GtkSettingSupported() {
|
||||
return gtk_settings_get_default != nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T GtkSetting(const gchar *propertyName) {
|
||||
GtkSettings *settings = gtk_settings_get_default();
|
||||
T value;
|
||||
g_object_get(settings, propertyName, &value, nullptr);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline QString GtkSetting(const gchar *propertyName) {
|
||||
gchararray value = GtkSetting<gchararray>(propertyName);
|
||||
QString str = QString::fromUtf8(value);
|
||||
g_free(value);
|
||||
DEBUG_LOG(("Getting GTK setting, %1: '%2'").arg(propertyName).arg(str));
|
||||
return str;
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace Libs
|
||||
} // namespace Platform
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_notification_service_watcher.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
|
||||
#include <QtDBus/QDBusConnection>
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
NotificationServiceWatcher::NotificationServiceWatcher()
|
||||
: _dbusWatcher(
|
||||
qsl("org.freedesktop.Notifications"),
|
||||
QDBusConnection::sessionBus(),
|
||||
QDBusServiceWatcher::WatchForOwnerChange) {
|
||||
const auto signal = &QDBusServiceWatcher::serviceOwnerChanged;
|
||||
QObject::connect(&_dbusWatcher, signal, [=](
|
||||
const QString &service,
|
||||
const QString &oldOwner,
|
||||
const QString &newOwner) {
|
||||
crl::on_main([=] {
|
||||
if (!Core::App().domain().started()) {
|
||||
return;
|
||||
} else if (IsNotificationServiceActivatable()
|
||||
&& newOwner.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::App().notifications().createManager();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
@@ -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
|
||||
|
||||
#include <QtDBus/QDBusServiceWatcher>
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
class NotificationServiceWatcher {
|
||||
public:
|
||||
NotificationServiceWatcher();
|
||||
|
||||
private:
|
||||
QDBusServiceWatcher _dbusWatcher;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
132
Telegram/SourceFiles/platform/linux/linux_open_with_dialog.cpp
Normal file
132
Telegram/SourceFiles/platform/linux/linux_open_with_dialog.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_open_with_dialog.h"
|
||||
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/application.h"
|
||||
|
||||
#include <private/qguiapplication_p.h>
|
||||
|
||||
namespace Platform {
|
||||
namespace File {
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
bool OpenWithDialogSupported() {
|
||||
return Platform::internal::GdkHelperLoaded()
|
||||
&& (gtk_app_chooser_dialog_new != nullptr)
|
||||
&& (gtk_app_chooser_get_app_info != nullptr)
|
||||
&& (gtk_app_chooser_get_type != nullptr)
|
||||
&& (gtk_widget_get_window != nullptr)
|
||||
&& (gtk_widget_realize != nullptr)
|
||||
&& (gtk_widget_show != nullptr)
|
||||
&& (gtk_widget_destroy != nullptr);
|
||||
}
|
||||
|
||||
class OpenWithDialog : public QWindow {
|
||||
public:
|
||||
OpenWithDialog(const QString &filepath);
|
||||
~OpenWithDialog();
|
||||
|
||||
bool exec();
|
||||
|
||||
private:
|
||||
static void handleResponse(OpenWithDialog *dialog, int responseId);
|
||||
|
||||
GFile *_gfileInstance = nullptr;
|
||||
GtkWidget *_gtkWidget = nullptr;
|
||||
QEventLoop _loop;
|
||||
std::optional<bool> _result;
|
||||
};
|
||||
|
||||
OpenWithDialog::OpenWithDialog(const QString &filepath)
|
||||
: _gfileInstance(g_file_new_for_path(filepath.toUtf8()))
|
||||
, _gtkWidget(gtk_app_chooser_dialog_new(
|
||||
nullptr,
|
||||
GTK_DIALOG_MODAL,
|
||||
_gfileInstance)) {
|
||||
g_signal_connect_swapped(
|
||||
_gtkWidget,
|
||||
"response",
|
||||
G_CALLBACK(handleResponse),
|
||||
this);
|
||||
}
|
||||
|
||||
OpenWithDialog::~OpenWithDialog() {
|
||||
gtk_widget_destroy(_gtkWidget);
|
||||
g_object_unref(_gfileInstance);
|
||||
}
|
||||
|
||||
bool OpenWithDialog::exec() {
|
||||
gtk_widget_realize(_gtkWidget);
|
||||
|
||||
if (const auto activeWindow = Core::App().activeWindow()) {
|
||||
Platform::internal::XSetTransientForHint(
|
||||
gtk_widget_get_window(_gtkWidget),
|
||||
activeWindow->widget().get()->windowHandle()->winId());
|
||||
}
|
||||
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
gtk_widget_show(_gtkWidget);
|
||||
|
||||
if (!_result.has_value()) {
|
||||
_loop.exec();
|
||||
}
|
||||
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
return *_result;
|
||||
}
|
||||
|
||||
void OpenWithDialog::handleResponse(OpenWithDialog *dialog, int responseId) {
|
||||
GAppInfo *chosenAppInfo = nullptr;
|
||||
dialog->_result = true;
|
||||
|
||||
switch (responseId) {
|
||||
case GTK_RESPONSE_OK:
|
||||
chosenAppInfo = gtk_app_chooser_get_app_info(
|
||||
gtk_app_chooser_cast(dialog->_gtkWidget));
|
||||
|
||||
if (chosenAppInfo) {
|
||||
GList *uris = nullptr;
|
||||
uris = g_list_prepend(uris, g_file_get_uri(dialog->_gfileInstance));
|
||||
g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr);
|
||||
g_list_free(uris);
|
||||
g_object_unref(chosenAppInfo);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case GTK_RESPONSE_CANCEL:
|
||||
case GTK_RESPONSE_DELETE_EVENT:
|
||||
break;
|
||||
|
||||
default:
|
||||
dialog->_result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
dialog->_loop.quit();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowOpenWithDialog(const QString &filepath) {
|
||||
if (!OpenWithDialogSupported()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return OpenWithDialog(filepath).exec();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace File
|
||||
} // namespace Platform
|
||||
18
Telegram/SourceFiles/platform/linux/linux_open_with_dialog.h
Normal file
18
Telegram/SourceFiles/platform/linux/linux_open_with_dialog.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
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 {
|
||||
namespace File {
|
||||
namespace internal {
|
||||
|
||||
bool ShowOpenWithDialog(const QString &filepath);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace File
|
||||
} // namespace Platform
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_xlib_helper.h"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
extern "C" {
|
||||
#include <X11/Xlib.h>
|
||||
}
|
||||
@@ -37,4 +36,3 @@ XErrorHandlerRestorer::~XErrorHandlerRestorer() = default;
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
@@ -23,4 +22,3 @@ private:
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
@@ -204,6 +204,13 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -250,7 +257,7 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
std::less<>(),
|
||||
&QSize::width);
|
||||
|
||||
if ((*biggestSize).width() > firstAttemptSize.width()) {
|
||||
if (biggestSize->width() > firstAttemptSize.width()) {
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(*biggestSize)
|
||||
.toImage();
|
||||
@@ -381,7 +388,7 @@ std::unique_ptr<QTemporaryFile> TrayIconFile(
|
||||
std::less<>(),
|
||||
&QSize::width);
|
||||
|
||||
if ((*biggestSize).width() > firstAttemptSize.width()) {
|
||||
if (biggestSize->width() > firstAttemptSize.width()) {
|
||||
scalePixmap(icon.pixmap(*biggestSize)).save(ret.get());
|
||||
} else {
|
||||
scalePixmap(firstAttempt).save(ret.get());
|
||||
@@ -420,10 +427,20 @@ bool IsSNIAvailable() {
|
||||
|
||||
if (reply.isValid()) {
|
||||
return reply.value().toBool();
|
||||
} else if (reply.error().type() != QDBusError::ServiceUnknown) {
|
||||
LOG(("SNI Error: %1").arg(reply.error().message()));
|
||||
}
|
||||
|
||||
switch (reply.error().type()) {
|
||||
case QDBusError::Disconnected:
|
||||
case QDBusError::ServiceUnknown:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LOG(("SNI Error: %1: %2")
|
||||
.arg(reply.error().name())
|
||||
.arg(reply.error().message()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -505,6 +522,11 @@ void ForceDisabled(QAction *action, bool disabled) {
|
||||
|
||||
MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
: Window::MainWindow(controller) {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
qDBusRegisterMetaType<ToolTip>();
|
||||
qDBusRegisterMetaType<IconPixmap>();
|
||||
qDBusRegisterMetaType<IconPixmapList>();
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
void MainWindow::initHook() {
|
||||
@@ -521,11 +543,13 @@ void MainWindow::initHook() {
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
g_signal_connect(
|
||||
_sniDBusProxy,
|
||||
"g-signal",
|
||||
G_CALLBACK(sniSignalEmitted),
|
||||
nullptr);
|
||||
if (_sniDBusProxy) {
|
||||
g_signal_connect(
|
||||
_sniDBusProxy,
|
||||
"g-signal",
|
||||
G_CALLBACK(sniSignalEmitted),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
auto sniWatcher = new QDBusServiceWatcher(
|
||||
kSNIWatcherService.utf16(),
|
||||
@@ -593,6 +617,11 @@ bool MainWindow::hasTrayIcon() const {
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
bool MainWindow::isActiveForTrayMenu() {
|
||||
updateIsActive();
|
||||
return Platform::IsWayland() ? isVisible() : isActive();
|
||||
}
|
||||
|
||||
void MainWindow::psShowTrayMenu() {
|
||||
_trayIconMenuXEmbed->popup(QCursor::pos());
|
||||
}
|
||||
@@ -607,7 +636,17 @@ void MainWindow::psTrayMenuUpdated() {
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
void MainWindow::setSNITrayIcon(int counter, bool muted) {
|
||||
if (IsIndicatorApplication()) {
|
||||
const auto iconName = GetTrayIconName(counter, muted);
|
||||
const auto panelIconName = GetPanelIconName(counter, muted);
|
||||
|
||||
if (iconName == panelIconName) {
|
||||
if (_sniTrayIcon->iconName() == iconName) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sniTrayIcon->setIconByName(iconName);
|
||||
_sniTrayIcon->setToolTipIconByName(iconName);
|
||||
} else if (IsIndicatorApplication()) {
|
||||
if (!IsIconRegenerationNeeded(counter, muted)
|
||||
&& _trayIconFile
|
||||
&& _sniTrayIcon->iconName() == _trayIconFile->fileName()) {
|
||||
@@ -661,7 +700,7 @@ void MainWindow::sniSignalEmitted(
|
||||
gchar *signal_name,
|
||||
GVariant *parameters,
|
||||
gpointer user_data) {
|
||||
if(signal_name == qstr("StatusNotifierHostRegistered")) {
|
||||
if (signal_name == qstr("StatusNotifierHostRegistered")) {
|
||||
crl::on_main([] {
|
||||
if (const auto window = App::wnd()) {
|
||||
window->handleSNIHostRegistered();
|
||||
@@ -684,7 +723,7 @@ void MainWindow::handleSNIHostRegistered() {
|
||||
LOG(("Switching to SNI tray icon..."));
|
||||
|
||||
if (trayIcon) {
|
||||
trayIcon->setContextMenu(0);
|
||||
trayIcon->setContextMenu(nullptr);
|
||||
trayIcon->deleteLater();
|
||||
}
|
||||
trayIcon = nullptr;
|
||||
@@ -877,16 +916,8 @@ void MainWindow::updateWaylandDecorationColors() {
|
||||
windowHandle()->resize(windowHandle()->size());
|
||||
}
|
||||
|
||||
void MainWindow::LibsLoaded() {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
qDBusRegisterMetaType<ToolTip>();
|
||||
qDBusRegisterMetaType<IconPixmap>();
|
||||
qDBusRegisterMetaType<IconPixmapList>();
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
void MainWindow::initTrayMenuHook() {
|
||||
_trayIconMenuXEmbed = new Ui::PopupMenu(nullptr, trayIconMenu);
|
||||
_trayIconMenuXEmbed.emplace(nullptr, trayIconMenu);
|
||||
_trayIconMenuXEmbed->deleteOnHide(false);
|
||||
}
|
||||
|
||||
@@ -1031,19 +1062,19 @@ void MainWindow::createGlobalMenu() {
|
||||
psAddContact = tools->addAction(
|
||||
tr::lng_mac_menu_add_contact(tr::now),
|
||||
App::wnd(),
|
||||
[=] { App::wnd()->onShowAddContact(); });
|
||||
[=] { App::wnd()->showAddContact(); });
|
||||
|
||||
tools->addSeparator();
|
||||
|
||||
psNewGroup = tools->addAction(
|
||||
tr::lng_mac_menu_new_group(tr::now),
|
||||
App::wnd(),
|
||||
[=] { App::wnd()->onShowNewGroup(); });
|
||||
[=] { App::wnd()->showNewGroup(); });
|
||||
|
||||
psNewChannel = tools->addAction(
|
||||
tr::lng_mac_menu_new_channel(tr::now),
|
||||
App::wnd(),
|
||||
[=] { App::wnd()->onShowNewChannel(); });
|
||||
[=] { App::wnd()->showNewChannel(); });
|
||||
|
||||
auto help = psMainMenu->addMenu(tr::lng_linux_menu_help(tr::now));
|
||||
|
||||
@@ -1220,10 +1251,10 @@ MainWindow::~MainWindow() {
|
||||
delete _mainMenuExporter;
|
||||
delete psMainMenu;
|
||||
|
||||
g_object_unref(_sniDBusProxy);
|
||||
if (_sniDBusProxy) {
|
||||
g_object_unref(_sniDBusProxy);
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
delete _trayIconMenuXEmbed;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform_main_window.h"
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
@@ -43,7 +44,7 @@ public:
|
||||
return _sniAvailable || QSystemTrayIcon::isSystemTrayAvailable();
|
||||
}
|
||||
|
||||
static void LibsLoaded();
|
||||
bool isActiveForTrayMenu() override;
|
||||
|
||||
~MainWindow();
|
||||
|
||||
@@ -75,7 +76,7 @@ protected:
|
||||
|
||||
private:
|
||||
bool _sniAvailable = false;
|
||||
Ui::PopupMenu *_trayIconMenuXEmbed = nullptr;
|
||||
base::unique_qptr<Ui::PopupMenu> _trayIconMenuXEmbed;
|
||||
|
||||
void updateIconCounters();
|
||||
void updateWaylandDecorationColors();
|
||||
|
||||
@@ -19,7 +19,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include <QtCore/QVersionNumber>
|
||||
#include <QtDBus/QDBusConnection>
|
||||
#include <QtDBus/QDBusConnectionInterface>
|
||||
#include <QtDBus/QDBusMessage>
|
||||
#include <QtDBus/QDBusPendingCall>
|
||||
#include <QtDBus/QDBusPendingCallWatcher>
|
||||
#include <QtDBus/QDBusPendingReply>
|
||||
#include <QtDBus/QDBusReply>
|
||||
#include <QtDBus/QDBusError>
|
||||
|
||||
@@ -33,120 +37,104 @@ namespace Platform {
|
||||
namespace Notifications {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDBusTimeout = 30000;
|
||||
constexpr auto kService = "org.freedesktop.Notifications"_cs;
|
||||
constexpr auto kObjectPath = "/org/freedesktop/Notifications"_cs;
|
||||
constexpr auto kInterface = kService;
|
||||
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
|
||||
constexpr auto kImageDataType = "(iiibii@ay)"_cs;
|
||||
constexpr auto kNotifyArgsType = "(susssasa{sv}i)"_cs;
|
||||
|
||||
bool NotificationsSupported = false;
|
||||
bool InhibitedNotSupported = false;
|
||||
struct ServerInformation {
|
||||
QString name;
|
||||
QString vendor;
|
||||
QVersionNumber version;
|
||||
QVersionNumber specVersion;
|
||||
};
|
||||
|
||||
bool ServiceRegistered = false;
|
||||
bool InhibitionSupported = false;
|
||||
std::optional<ServerInformation> CurrentServerInformation;
|
||||
QStringList CurrentCapabilities;
|
||||
|
||||
bool GetServiceRegistered() {
|
||||
const auto interface = QDBusConnection::sessionBus().interface();
|
||||
const auto activatable = IsNotificationServiceActivatable();
|
||||
|
||||
return interface
|
||||
? interface->isServiceRegistered(kService.utf16()) || activatable
|
||||
: activatable;
|
||||
}
|
||||
|
||||
void GetServerInformation(Fn<void(std::optional<ServerInformation>)> callback) {
|
||||
using ServerInformationReply = QDBusPendingReply<
|
||||
QString,
|
||||
QString,
|
||||
QString,
|
||||
QString>;
|
||||
|
||||
void ComputeSupported(bool wait = false) {
|
||||
const auto message = QDBusMessage::createMethodCall(
|
||||
kService.utf16(),
|
||||
kObjectPath.utf16(),
|
||||
kInterface.utf16(),
|
||||
qsl("GetServerInformation"));
|
||||
|
||||
auto async = QDBusConnection::sessionBus().asyncCall(message);
|
||||
const auto async = QDBusConnection::sessionBus().asyncCall(message);
|
||||
auto watcher = new QDBusPendingCallWatcher(async);
|
||||
|
||||
QObject::connect(
|
||||
watcher,
|
||||
&QDBusPendingCallWatcher::finished,
|
||||
[=](QDBusPendingCallWatcher *call) {
|
||||
QDBusPendingReply<
|
||||
QString,
|
||||
QString,
|
||||
QString,
|
||||
QString> reply = *call;
|
||||
const auto finished = [=](QDBusPendingCallWatcher *call) {
|
||||
const ServerInformationReply reply = *call;
|
||||
|
||||
if (reply.isValid()) {
|
||||
NotificationsSupported = true;
|
||||
}
|
||||
if (reply.isValid()) {
|
||||
crl::on_main([=] {
|
||||
callback(ServerInformation{
|
||||
reply.argumentAt<0>(),
|
||||
reply.argumentAt<1>(),
|
||||
QVersionNumber::fromString(reply.argumentAt<2>()),
|
||||
QVersionNumber::fromString(reply.argumentAt<3>()),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
LOG(("Native Notification Error: %1: %2")
|
||||
.arg(reply.error().name())
|
||||
.arg(reply.error().message()));
|
||||
|
||||
call->deleteLater();
|
||||
});
|
||||
crl::on_main([=] { callback(std::nullopt); });
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
watcher->waitForFinished();
|
||||
}
|
||||
call->deleteLater();
|
||||
};
|
||||
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, finished);
|
||||
}
|
||||
|
||||
void GetSupported() {
|
||||
static auto Checked = false;
|
||||
if (Checked) {
|
||||
return;
|
||||
}
|
||||
Checked = true;
|
||||
|
||||
if (Core::App().settings().nativeNotifications() && !IsWayland()) {
|
||||
ComputeSupported(true);
|
||||
} else {
|
||||
ComputeSupported();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<QString> ComputeServerInformation() {
|
||||
std::vector<QString> serverInformation;
|
||||
|
||||
const auto message = QDBusMessage::createMethodCall(
|
||||
kService.utf16(),
|
||||
kObjectPath.utf16(),
|
||||
kInterface.utf16(),
|
||||
qsl("GetServerInformation"));
|
||||
|
||||
const auto reply = QDBusConnection::sessionBus().call(message);
|
||||
|
||||
if (reply.type() == QDBusMessage::ReplyMessage) {
|
||||
ranges::transform(
|
||||
reply.arguments(),
|
||||
ranges::back_inserter(serverInformation),
|
||||
&QVariant::toString
|
||||
);
|
||||
} else if (reply.type() == QDBusMessage::ErrorMessage) {
|
||||
LOG(("Native notification error: %1").arg(reply.errorMessage()));
|
||||
} else {
|
||||
LOG(("Native notification error: "
|
||||
"invalid reply from GetServerInformation"));
|
||||
}
|
||||
|
||||
return serverInformation;
|
||||
}
|
||||
|
||||
std::vector<QString> GetServerInformation() {
|
||||
static const auto Result = ComputeServerInformation();
|
||||
return Result;
|
||||
}
|
||||
|
||||
QStringList ComputeCapabilities() {
|
||||
void GetCapabilities(Fn<void(QStringList)> callback) {
|
||||
const auto message = QDBusMessage::createMethodCall(
|
||||
kService.utf16(),
|
||||
kObjectPath.utf16(),
|
||||
kInterface.utf16(),
|
||||
qsl("GetCapabilities"));
|
||||
|
||||
const QDBusReply<QStringList> reply = QDBusConnection::sessionBus().call(
|
||||
message);
|
||||
const auto async = QDBusConnection::sessionBus().asyncCall(message);
|
||||
auto watcher = new QDBusPendingCallWatcher(async);
|
||||
|
||||
if (reply.isValid()) {
|
||||
return reply.value();
|
||||
} else {
|
||||
LOG(("Native notification error: %1").arg(reply.error().message()));
|
||||
}
|
||||
const auto finished = [=](QDBusPendingCallWatcher *call) {
|
||||
const QDBusPendingReply<QStringList> reply = *call;
|
||||
|
||||
return {};
|
||||
if (reply.isValid()) {
|
||||
crl::on_main([=] { callback(reply.value()); });
|
||||
} else {
|
||||
LOG(("Native Notification Error: %1: %2")
|
||||
.arg(reply.error().name())
|
||||
.arg(reply.error().message()));
|
||||
|
||||
crl::on_main([=] { callback({}); });
|
||||
}
|
||||
|
||||
call->deleteLater();
|
||||
};
|
||||
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, finished);
|
||||
}
|
||||
|
||||
QStringList GetCapabilities() {
|
||||
static const auto Result = ComputeCapabilities();
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool Inhibited() {
|
||||
void GetInhibitionSupported(Fn<void(bool)> callback) {
|
||||
auto message = QDBusMessage::createMethodCall(
|
||||
kService.utf16(),
|
||||
kObjectPath.utf16(),
|
||||
@@ -154,61 +142,102 @@ bool Inhibited() {
|
||||
qsl("Get"));
|
||||
|
||||
message.setArguments({
|
||||
qsl("org.freedesktop.Notifications"),
|
||||
kInterface.utf16(),
|
||||
qsl("Inhibited")
|
||||
});
|
||||
|
||||
const auto async = QDBusConnection::sessionBus().asyncCall(message);
|
||||
auto watcher = new QDBusPendingCallWatcher(async);
|
||||
|
||||
const auto finished = [=](QDBusPendingCallWatcher *call) {
|
||||
const auto error = QDBusPendingReply<QVariant>(*call).error();
|
||||
|
||||
if (error.isValid() && error.type() != QDBusError::InvalidArgs) {
|
||||
LOG(("Native Notification Error: %1: %2")
|
||||
.arg(error.name())
|
||||
.arg(error.message()));
|
||||
}
|
||||
|
||||
crl::on_main([=] { callback(!error.isValid()); });
|
||||
call->deleteLater();
|
||||
};
|
||||
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, finished);
|
||||
}
|
||||
|
||||
bool Inhibited() {
|
||||
if (!Supported()
|
||||
|| !CurrentCapabilities.contains(qsl("inhibitions"))
|
||||
|| !InhibitionSupported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto message = QDBusMessage::createMethodCall(
|
||||
kService.utf16(),
|
||||
kObjectPath.utf16(),
|
||||
kPropertiesInterface.utf16(),
|
||||
qsl("Get"));
|
||||
|
||||
message.setArguments({
|
||||
kInterface.utf16(),
|
||||
qsl("Inhibited")
|
||||
});
|
||||
|
||||
const QDBusReply<QVariant> reply = QDBusConnection::sessionBus().call(
|
||||
message);
|
||||
|
||||
static const auto NotSupportedErrors = {
|
||||
QDBusError::ServiceUnknown,
|
||||
QDBusError::InvalidArgs,
|
||||
};
|
||||
|
||||
if (reply.isValid()) {
|
||||
return reply.value().toBool();
|
||||
} else if (ranges::contains(NotSupportedErrors, reply.error().type())) {
|
||||
InhibitedNotSupported = true;
|
||||
} else {
|
||||
if (reply.error().type() == QDBusError::AccessDenied) {
|
||||
InhibitedNotSupported = true;
|
||||
}
|
||||
|
||||
LOG(("Native notification error: %1").arg(reply.error().message()));
|
||||
}
|
||||
|
||||
LOG(("Native Notification Error: %1: %2")
|
||||
.arg(reply.error().name())
|
||||
.arg(reply.error().message()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QVersionNumber ParseSpecificationVersion(
|
||||
const std::vector<QString> &serverInformation) {
|
||||
if (serverInformation.size() >= 4) {
|
||||
return QVersionNumber::fromString(serverInformation[3]);
|
||||
} else {
|
||||
LOG(("Native notification error: "
|
||||
"server information should have 4 elements"));
|
||||
}
|
||||
bool IsQualifiedDaemon() {
|
||||
// A list of capabilities that offer feature parity
|
||||
// with custom notifications
|
||||
static const auto NeededCapabilities = {
|
||||
// To show message content
|
||||
qsl("body"),
|
||||
// To make the sender name bold
|
||||
qsl("body-markup"),
|
||||
// To have buttons on notifications
|
||||
qsl("actions"),
|
||||
// To have quick reply
|
||||
qsl("inline-reply"),
|
||||
// To not to play sound with Don't Disturb activated
|
||||
// (no, using sound capability is not a way)
|
||||
qsl("inhibitions"),
|
||||
};
|
||||
|
||||
return QVersionNumber();
|
||||
return ranges::all_of(NeededCapabilities, [&](const auto &capability) {
|
||||
return CurrentCapabilities.contains(capability);
|
||||
}) && InhibitionSupported;
|
||||
}
|
||||
|
||||
ServerInformation CurrentServerInformationValue() {
|
||||
return CurrentServerInformation.value_or(ServerInformation{});
|
||||
}
|
||||
|
||||
QString GetImageKey(const QVersionNumber &specificationVersion) {
|
||||
if (!specificationVersion.isNull()) {
|
||||
if (specificationVersion >= QVersionNumber(1, 2)) {
|
||||
return qsl("image-data");
|
||||
} else if (specificationVersion == QVersionNumber(1, 1)) {
|
||||
return qsl("image_data");
|
||||
} else if (specificationVersion < QVersionNumber(1, 1)) {
|
||||
return qsl("icon_data");
|
||||
} else {
|
||||
LOG(("Native notification error: unknown specification version"));
|
||||
}
|
||||
} else {
|
||||
LOG(("Native notification error: specification version is null"));
|
||||
const auto normalizedVersion = specificationVersion.normalized();
|
||||
|
||||
if (normalizedVersion.isNull()) {
|
||||
LOG(("Native Notification Error: specification version is null"));
|
||||
return QString();
|
||||
}
|
||||
|
||||
return QString();
|
||||
if (normalizedVersion >= QVersionNumber(1, 2)) {
|
||||
return qsl("image-data");
|
||||
} else if (normalizedVersion == QVersionNumber(1, 1)) {
|
||||
return qsl("image_data");
|
||||
}
|
||||
|
||||
return qsl("icon_data");
|
||||
}
|
||||
|
||||
class NotificationData {
|
||||
@@ -230,7 +259,7 @@ public:
|
||||
|
||||
~NotificationData();
|
||||
|
||||
bool show();
|
||||
void show();
|
||||
void close();
|
||||
void setImage(const QString &imagePath);
|
||||
|
||||
@@ -255,6 +284,11 @@ private:
|
||||
void actionInvoked(uint id, const QString &actionName);
|
||||
void notificationReplied(uint id, const QString &text);
|
||||
|
||||
static void notificationShown(
|
||||
GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data);
|
||||
|
||||
static void signalEmitted(
|
||||
GDBusConnection *connection,
|
||||
const gchar *sender_name,
|
||||
@@ -277,8 +311,7 @@ NotificationData::NotificationData(
|
||||
bool hideReplyButton)
|
||||
: _manager(manager)
|
||||
, _title(title)
|
||||
, _imageKey(GetImageKey(ParseSpecificationVersion(
|
||||
GetServerInformation())))
|
||||
, _imageKey(GetImageKey(CurrentServerInformationValue().specVersion))
|
||||
, _id(id) {
|
||||
GError *error = nullptr;
|
||||
|
||||
@@ -288,12 +321,12 @@ NotificationData::NotificationData(
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
LOG(("Native notification error: %1").arg(error->message));
|
||||
LOG(("Native Notification Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto capabilities = GetCapabilities();
|
||||
const auto capabilities = CurrentCapabilities;
|
||||
|
||||
if (capabilities.contains(qsl("body-markup"))) {
|
||||
_body = subtitle.isEmpty()
|
||||
@@ -424,7 +457,7 @@ NotificationData::~NotificationData() {
|
||||
}
|
||||
}
|
||||
|
||||
bool NotificationData::show() {
|
||||
void NotificationData::show() {
|
||||
GVariantBuilder actionsBuilder, hintsBuilder;
|
||||
GError *error = nullptr;
|
||||
|
||||
@@ -451,14 +484,14 @@ bool NotificationData::show() {
|
||||
? GetIconName()
|
||||
: QString();
|
||||
|
||||
auto reply = g_dbus_connection_call_sync(
|
||||
g_dbus_connection_call(
|
||||
_dbusConnection,
|
||||
kService.utf8(),
|
||||
kObjectPath.utf8(),
|
||||
kInterface.utf8(),
|
||||
"Notify",
|
||||
g_variant_new(
|
||||
kNotifyArgsType.utf8(),
|
||||
"(susssasa{sv}i)",
|
||||
AppName.utf8().constData(),
|
||||
0,
|
||||
iconName.toUtf8().constData(),
|
||||
@@ -469,21 +502,42 @@ bool NotificationData::show() {
|
||||
-1),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
kDBusTimeout,
|
||||
-1,
|
||||
nullptr,
|
||||
&error);
|
||||
notificationShown,
|
||||
this);
|
||||
}
|
||||
|
||||
const auto replyValid = !error;
|
||||
void NotificationData::notificationShown(
|
||||
GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data) {
|
||||
const auto notificationData = reinterpret_cast<NotificationData*>(
|
||||
user_data);
|
||||
|
||||
if (replyValid) {
|
||||
g_variant_get(reply, "(u)", &_notificationId);
|
||||
g_variant_unref(reply);
|
||||
} else {
|
||||
LOG(("Native notification error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
if (!notificationData) {
|
||||
return;
|
||||
}
|
||||
|
||||
return replyValid;
|
||||
GError *error = nullptr;
|
||||
|
||||
auto reply = g_dbus_connection_call_finish(
|
||||
notificationData->_dbusConnection,
|
||||
res,
|
||||
&error);
|
||||
|
||||
if (!error) {
|
||||
g_variant_get(reply, "(u)", ¬ificationData->_notificationId);
|
||||
g_variant_unref(reply);
|
||||
} else {
|
||||
const auto manager = notificationData->_manager;
|
||||
const auto my = notificationData->_id;
|
||||
crl::on_main(manager, [=] {
|
||||
manager->clearNotification(my);
|
||||
});
|
||||
LOG(("Native Notification Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationData::close() {
|
||||
@@ -510,7 +564,7 @@ void NotificationData::setImage(const QString &imagePath) {
|
||||
_image = QImage(imagePath).convertToFormat(QImage::Format_RGBA8888);
|
||||
|
||||
_hints.emplace(_imageKey, g_variant_new(
|
||||
kImageDataType.utf8(),
|
||||
"(iiibii@ay)",
|
||||
_image.width(),
|
||||
_image.height(),
|
||||
_image.bytesPerLine(),
|
||||
@@ -613,37 +667,84 @@ void NotificationData::notificationReplied(uint id, const QString &text) {
|
||||
} // namespace
|
||||
|
||||
bool SkipAudio() {
|
||||
if (Supported()
|
||||
&& GetCapabilities().contains(qsl("inhibitions"))
|
||||
&& !InhibitedNotSupported) {
|
||||
return Inhibited();
|
||||
}
|
||||
|
||||
return false;
|
||||
return Inhibited();
|
||||
}
|
||||
|
||||
bool SkipToast() {
|
||||
return SkipAudio();
|
||||
// Do not skip native notifications because of Do not disturb.
|
||||
// They respect this setting anyway.
|
||||
if ((Core::App().settings().nativeNotifications() && Supported())
|
||||
|| Enforced()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Inhibited();
|
||||
}
|
||||
|
||||
bool SkipFlashBounce() {
|
||||
return SkipAudio();
|
||||
return Inhibited();
|
||||
}
|
||||
|
||||
bool Supported() {
|
||||
return NotificationsSupported;
|
||||
return ServiceRegistered;
|
||||
}
|
||||
|
||||
std::unique_ptr<Window::Notifications::Manager> Create(
|
||||
Window::Notifications::System *system) {
|
||||
GetSupported();
|
||||
bool Enforced() {
|
||||
// Wayland doesn't support positioning
|
||||
// and custom notifications don't work here
|
||||
return IsQualifiedDaemon() || IsWayland();
|
||||
}
|
||||
|
||||
if ((Core::App().settings().nativeNotifications() && Supported())
|
||||
|| IsWayland()) {
|
||||
return std::make_unique<Manager>(system);
|
||||
void Create(Window::Notifications::System *system) {
|
||||
ServiceRegistered = GetServiceRegistered();
|
||||
|
||||
const auto managerSetter = [=] {
|
||||
using ManagerType = Window::Notifications::ManagerType;
|
||||
if ((Core::App().settings().nativeNotifications() && Supported())
|
||||
|| Enforced()) {
|
||||
if (*system->managerType() != ManagerType::Native) {
|
||||
system->setManager(std::make_unique<Manager>(system));
|
||||
}
|
||||
} else {
|
||||
if (*system->managerType() != ManagerType::Default) {
|
||||
system->setManager(nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!system->managerType().has_value()) {
|
||||
using DummyManager = Window::Notifications::DummyManager;
|
||||
system->setManager(std::make_unique<DummyManager>(system));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
if (ServiceRegistered) {
|
||||
const auto counter = std::make_shared<int>(3);
|
||||
const auto oneReady = [=] {
|
||||
if (!--*counter) {
|
||||
managerSetter();
|
||||
}
|
||||
};
|
||||
|
||||
GetServerInformation([=](std::optional<ServerInformation> result) {
|
||||
CurrentServerInformation = result;
|
||||
oneReady();
|
||||
});
|
||||
|
||||
GetCapabilities([=](QStringList result) {
|
||||
CurrentCapabilities = result;
|
||||
oneReady();
|
||||
});
|
||||
|
||||
GetInhibitionSupported([=](bool result) {
|
||||
InhibitionSupported = result;
|
||||
oneReady();
|
||||
});
|
||||
} else {
|
||||
CurrentServerInformation = std::nullopt;
|
||||
CurrentCapabilities = QStringList{};
|
||||
InhibitionSupported = false;
|
||||
managerSetter();
|
||||
}
|
||||
}
|
||||
|
||||
class Manager::Private {
|
||||
@@ -683,21 +784,21 @@ Manager::Private::Private(not_null<Manager*> manager, Type type)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto serverInformation = GetServerInformation();
|
||||
const auto capabilities = GetCapabilities();
|
||||
const auto serverInformation = CurrentServerInformation;
|
||||
const auto capabilities = CurrentCapabilities;
|
||||
|
||||
if (!serverInformation.empty()) {
|
||||
if (serverInformation.has_value()) {
|
||||
LOG(("Notification daemon product name: %1")
|
||||
.arg(serverInformation[0]));
|
||||
.arg(serverInformation->name));
|
||||
|
||||
LOG(("Notification daemon vendor name: %1")
|
||||
.arg(serverInformation[1]));
|
||||
.arg(serverInformation->vendor));
|
||||
|
||||
LOG(("Notification daemon version: %1")
|
||||
.arg(serverInformation[2]));
|
||||
.arg(serverInformation->version.toString()));
|
||||
|
||||
LOG(("Notification daemon specification version: %1")
|
||||
.arg(serverInformation[3]));
|
||||
.arg(serverInformation->specVersion.toString()));
|
||||
}
|
||||
|
||||
if (!capabilities.isEmpty()) {
|
||||
@@ -753,15 +854,7 @@ void Manager::Private::showNotification(
|
||||
base::flat_map<MsgId, Notification>()).first;
|
||||
}
|
||||
i->second.emplace(msgId, notification);
|
||||
if (!notification->show()) {
|
||||
i = _notifications.find(key);
|
||||
if (i != _notifications.cend()) {
|
||||
i->second.remove(msgId);
|
||||
if (i->second.empty()) {
|
||||
_notifications.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
notification->show();
|
||||
}
|
||||
|
||||
void Manager::Private::clearAll() {
|
||||
|
||||
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/notifications_manager_linux.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace Notifications {
|
||||
|
||||
@@ -27,13 +29,19 @@ bool Supported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<Window::Notifications::Manager> Create(
|
||||
Window::Notifications::System *system) {
|
||||
if (IsWayland()) {
|
||||
return std::make_unique<Window::Notifications::DummyManager>(system);
|
||||
}
|
||||
bool Enforced() {
|
||||
// Wayland doesn't support positioning
|
||||
// and custom notifications don't work here
|
||||
return IsWayland();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
void Create(Window::Notifications::System *system) {
|
||||
if (Enforced()) {
|
||||
using DummyManager = Window::Notifications::DummyManager;
|
||||
system->setManager(std::make_unique<DummyManager>(system));
|
||||
} else {
|
||||
system->setManager(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Notifications
|
||||
|
||||
@@ -7,24 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/specific_linux.h"
|
||||
|
||||
#include "platform/linux/linux_libs.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/linux/base_xcb_utilities_linux.h"
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
#include "platform/linux/linux_wayland_integration.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
#include "platform/linux/file_utilities_linux.h"
|
||||
#include "platform/linux/linux_wayland_integration.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/application.h"
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include "platform/linux/linux_notification_service_watcher.h"
|
||||
#include "platform/linux/linux_gsd_media_keys.h"
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
@@ -65,13 +62,12 @@ extern "C" {
|
||||
#include <iostream>
|
||||
|
||||
using namespace Platform;
|
||||
using Platform::File::internal::EscapeShell;
|
||||
using Platform::internal::WaylandIntegration;
|
||||
using Platform::internal::GtkIntegration;
|
||||
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDisableGtkIntegration = "TDESKTOP_DISABLE_GTK_INTEGRATION"_cs;
|
||||
constexpr auto kIgnoreGtkIncompatibility = "TDESKTOP_I_KNOW_ABOUT_GTK_INCOMPATIBILITY"_cs;
|
||||
|
||||
constexpr auto kDesktopFile = ":/misc/telegramdesktop.desktop"_cs;
|
||||
@@ -87,6 +83,8 @@ constexpr auto kXCBFrameExtentsAtomName = "_GTK_FRAME_EXTENTS"_cs;
|
||||
QStringList PlatformThemes;
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
std::unique_ptr<internal::NotificationServiceWatcher> NSWInstance;
|
||||
|
||||
QStringList ListDBusActivatableNames() {
|
||||
static const auto Result = [&] {
|
||||
const auto message = QDBusMessage::createMethodCall(
|
||||
@@ -100,8 +98,8 @@ QStringList ListDBusActivatableNames() {
|
||||
|
||||
if (reply.isValid()) {
|
||||
return reply.value();
|
||||
} else {
|
||||
LOG(("App Error: %1: %2")
|
||||
} else if (reply.error().type() != QDBusError::Disconnected) {
|
||||
LOG(("ListActivatableNames Error: %1: %2")
|
||||
.arg(reply.error().name())
|
||||
.arg(reply.error().message()));
|
||||
}
|
||||
@@ -152,13 +150,14 @@ void PortalAutostart(bool autostart, bool silent = false) {
|
||||
|
||||
if (silent) {
|
||||
QDBusConnection::sessionBus().send(message);
|
||||
} else {
|
||||
const QDBusReply<void> reply = QDBusConnection::sessionBus().call(
|
||||
message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reply.isValid()) {
|
||||
LOG(("Flatpak autostart error: %1").arg(reply.error().message()));
|
||||
}
|
||||
const QDBusError error = QDBusConnection::sessionBus().call(message);
|
||||
if (error.isValid()) {
|
||||
LOG(("Flatpak Autostart Error: %1: %2")
|
||||
.arg(error.name())
|
||||
.arg(error.message()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,11 +213,12 @@ uint FileChooserPortalVersion() {
|
||||
|
||||
if (reply.isValid()) {
|
||||
return reply.value().toUInt();
|
||||
} else {
|
||||
LOG(("Error getting FileChooser portal version: %1")
|
||||
.arg(reply.error().message()));
|
||||
}
|
||||
|
||||
LOG(("Error getting FileChooser portal version: %1: %2")
|
||||
.arg(reply.error().name())
|
||||
.arg(reply.error().message()));
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
@@ -226,6 +226,32 @@ uint FileChooserPortalVersion() {
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
QByteArray EscapeShell(const QByteArray &content) {
|
||||
auto result = QByteArray();
|
||||
|
||||
auto b = content.constData(), e = content.constEnd();
|
||||
for (auto ch = b; ch != e; ++ch) {
|
||||
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
|
||||
if (result.isEmpty()) {
|
||||
result.reserve(content.size() * 2);
|
||||
}
|
||||
if (ch > b) {
|
||||
result.append(b, ch - b);
|
||||
}
|
||||
result.append('\\');
|
||||
b = ch;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (e > b) {
|
||||
result.append(b, e - b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString EscapeShellInLauncher(const QString &content) {
|
||||
return EscapeShell(content.toUtf8()).replace('\\', "\\\\");
|
||||
}
|
||||
@@ -327,21 +353,6 @@ bool GenerateDesktopFile(
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
bool GetImageFromClipboardSupported() {
|
||||
return (Libs::gtk_clipboard_wait_for_contents != nullptr)
|
||||
&& (Libs::gtk_clipboard_wait_for_image != nullptr)
|
||||
&& (Libs::gtk_selection_data_targets_include_image != nullptr)
|
||||
&& (Libs::gtk_selection_data_free != nullptr)
|
||||
&& (Libs::gdk_pixbuf_get_pixels != nullptr)
|
||||
&& (Libs::gdk_pixbuf_get_width != nullptr)
|
||||
&& (Libs::gdk_pixbuf_get_height != nullptr)
|
||||
&& (Libs::gdk_pixbuf_get_rowstride != nullptr)
|
||||
&& (Libs::gdk_pixbuf_get_has_alpha != nullptr)
|
||||
&& (Libs::gdk_atom_intern != nullptr);
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
uint XCBMoveResizeFromEdges(Qt::Edges edges) {
|
||||
if (edges == (Qt::TopEdge | Qt::LeftEdge))
|
||||
return 0;
|
||||
@@ -559,28 +570,17 @@ bool IsStaticBinary() {
|
||||
#endif // !DESKTOP_APP_USE_PACKAGED
|
||||
}
|
||||
|
||||
bool UseGtkIntegration() {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
static const auto Result = !qEnvironmentVariableIsSet(
|
||||
kDisableGtkIntegration.utf8());
|
||||
|
||||
return Result;
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsGtkIntegrationForced() {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
static const auto Result = [&] {
|
||||
if (!GtkIntegration::Instance()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PlatformThemes.contains(qstr("gtk3"), Qt::CaseInsensitive)
|
||||
|| PlatformThemes.contains(qstr("gtk2"), Qt::CaseInsensitive);
|
||||
}();
|
||||
|
||||
return Result;
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AreQtPluginsBundled() {
|
||||
@@ -632,6 +632,17 @@ bool CanOpenDirectoryWithPortal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsNotificationServiceActivatable() {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
static const auto Result = ListDBusActivatableNames().contains(
|
||||
qsl("org.freedesktop.Notifications"));
|
||||
|
||||
return Result;
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString AppRuntimeDirectory() {
|
||||
static const auto Result = [&] {
|
||||
auto runtimeDir = QStandardPaths::writableLocation(
|
||||
@@ -707,64 +718,38 @@ QString GetIconName() {
|
||||
}
|
||||
|
||||
QImage GetImageFromClipboard() {
|
||||
QImage data;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (!GetImageFromClipboardSupported() || !Libs::GtkClipboard()) {
|
||||
return data;
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
return integration->getImageFromClipboard();
|
||||
}
|
||||
|
||||
auto gsel = Libs::gtk_clipboard_wait_for_contents(
|
||||
Libs::GtkClipboard(),
|
||||
Libs::gdk_atom_intern("TARGETS", true));
|
||||
|
||||
if (gsel) {
|
||||
if (Libs::gtk_selection_data_targets_include_image(gsel, false)) {
|
||||
auto img = Libs::gtk_clipboard_wait_for_image(
|
||||
Libs::GtkClipboard());
|
||||
|
||||
if (img) {
|
||||
data = QImage(
|
||||
Libs::gdk_pixbuf_get_pixels(img),
|
||||
Libs::gdk_pixbuf_get_width(img),
|
||||
Libs::gdk_pixbuf_get_height(img),
|
||||
Libs::gdk_pixbuf_get_rowstride(img),
|
||||
Libs::gdk_pixbuf_get_has_alpha(img)
|
||||
? QImage::Format_RGBA8888
|
||||
: QImage::Format_RGB888).copy();
|
||||
|
||||
g_object_unref(img);
|
||||
}
|
||||
}
|
||||
|
||||
Libs::gtk_selection_data_free(gsel);
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
return data;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<bool> IsDarkMode() {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (Libs::GtkSettingSupported() && Libs::GtkLoaded()) {
|
||||
if (Libs::gtk_check_version != nullptr
|
||||
&& !Libs::gtk_check_version(3, 0, 0)
|
||||
&& Libs::GtkSetting<gboolean>(
|
||||
"gtk-application-prefer-dark-theme")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto themeName = Libs::GtkSetting("gtk-theme-name").toLower();
|
||||
|
||||
if (themeName.contains(qsl("-dark"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
const auto integration = GtkIntegration::Instance();
|
||||
if (!integration) {
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
return std::nullopt;
|
||||
if (integration->checkVersion(3, 0, 0)) {
|
||||
const auto preferDarkTheme = integration->getBoolSetting(
|
||||
qsl("gtk-application-prefer-dark-theme"));
|
||||
|
||||
if (!preferDarkTheme.has_value()) {
|
||||
return std::nullopt;
|
||||
} else if (*preferDarkTheme) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto themeName = integration->getStringSetting(qsl("gtk-theme-name"));
|
||||
if (!themeName.has_value()) {
|
||||
return std::nullopt;
|
||||
} else if (themeName->toLower().contains(qsl("-dark"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AutostartSupported() {
|
||||
@@ -832,38 +817,44 @@ bool WindowsNeedShadow() {
|
||||
}
|
||||
|
||||
Window::ControlsLayout WindowControlsLayout() {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (Libs::GtkSettingSupported()
|
||||
&& Libs::GtkLoaded()
|
||||
&& Libs::gtk_check_version != nullptr
|
||||
&& !Libs::gtk_check_version(3, 12, 0)) {
|
||||
const auto decorationLayout = Libs::GtkSetting(
|
||||
"gtk-decoration-layout").split(':');
|
||||
const auto gtkResult = []() -> std::optional<Window::ControlsLayout> {
|
||||
const auto integration = GtkIntegration::Instance();
|
||||
if (!integration || !integration->checkVersion(3, 12, 0)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto decorationLayoutSetting = integration->getStringSetting(
|
||||
qsl("gtk-decoration-layout"));
|
||||
|
||||
if (!decorationLayoutSetting.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto decorationLayout = decorationLayoutSetting->split(':');
|
||||
|
||||
std::vector<Window::Control> controlsLeft;
|
||||
ranges::transform(
|
||||
decorationLayout[0].split(','),
|
||||
ranges::back_inserter(controlsLeft),
|
||||
GtkKeywordToWindowControl
|
||||
);
|
||||
GtkKeywordToWindowControl);
|
||||
|
||||
std::vector<Window::Control> controlsRight;
|
||||
if (decorationLayout.size() > 1) {
|
||||
ranges::transform(
|
||||
decorationLayout[1].split(','),
|
||||
ranges::back_inserter(controlsRight),
|
||||
GtkKeywordToWindowControl
|
||||
);
|
||||
GtkKeywordToWindowControl);
|
||||
}
|
||||
|
||||
return Window::ControlsLayout{
|
||||
.left = controlsLeft,
|
||||
.right = controlsRight
|
||||
};
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
}();
|
||||
|
||||
if (DesktopEnvironment::IsUnity()) {
|
||||
if (gtkResult.has_value()) {
|
||||
return *gtkResult;
|
||||
} else if (DesktopEnvironment::IsUnity()) {
|
||||
return Window::ControlsLayout{
|
||||
.left = {
|
||||
Window::Control::Close,
|
||||
@@ -967,7 +958,7 @@ void start() {
|
||||
|
||||
// if gtk integration and qgtk3/qgtk2 platformtheme (or qgtk2 style)
|
||||
// is used at the same time, the app will crash
|
||||
if (UseGtkIntegration()
|
||||
if (GtkIntegration::Instance()
|
||||
&& !IsStaticBinary()
|
||||
&& !qEnvironmentVariableIsSet(
|
||||
kIgnoreGtkIncompatibility.utf8())) {
|
||||
@@ -988,7 +979,7 @@ void start() {
|
||||
"Keep in mind that this will lead to clipboard issues "
|
||||
"and tdesktop will be unable to get settings from GTK "
|
||||
"(such as decoration layout, dark mode & more).",
|
||||
kDisableGtkIntegration.utf8().constData());
|
||||
internal::kDisableGtkIntegration.utf8().constData());
|
||||
|
||||
qunsetenv("QT_QPA_PLATFORMTHEME");
|
||||
qunsetenv("QT_STYLE_OVERRIDE");
|
||||
@@ -999,7 +990,7 @@ void start() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!UseGtkIntegration()) {
|
||||
if (!GtkIntegration::Instance()) {
|
||||
g_warning(
|
||||
"GTK integration was disabled on build or in runtime. "
|
||||
"This will lead to clipboard issues and a lack of some features "
|
||||
@@ -1224,16 +1215,24 @@ void start() {
|
||||
DEBUG_LOG(("Icon theme: %1").arg(QIcon::themeName()));
|
||||
DEBUG_LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName()));
|
||||
|
||||
Libs::start();
|
||||
MainWindow::LibsLoaded();
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
integration->load();
|
||||
}
|
||||
|
||||
// wait for interface announce to know if native window frame is supported
|
||||
if (const auto waylandIntegration = WaylandIntegration::Instance()) {
|
||||
waylandIntegration->waitForInterfaceAnnounce();
|
||||
}
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
NSWInstance = std::make_unique<internal::NotificationServiceWatcher>();
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
void finish() {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
NSWInstance = nullptr;
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
} // namespace ThirdParty
|
||||
|
||||
@@ -9,8 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/platform_specific.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
namespace Data {
|
||||
class LocationPoint;
|
||||
} // namespace Data
|
||||
@@ -21,10 +19,10 @@ bool InFlatpak();
|
||||
bool InSnap();
|
||||
bool IsStaticBinary();
|
||||
bool AreQtPluginsBundled();
|
||||
bool UseGtkIntegration();
|
||||
bool IsGtkIntegrationForced();
|
||||
bool UseXDGDesktopPortal();
|
||||
bool CanOpenDirectoryWithPortal();
|
||||
bool IsNotificationServiceActivatable();
|
||||
|
||||
QString AppRuntimeDirectory();
|
||||
QString GetLauncherBasename();
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_controller.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "platform/mac/touchbar/mac_touchbar_manager.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
@@ -692,7 +693,9 @@ void MainWindow::createGlobalMenu() {
|
||||
about->setMenuRole(QAction::AboutQtRole);
|
||||
|
||||
main->addSeparator();
|
||||
QAction *prefs = main->addAction(tr::lng_mac_menu_preferences(tr::now), App::wnd(), SLOT(showSettings()), QKeySequence(Qt::ControlModifier | Qt::Key_Comma));
|
||||
QAction *prefs = main->addAction(tr::lng_mac_menu_preferences(tr::now), App::wnd(), [=] {
|
||||
App::wnd()->showSettings();
|
||||
}, QKeySequence(Qt::ControlModifier | Qt::Key_Comma));
|
||||
prefs->setMenuRole(QAction::PreferencesRole);
|
||||
|
||||
QMenu *file = psMainMenu.addMenu(tr::lng_mac_menu_file(tr::now));
|
||||
@@ -728,19 +731,27 @@ void MainWindow::createGlobalMenu() {
|
||||
psContacts = window->addAction(tr::lng_mac_menu_contacts(tr::now));
|
||||
connect(psContacts, &QAction::triggered, psContacts, crl::guard(this, [=] {
|
||||
if (isHidden()) {
|
||||
App::wnd()->showFromTray();
|
||||
showFromTray();
|
||||
}
|
||||
if (!sessionController()) {
|
||||
return;
|
||||
}
|
||||
Ui::show(PrepareContactsBox(sessionController()));
|
||||
}));
|
||||
psAddContact = window->addAction(tr::lng_mac_menu_add_contact(tr::now), App::wnd(), SLOT(onShowAddContact()));
|
||||
psAddContact = window->addAction(tr::lng_mac_menu_add_contact(tr::now), App::wnd(), [=] {
|
||||
App::wnd()->showAddContact();
|
||||
});
|
||||
window->addSeparator();
|
||||
psNewGroup = window->addAction(tr::lng_mac_menu_new_group(tr::now), App::wnd(), SLOT(onShowNewGroup()));
|
||||
psNewChannel = window->addAction(tr::lng_mac_menu_new_channel(tr::now), App::wnd(), SLOT(onShowNewChannel()));
|
||||
psNewGroup = window->addAction(tr::lng_mac_menu_new_group(tr::now), App::wnd(), [=] {
|
||||
App::wnd()->showNewGroup();
|
||||
});
|
||||
psNewChannel = window->addAction(tr::lng_mac_menu_new_channel(tr::now), App::wnd(), [=] {
|
||||
App::wnd()->showNewChannel();
|
||||
});
|
||||
window->addSeparator();
|
||||
psShowTelegram = window->addAction(tr::lng_mac_menu_show(tr::now), App::wnd(), SLOT(showFromTray()));
|
||||
psShowTelegram = window->addAction(tr::lng_mac_menu_show(tr::now), App::wnd(), [=] {
|
||||
showFromTray();
|
||||
});
|
||||
|
||||
updateGlobalMenu();
|
||||
}
|
||||
|
||||
@@ -162,11 +162,16 @@ bool Supported() {
|
||||
return Platform::IsMac10_8OrGreater();
|
||||
}
|
||||
|
||||
std::unique_ptr<Window::Notifications::Manager> Create(Window::Notifications::System *system) {
|
||||
bool Enforced() {
|
||||
return Supported();
|
||||
}
|
||||
|
||||
void Create(Window::Notifications::System *system) {
|
||||
if (Supported()) {
|
||||
return std::make_unique<Manager>(system);
|
||||
system->setManager(std::make_unique<Manager>(system));
|
||||
} else {
|
||||
system->setManager(nullptr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class Manager::Private : public QObject, private base::Subscriber {
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/timer.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "platform/platform_specific.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace Notifications {
|
||||
[[nodiscard]] bool SkipFlashBounce();
|
||||
|
||||
[[nodiscard]] bool Supported();
|
||||
[[nodiscard]] std::unique_ptr<Window::Notifications::Manager> Create(
|
||||
Window::Notifications::System *system);
|
||||
[[nodiscard]] bool Enforced();
|
||||
void Create(Window::Notifications::System *system);
|
||||
|
||||
} // namespace Notifications
|
||||
} // namespace Platform
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/win/main_window_win.h"
|
||||
|
||||
#include "styles/style_window.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "platform/win/windows_dlls.h"
|
||||
#include "platform/win/windows_event_filter.h"
|
||||
@@ -47,6 +48,13 @@ Q_DECLARE_METATYPE(QMargins);
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
// Mouse down on tray icon deactivates the application.
|
||||
// So there is no way to know for sure if the tray icon was clicked from
|
||||
// active application or from inactive application. So we assume that
|
||||
// if the application was deactivated less than 0.5s ago, then the tray
|
||||
// icon click (both left or right button) was made from the active app.
|
||||
constexpr auto kKeepActiveForTrayIcon = crl::time(500);
|
||||
|
||||
HICON createHIconFromQIcon(const QIcon &icon, int xSize, int ySize) {
|
||||
if (!icon.isNull()) {
|
||||
const QPixmap pm = icon.pixmap(icon.actualSize(QSize(xSize, ySize)));
|
||||
@@ -113,6 +121,13 @@ MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
}
|
||||
});
|
||||
setupNativeWindowFrame();
|
||||
|
||||
using namespace rpl::mappers;
|
||||
Core::App().appDeactivatedValue(
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::filter(_1) | rpl::start_with_next([=] {
|
||||
_lastDeactivateTime = crl::now();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void MainWindow::setupNativeWindowFrame() {
|
||||
@@ -280,6 +295,11 @@ void MainWindow::updateWindowIcon() {
|
||||
updateIconCounters();
|
||||
}
|
||||
|
||||
bool MainWindow::isActiveForTrayMenu() {
|
||||
return !_lastDeactivateTime
|
||||
|| (_lastDeactivateTime + kKeepActiveForTrayIcon >= crl::now());
|
||||
}
|
||||
|
||||
void MainWindow::unreadCounterChangedHook() {
|
||||
setWindowTitle(titleText());
|
||||
updateIconCounters();
|
||||
@@ -604,6 +624,17 @@ void MainWindow::fixMaximizedWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
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 ps_hWnd;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ class MainWindow : public Window::MainWindow {
|
||||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> controller);
|
||||
|
||||
void showFromTrayMenu() override;
|
||||
|
||||
HWND psHwnd() const;
|
||||
HMENU psMenu() const;
|
||||
|
||||
@@ -32,6 +34,7 @@ public:
|
||||
void updateCustomMargins();
|
||||
|
||||
void updateWindowIcon() override;
|
||||
bool isActiveForTrayMenu() override;
|
||||
|
||||
void psRefreshTaskbarIcon();
|
||||
|
||||
@@ -103,6 +106,10 @@ private:
|
||||
bool _wasNativeFrame = false;
|
||||
bool _hasActiveFrame = false;
|
||||
|
||||
// Workarounds for activation from tray icon.
|
||||
crl::time _lastDeactivateTime = 0;
|
||||
rpl::lifetime _showFromTrayLifetime;
|
||||
|
||||
HWND ps_hWnd = nullptr;
|
||||
HWND ps_tbHider_hWnd = nullptr;
|
||||
HMENU ps_menu = nullptr;
|
||||
|
||||
@@ -325,16 +325,21 @@ bool Supported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<Window::Notifications::Manager> Create(Window::Notifications::System *system) {
|
||||
bool Enforced() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Create(Window::Notifications::System *system) {
|
||||
#ifndef __MINGW32__
|
||||
if (Core::App().settings().nativeNotifications() && Supported()) {
|
||||
auto result = std::make_unique<Manager>(system);
|
||||
if (result->init()) {
|
||||
return std::move(result);
|
||||
system->setManager(std::move(result));
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif // !__MINGW32__
|
||||
return nullptr;
|
||||
system->setManager(nullptr);
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
|
||||
@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_file_origin.h"
|
||||
#include "chat_helpers/emoji_sets_manager.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "support/support_common.h"
|
||||
#include "support/support_templates.h"
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -676,14 +677,13 @@ void SetupNotificationsContent(
|
||||
}, joined->lifetime());
|
||||
|
||||
const auto nativeText = [&] {
|
||||
if (!Platform::Notifications::Supported()) {
|
||||
if (!Platform::Notifications::Supported()
|
||||
|| Platform::Notifications::Enforced()) {
|
||||
return QString();
|
||||
} else if (Platform::IsWindows()) {
|
||||
return tr::lng_settings_use_windows(tr::now);
|
||||
} else if (Platform::IsLinux() && !Platform::IsWayland()) {
|
||||
return tr::lng_settings_use_native_notifications(tr::now);
|
||||
}
|
||||
return QString();
|
||||
return tr::lng_settings_use_native_notifications(tr::now);
|
||||
}();
|
||||
const auto native = [&]() -> Ui::Checkbox* {
|
||||
if (nativeText.isEmpty()) {
|
||||
@@ -697,8 +697,7 @@ void SetupNotificationsContent(
|
||||
return addCheckbox(nativeText, settings.nativeNotifications());
|
||||
}();
|
||||
|
||||
const auto advancedSlide = !Platform::IsMac10_8OrGreater()
|
||||
&& !Platform::IsWayland()
|
||||
const auto advancedSlide = !Platform::Notifications::Enforced()
|
||||
? container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
|
||||
@@ -171,6 +171,10 @@ void DownloadManagerMtproto::checkSendNext(MTP::DcId dcId, Queue &queue) {
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadManagerMtproto::checkSendNextAfterSuccess(MTP::DcId dcId) {
|
||||
checkSendNext(dcId, _queues[dcId]);
|
||||
}
|
||||
|
||||
bool DownloadManagerMtproto::trySendNextPart(MTP::DcId dcId, Queue &queue) {
|
||||
auto &balanceData = _balanceData[dcId];
|
||||
const auto &sessions = balanceData.sessions;
|
||||
@@ -227,10 +231,6 @@ void DownloadManagerMtproto::requestSucceeded(
|
||||
crl::time timeAtRequestStart) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto guard = gsl::finally([&] {
|
||||
checkSendNext(dcId, _queues[dcId]);
|
||||
});
|
||||
|
||||
const auto i = _balanceData.find(dcId);
|
||||
Assert(i != end(_balanceData));
|
||||
auto &dc = i->second;
|
||||
@@ -606,24 +606,34 @@ void DownloadMtprotoTask::normalPartLoaded(
|
||||
const auto requestData = finishSentRequest(
|
||||
requestId,
|
||||
FinishRequestReason::Success);
|
||||
const auto owner = _owner;
|
||||
const auto dcId = this->dcId();
|
||||
result.match([&](const MTPDupload_fileCdnRedirect &data) {
|
||||
switchToCDN(requestData, data);
|
||||
}, [&](const MTPDupload_file &data) {
|
||||
partLoaded(requestData.offset, data.vbytes().v);
|
||||
});
|
||||
|
||||
// 'this' may be deleted at this point.
|
||||
owner->checkSendNextAfterSuccess(dcId);
|
||||
}
|
||||
|
||||
void DownloadMtprotoTask::webPartLoaded(
|
||||
const MTPupload_WebFile &result,
|
||||
mtpRequestId requestId) {
|
||||
const auto requestData = finishSentRequest(
|
||||
requestId,
|
||||
FinishRequestReason::Success);
|
||||
const auto owner = _owner;
|
||||
const auto dcId = this->dcId();
|
||||
result.match([&](const MTPDupload_webFile &data) {
|
||||
const auto requestData = finishSentRequest(
|
||||
requestId,
|
||||
FinishRequestReason::Success);
|
||||
if (setWebFileSizeHook(data.vsize().v)) {
|
||||
partLoaded(requestData.offset, data.vbytes().v);
|
||||
}
|
||||
});
|
||||
|
||||
// 'this' may be deleted at this point.
|
||||
owner->checkSendNextAfterSuccess(dcId);
|
||||
}
|
||||
|
||||
void DownloadMtprotoTask::cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId) {
|
||||
@@ -647,6 +657,13 @@ void DownloadMtprotoTask::cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequ
|
||||
const auto requestData = finishSentRequest(
|
||||
requestId,
|
||||
FinishRequestReason::Success);
|
||||
const auto owner = _owner;
|
||||
const auto dcId = this->dcId();
|
||||
const auto guard = gsl::finally([=] {
|
||||
// 'this' may be deleted at this point.
|
||||
owner->checkSendNextAfterSuccess(dcId);
|
||||
});
|
||||
|
||||
auto key = bytes::make_span(_cdnEncryptionKey);
|
||||
auto iv = bytes::make_span(_cdnEncryptionIV);
|
||||
Expects(key.size() == MTP::CTRState::KeySize);
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
int index,
|
||||
int amountAtRequestStart,
|
||||
crl::time timeAtRequestStart);
|
||||
void checkSendNextAfterSuccess(MTP::DcId dcId);
|
||||
[[nodiscard]] int chooseSessionIndex(MTP::DcId dcId) const;
|
||||
|
||||
private:
|
||||
|
||||
151
Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp
Normal file
151
Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
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 "storage/storage_cloud_song_cover.h"
|
||||
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/file_download.h"
|
||||
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
|
||||
namespace Storage::CloudSongCover {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxResponseSize = 1024 * 1024;
|
||||
constexpr auto kDefaultCoverSize = 100;
|
||||
|
||||
struct Responce {
|
||||
const QString artworkUrl;
|
||||
const int size;
|
||||
};
|
||||
|
||||
auto Location(const QString &url) {
|
||||
return DownloadLocation{ PlainUrlLocation{ url } };
|
||||
}
|
||||
|
||||
auto JsonUrl(not_null<SongData*> song) {
|
||||
return QString("https://itunes.apple.com/search?term=" \
|
||||
"%1 %2&entity=song&limit=4").arg(song->performer).arg(song->title);
|
||||
}
|
||||
|
||||
// Dummy JSON responce.
|
||||
// {
|
||||
// "resultCount": 2,
|
||||
// "results": [
|
||||
// {
|
||||
// "artworkUrl100": ""
|
||||
// },
|
||||
// {
|
||||
// "artworkUrl100": ""
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
std::optional<Responce> ParseResponce(const QByteArray &response) {
|
||||
if (response.size() >= kMaxResponseSize) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(response, &error);
|
||||
|
||||
const auto log = [](const QString &message) {
|
||||
DEBUG_LOG(("Parse Artwork JSON Error: %1.").arg(message));
|
||||
};
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
log(error.errorString());
|
||||
return std::nullopt;
|
||||
} else if (!document.isObject()) {
|
||||
log("not an object received in JSON");
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto results = document.object().value("results");
|
||||
if (!results.isArray()) {
|
||||
log("'results' field not found");
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto resultsArray = results.toArray();
|
||||
if (resultsArray.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto artworkUrl = resultsArray.first().toObject()
|
||||
.value("artworkUrl100").toString();
|
||||
if (artworkUrl.isEmpty()) {
|
||||
log("'artworkUrl100' field is empty");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Responce{ artworkUrl, kDefaultCoverSize };
|
||||
}
|
||||
|
||||
void LoadAndApplyThumbnail(
|
||||
not_null<DocumentData*> document,
|
||||
const Responce &responce) {
|
||||
const auto size = responce.size;
|
||||
const auto imageWithLocation = ImageWithLocation{
|
||||
.location = ImageLocation(Location(responce.artworkUrl), size, size)
|
||||
};
|
||||
|
||||
document->updateThumbnails(
|
||||
QByteArray(),
|
||||
imageWithLocation,
|
||||
ImageWithLocation{ .location = ImageLocation() });
|
||||
|
||||
document->loadThumbnail(Data::FileOrigin());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LoadThumbnailFromExternal(not_null<DocumentData*> document) {
|
||||
const auto songData = document->song();
|
||||
if (!songData
|
||||
|| songData->performer.isEmpty()
|
||||
|| songData->title.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &size = kDefaultCoverSize;
|
||||
const auto jsonLocation = ImageWithLocation{
|
||||
.location = ImageLocation(Location(JsonUrl(songData)), size, size)
|
||||
};
|
||||
|
||||
const auto jsonCloudFile = std::make_shared<Data::CloudFile>();
|
||||
Data::UpdateCloudFile(
|
||||
*jsonCloudFile,
|
||||
jsonLocation,
|
||||
document->owner().cache(),
|
||||
0, // Cache tag.
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
auto done = [=](const QByteArray &result) {
|
||||
if (!jsonCloudFile) {
|
||||
return;
|
||||
}
|
||||
if (const auto responce = ParseResponce(result)) {
|
||||
LoadAndApplyThumbnail(document, *responce);
|
||||
}
|
||||
};
|
||||
Data::LoadCloudFile(
|
||||
&document->session(),
|
||||
*jsonCloudFile,
|
||||
Data::FileOrigin(),
|
||||
LoadFromCloudOrLocal,
|
||||
true,
|
||||
0,
|
||||
[] { return true; },
|
||||
std::move(done));
|
||||
}
|
||||
|
||||
} // namespace Storage::CloudSongCover
|
||||
16
Telegram/SourceFiles/storage/storage_cloud_song_cover.h
Normal file
16
Telegram/SourceFiles/storage/storage_cloud_song_cover.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
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
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Storage::CloudSongCover {
|
||||
|
||||
void LoadThumbnailFromExternal(not_null<DocumentData*> document);
|
||||
|
||||
} // namespace Storage::CloudSongCover
|
||||
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
|
||||
#include "ui/chat/attach/attach_send_files_way.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "core/mime_type.h"
|
||||
|
||||
namespace Ui {
|
||||
@@ -150,6 +152,25 @@ bool PreparedList::canAddCaption(bool sendingAlbum) const {
|
||||
} else if (!sendingAlbum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All music.
|
||||
{
|
||||
auto pred = [](const PreparedFile &file) {
|
||||
return file.type == PreparedFile::Type::Music;
|
||||
};
|
||||
if (ranges::all_of(files, std::move(pred))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// All files.
|
||||
{
|
||||
auto pred = [](const PreparedFile &file) {
|
||||
return file.type == PreparedFile::Type::File;
|
||||
};
|
||||
if (ranges::all_of(files, std::move(pred))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto hasFiles = ranges::contains(
|
||||
files,
|
||||
PreparedFile::Type::File,
|
||||
@@ -255,4 +276,20 @@ std::vector<PreparedGroup> DivideByGroups(
|
||||
return result;
|
||||
}
|
||||
|
||||
QPixmap PrepareSongCoverForThumbnail(QImage image, int size) {
|
||||
const auto scaledSize = image.size().scaled(
|
||||
size,
|
||||
size,
|
||||
Qt::KeepAspectRatioByExpanding);
|
||||
using Option = Images::Option;
|
||||
return PixmapFromImage(Images::prepare(
|
||||
std::move(image),
|
||||
scaledSize.width() * style::DevicePixelRatio(),
|
||||
scaledSize.height() * style::DevicePixelRatio(),
|
||||
Option::Circled | Option::Colored | Option::Smooth,
|
||||
size,
|
||||
size,
|
||||
&st::songCoverOverlayFg));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -120,7 +120,7 @@ struct PreparedGroup {
|
||||
|
||||
[[nodiscard]] bool sentWithCaption() const {
|
||||
return (list.files.size() == 1)
|
||||
|| (type == AlbumType::PhotoVideo);
|
||||
|| (type != AlbumType::None);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,4 +132,6 @@ struct PreparedGroup {
|
||||
[[nodiscard]] int MaxAlbumItems();
|
||||
[[nodiscard]] bool ValidateThumbDimensions(int width, int height);
|
||||
|
||||
[[nodiscard]] QPixmap PrepareSongCoverForThumbnail(QImage image, int size);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -32,7 +32,7 @@ SingleFilePreview::SingleFilePreview(
|
||||
_editMedia->setIconOverride(&st::sendBoxAlbumGroupEditButtonIconFile);
|
||||
_deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile);
|
||||
|
||||
const auto &st = _fileThumb.isNull()
|
||||
const auto &st = !isThumbedLayout()
|
||||
? st::attachPreviewLayout
|
||||
: st::attachPreviewThumbLayout;
|
||||
resize(width(), st.thumbSize);
|
||||
@@ -106,13 +106,19 @@ void SingleFilePreview::preparePreview(const PreparedFile &file) {
|
||||
songTitle = song->title;
|
||||
songPerformer = song->performer;
|
||||
_fileIsAudio = true;
|
||||
|
||||
if (auto cover = song->cover; !cover.isNull()) {
|
||||
_fileThumb = Ui::PrepareSongCoverForThumbnail(
|
||||
cover,
|
||||
st::attachPreviewLayout.thumbSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_name = ComposeNameString(filename, songTitle, songPerformer);
|
||||
_statusText = FormatSizeText(fileinfo.size());
|
||||
}
|
||||
const auto &st = _fileThumb.isNull()
|
||||
const auto &st = !isThumbedLayout()
|
||||
? st::attachPreviewLayout
|
||||
: st::attachPreviewThumbLayout;
|
||||
const auto nameleft = st.thumbSize + st.padding.right();
|
||||
@@ -141,7 +147,7 @@ void SingleFilePreview::paintEvent(QPaintEvent *e) {
|
||||
|
||||
auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
auto h = height();
|
||||
const auto &st = _fileThumb.isNull()
|
||||
const auto &st = !isThumbedLayout()
|
||||
? st::attachPreviewLayout
|
||||
: st::attachPreviewThumbLayout;
|
||||
const auto nameleft = st.thumbSize + st.padding.right();
|
||||
@@ -149,12 +155,14 @@ void SingleFilePreview::paintEvent(QPaintEvent *e) {
|
||||
const auto statustop = st.statusTop;
|
||||
const auto x = (width() - w) / 2, y = 0;
|
||||
|
||||
if (_fileThumb.isNull()) {
|
||||
if (!isThumbedLayout()) {
|
||||
QRect inner(style::rtlrect(x, y, st.thumbSize, st.thumbSize, width()));
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgFileInBg);
|
||||
|
||||
{
|
||||
if (_fileIsAudio && !_fileThumb.isNull()) {
|
||||
p.drawPixmap(inner.topLeft(), _fileThumb);
|
||||
} else {
|
||||
p.setBrush(st::msgFileInBg);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
@@ -188,4 +196,8 @@ void SingleFilePreview::resizeEvent(QResizeEvent *e) {
|
||||
_editMedia->moveToRight(right, top);
|
||||
}
|
||||
|
||||
bool SingleFilePreview::isThumbedLayout() const {
|
||||
return (!_fileThumb.isNull() && !_fileIsAudio);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -32,6 +32,8 @@ private:
|
||||
void preparePreview(const PreparedFile &file);
|
||||
void prepareThumb(const QImage &preview);
|
||||
|
||||
bool isThumbedLayout() const;
|
||||
|
||||
QPixmap _fileThumb;
|
||||
QString _name;
|
||||
QString _statusText;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user