Added initial implementation of Windows tray.
This commit is contained in:
		
							parent
							
								
									56fdc7d39a
								
							
						
					
					
						commit
						70acc7a0e3
					
				
					 4 changed files with 269 additions and 1 deletions
				
			
		| 
						 | 
					@ -7,9 +7,237 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
#include "platform/win/tray_win.h"
 | 
					#include "platform/win/tray_win.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "base/invoke_queued.h"
 | 
				
			||||||
 | 
					#include "base/qt_signal_producer.h"
 | 
				
			||||||
 | 
					#include "core/application.h"
 | 
				
			||||||
 | 
					#include "main/main_session.h"
 | 
				
			||||||
 | 
					#include "storage/localstorage.h"
 | 
				
			||||||
 | 
					#include "ui/ui_utility.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/popup_menu.h"
 | 
				
			||||||
 | 
					#include "window/window_controller.h"
 | 
				
			||||||
 | 
					#include "window/window_session_controller.h"
 | 
				
			||||||
 | 
					#include "styles/style_window.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QtWidgets/QSystemTrayIcon>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Platform {
 | 
					namespace Platform {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					constexpr auto kTooltipDelay = crl::time(10000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[nodiscard]] QImage IconWithCounter(
 | 
				
			||||||
 | 
							Window::CounterLayerArgs &&args,
 | 
				
			||||||
 | 
							bool supportMode,
 | 
				
			||||||
 | 
							bool smallIcon) {
 | 
				
			||||||
 | 
						static constexpr auto kCount = 3;
 | 
				
			||||||
 | 
						static auto ScaledLogo = std::array<QImage, kCount>();
 | 
				
			||||||
 | 
						static auto ScaledLogoNoMargin = std::array<QImage, kCount>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct Dimensions {
 | 
				
			||||||
 | 
							int index = 0;
 | 
				
			||||||
 | 
							int size = 0;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto d = [&]() -> Dimensions {
 | 
				
			||||||
 | 
							switch (args.size) {
 | 
				
			||||||
 | 
							case 16:
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									.index = 0,
 | 
				
			||||||
 | 
									.size = 16,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							case 32:
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									.index = 1,
 | 
				
			||||||
 | 
									.size = 32,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									.index = 2,
 | 
				
			||||||
 | 
									.size = 64,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}();
 | 
				
			||||||
 | 
						Assert(d.index < kCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto &scaled = smallIcon ? ScaledLogoNoMargin : ScaledLogo;
 | 
				
			||||||
 | 
						auto result = [&] {
 | 
				
			||||||
 | 
							auto &image = scaled[d.index];
 | 
				
			||||||
 | 
							if (image.isNull()) {
 | 
				
			||||||
 | 
								image = (smallIcon
 | 
				
			||||||
 | 
									? Window::LogoNoMargin()
 | 
				
			||||||
 | 
									: Window::Logo()).scaledToWidth(
 | 
				
			||||||
 | 
										d.size,
 | 
				
			||||||
 | 
										Qt::SmoothTransformation);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return image;
 | 
				
			||||||
 | 
						}();
 | 
				
			||||||
 | 
						if (supportMode) {
 | 
				
			||||||
 | 
							Window::ConvertIconToBlack(result);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (!args.count) {
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						} else if (smallIcon) {
 | 
				
			||||||
 | 
							return Window::WithSmallCounter(std::move(result), std::move(args));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						QPainter p(&result);
 | 
				
			||||||
 | 
						const auto half = d.size / 2;
 | 
				
			||||||
 | 
						args.size = half;
 | 
				
			||||||
 | 
						p.drawPixmap(
 | 
				
			||||||
 | 
							half,
 | 
				
			||||||
 | 
							half,
 | 
				
			||||||
 | 
							Ui::PixmapFromImage(Window::GenerateCounterLayer(std::move(args))));
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[nodiscard]] QWidget *Parent() {
 | 
				
			||||||
 | 
						Expects(Core::App().primaryWindow() != nullptr);
 | 
				
			||||||
 | 
						return Core::App().primaryWindow()->widget();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Tray::Tray() {
 | 
					Tray::Tray() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Tray::createIcon() {
 | 
				
			||||||
 | 
						if (!_icon) {
 | 
				
			||||||
 | 
							_icon = base::make_unique_q<QSystemTrayIcon>(Parent());
 | 
				
			||||||
 | 
							updateIcon();
 | 
				
			||||||
 | 
							_icon->setToolTip(AppName.utf16());
 | 
				
			||||||
 | 
							using Reason = QSystemTrayIcon::ActivationReason;
 | 
				
			||||||
 | 
							base::qt_signal_producer(
 | 
				
			||||||
 | 
								_icon.get(),
 | 
				
			||||||
 | 
								&QSystemTrayIcon::activated
 | 
				
			||||||
 | 
							) | rpl::start_with_next([=](Reason reason) {
 | 
				
			||||||
 | 
								if (reason == QSystemTrayIcon::Context && _menu) {
 | 
				
			||||||
 | 
									_aboutToShowRequests.fire({});
 | 
				
			||||||
 | 
									InvokeQueued(_menu.get(), [=] {
 | 
				
			||||||
 | 
										_menu->popup(QCursor::pos());
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									_iconClicks.fire({});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}, _lifetime);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							updateIcon();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_icon->show();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Tray::destroyIcon() {
 | 
				
			||||||
 | 
						_icon = nullptr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Tray::updateIcon() {
 | 
				
			||||||
 | 
						if (!_icon) {
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const auto counter = Core::App().unreadBadge();
 | 
				
			||||||
 | 
						const auto muted = Core::App().unreadBadgeMuted();
 | 
				
			||||||
 | 
						const auto controller = Core::App().primaryWindow();
 | 
				
			||||||
 | 
						const auto session = !controller
 | 
				
			||||||
 | 
							? nullptr
 | 
				
			||||||
 | 
							: !controller->sessionController()
 | 
				
			||||||
 | 
							? nullptr
 | 
				
			||||||
 | 
							: &controller->sessionController()->session();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto iconSizeSmall = QSize(
 | 
				
			||||||
 | 
							GetSystemMetrics(SM_CXSMICON),
 | 
				
			||||||
 | 
							GetSystemMetrics(SM_CYSMICON));
 | 
				
			||||||
 | 
						const auto iconSizeBig = QSize(
 | 
				
			||||||
 | 
							GetSystemMetrics(SM_CXICON),
 | 
				
			||||||
 | 
							GetSystemMetrics(SM_CYICON));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto &bg = muted ? st::trayCounterBgMute : st::trayCounterBg;
 | 
				
			||||||
 | 
						const auto &fg = st::trayCounterFg;
 | 
				
			||||||
 | 
						const auto counterArgs = [&](int size, int counter) {
 | 
				
			||||||
 | 
							return Window::CounterLayerArgs{
 | 
				
			||||||
 | 
								.size = size,
 | 
				
			||||||
 | 
								.count = counter,
 | 
				
			||||||
 | 
								.bg = bg,
 | 
				
			||||||
 | 
								.fg = fg,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto iconWithCounter = [&](int size, int counter, bool smallIcon) {
 | 
				
			||||||
 | 
							return Ui::PixmapFromImage(IconWithCounter(
 | 
				
			||||||
 | 
								counterArgs(size, counter),
 | 
				
			||||||
 | 
								session && session->supportMode(),
 | 
				
			||||||
 | 
								smallIcon));
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto iconSmallPixmap16 = iconWithCounter(16, counter, true);
 | 
				
			||||||
 | 
						auto iconSmallPixmap32 = iconWithCounter(32, counter, true);
 | 
				
			||||||
 | 
						auto iconSmall = QIcon();
 | 
				
			||||||
 | 
						iconSmall.addPixmap(iconSmallPixmap16);
 | 
				
			||||||
 | 
						iconSmall.addPixmap(iconSmallPixmap32);
 | 
				
			||||||
 | 
						// Force Qt to use right icon size, not the larger one.
 | 
				
			||||||
 | 
						QIcon forTrayIcon;
 | 
				
			||||||
 | 
						forTrayIcon.addPixmap(iconSizeSmall.width() >= 20
 | 
				
			||||||
 | 
							? iconSmallPixmap32
 | 
				
			||||||
 | 
							: iconSmallPixmap16);
 | 
				
			||||||
 | 
						_icon->setIcon(forTrayIcon);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Tray::createMenu() {
 | 
				
			||||||
 | 
						if (!_menu) {
 | 
				
			||||||
 | 
							_menu = base::make_unique_q<Ui::PopupMenu>(nullptr);
 | 
				
			||||||
 | 
							_menu->deleteOnHide(false);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Tray::destroyMenu() {
 | 
				
			||||||
 | 
						_menu = nullptr;
 | 
				
			||||||
 | 
						_actionsLifetime.destroy();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
 | 
				
			||||||
 | 
						if (!_menu) {
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto action = _menu->addAction(QString(), std::move(callback));
 | 
				
			||||||
 | 
						std::move(
 | 
				
			||||||
 | 
							text
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](const QString &text) {
 | 
				
			||||||
 | 
							action->setText(text);
 | 
				
			||||||
 | 
						}, _actionsLifetime);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Tray::showTrayMessage() const {
 | 
				
			||||||
 | 
						if (!cSeenTrayTooltip() && _icon) {
 | 
				
			||||||
 | 
							_icon->showMessage(
 | 
				
			||||||
 | 
								AppName.utf16(),
 | 
				
			||||||
 | 
								tr::lng_tray_icon_text(tr::now),
 | 
				
			||||||
 | 
								QSystemTrayIcon::Information,
 | 
				
			||||||
 | 
								kTooltipDelay);
 | 
				
			||||||
 | 
							cSetSeenTrayTooltip(true);
 | 
				
			||||||
 | 
							Local::writeSettings();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Tray::hasTrayMessageSupport() const {
 | 
				
			||||||
 | 
						return !cSeenTrayTooltip();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<> Tray::aboutToShowRequests() const {
 | 
				
			||||||
 | 
						return _aboutToShowRequests.events();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<> Tray::showFromTrayRequests() const {
 | 
				
			||||||
 | 
						return rpl::never<>();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<> Tray::hideToTrayRequests() const {
 | 
				
			||||||
 | 
						return rpl::never<>();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<> Tray::iconClicks() const {
 | 
				
			||||||
 | 
						return _iconClicks.events();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::lifetime &Tray::lifetime() {
 | 
				
			||||||
 | 
						return _lifetime;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace Platform
 | 
					} // namespace Platform
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "platform/platform_tray.h"
 | 
					#include "platform/platform_tray.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "base/unique_qptr.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Ui {
 | 
				
			||||||
 | 
					class PopupMenu;
 | 
				
			||||||
 | 
					} // namespace Ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QSystemTrayIcon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Platform {
 | 
					namespace Platform {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Tray final {
 | 
					class Tray final {
 | 
				
			||||||
| 
						 | 
					@ -36,6 +44,14 @@ public:
 | 
				
			||||||
	[[nodiscard]] rpl::lifetime &lifetime();
 | 
						[[nodiscard]] rpl::lifetime &lifetime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
 | 
						base::unique_qptr<QSystemTrayIcon> _icon;
 | 
				
			||||||
 | 
						base::unique_qptr<Ui::PopupMenu> _menu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rpl::event_stream<> _iconClicks;
 | 
				
			||||||
 | 
						rpl::event_stream<> _aboutToShowRequests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rpl::lifetime _actionsLifetime;
 | 
				
			||||||
 | 
						rpl::lifetime _lifetime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "core/application.h"
 | 
					#include "core/application.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QtWidgets/QApplication>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Core {
 | 
					namespace Core {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Tray::Tray() {
 | 
					Tray::Tray() {
 | 
				
			||||||
| 
						 | 
					@ -40,6 +42,18 @@ void Tray::create() {
 | 
				
			||||||
	) | rpl::start_with_next([=] {
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
		rebuildMenu();
 | 
							rebuildMenu();
 | 
				
			||||||
	}, _tray.lifetime());
 | 
						}, _tray.lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_tray.iconClicks(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
 | 
							const auto skipTrayClick = (_lastTrayClickTime > 0)
 | 
				
			||||||
 | 
								&& (crl::now() - _lastTrayClickTime
 | 
				
			||||||
 | 
									< QApplication::doubleClickInterval());
 | 
				
			||||||
 | 
							if (!skipTrayClick) {
 | 
				
			||||||
 | 
								_activeForTrayIconAction = Core::App().isActiveForTrayMenu();
 | 
				
			||||||
 | 
								_minimizeMenuItemClicks.fire({});
 | 
				
			||||||
 | 
								_lastTrayClickTime = crl::now();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}, _tray.lifetime());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Tray::rebuildMenu() {
 | 
					void Tray::rebuildMenu() {
 | 
				
			||||||
| 
						 | 
					@ -100,12 +114,20 @@ rpl::producer<> Tray::showFromTrayRequests() const {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rpl::producer<> Tray::hideToTrayRequests() const {
 | 
					rpl::producer<> Tray::hideToTrayRequests() const {
 | 
				
			||||||
	return rpl::merge(
 | 
						auto triggers = rpl::merge(
 | 
				
			||||||
		_tray.hideToTrayRequests(),
 | 
							_tray.hideToTrayRequests(),
 | 
				
			||||||
		_minimizeMenuItemClicks.events() | rpl::filter([=] {
 | 
							_minimizeMenuItemClicks.events() | rpl::filter([=] {
 | 
				
			||||||
			return _activeForTrayIconAction;
 | 
								return _activeForTrayIconAction;
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
						if (_tray.hasTrayMessageSupport()) {
 | 
				
			||||||
 | 
							return std::move(triggers) | rpl::map([=]() -> rpl::empty_value {
 | 
				
			||||||
 | 
								_tray.showTrayMessage();
 | 
				
			||||||
 | 
								return {};
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return triggers;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Tray::toggleSoundNotifications() {
 | 
					void Tray::toggleSoundNotifications() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,8 @@ private:
 | 
				
			||||||
	Platform::Tray _tray;
 | 
						Platform::Tray _tray;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool _activeForTrayIconAction = false;
 | 
						bool _activeForTrayIconAction = false;
 | 
				
			||||||
 | 
						crl::time _lastTrayClickTime = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rpl::event_stream<> _textUpdates;
 | 
						rpl::event_stream<> _textUpdates;
 | 
				
			||||||
	rpl::event_stream<> _minimizeMenuItemClicks;
 | 
						rpl::event_stream<> _minimizeMenuItemClicks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue