/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/main_window_win.h" #include "styles/style_window.h" #include "platform/platform_notifications_manager.h" #include "platform/win/windows_dlls.h" #include "platform/win/windows_event_filter.h" #include "window/notifications_manager.h" #include "mainwindow.h" #include "base/crc32hash.h" #include "core/application.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "ui/widgets/popup_menu.h" #include "window/themes/window_theme.h" #include "history/history.h" #include "app.h" #include #include #include #include #include #include #include #include #include #include #include #include HICON qt_pixmapToWinHICON(const QPixmap &); using namespace Microsoft::WRL; Q_DECLARE_METATYPE(QMargins); namespace Platform { namespace { HICON createHIconFromQIcon(const QIcon &icon, int xSize, int ySize) { if (!icon.isNull()) { const QPixmap pm = icon.pixmap(icon.actualSize(QSize(xSize, ySize))); if (!pm.isNull()) { return qt_pixmapToWinHICON(pm); } } return nullptr; } HWND createTaskbarHider() { HINSTANCE appinst = (HINSTANCE)GetModuleHandle(0); HWND hWnd = 0; QString cn = QString("TelegramTaskbarHider"); LPCWSTR _cn = (LPCWSTR)cn.utf16(); WNDCLASSEX wc; wc.cbSize = sizeof(wc); wc.style = 0; wc.lpfnWndProc = DefWindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = appinst; wc.hIcon = 0; wc.hCursor = 0; wc.hbrBackground = 0; wc.lpszMenuName = NULL; wc.lpszClassName = _cn; wc.hIconSm = 0; if (!RegisterClassEx(&wc)) { DEBUG_LOG(("Application Error: could not register taskbar hider window class, error: %1").arg(GetLastError())); return hWnd; } hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, _cn, 0, WS_POPUP, 0, 0, 0, 0, 0, 0, appinst, 0); if (!hWnd) { DEBUG_LOG(("Application Error: could not create taskbar hider window class, error: %1").arg(GetLastError())); return hWnd; } return hWnd; } ComPtr taskbarList; bool handleSessionNotification = false; } // namespace UINT MainWindow::_taskbarCreatedMsgId = 0; MainWindow::MainWindow(not_null controller) : Window::MainWindow(controller) , ps_tbHider_hWnd(createTaskbarHider()) { QCoreApplication::instance()->installNativeEventFilter( EventFilter::CreateInstance(this)); if (!_taskbarCreatedMsgId) { _taskbarCreatedMsgId = RegisterWindowMessage(L"TaskbarButtonCreated"); } subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (_shadow && update.paletteChanged()) { _shadow->setColor(st::windowShadowFg->c); } }); } void MainWindow::TaskbarCreated() { HRESULT hr = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&taskbarList)); if (!SUCCEEDED(hr)) { taskbarList.Reset(); } } void MainWindow::shadowsUpdate( Ui::Platform::WindowShadow::Changes changes, WINDOWPOS *position) { if (_shadow) { _shadow->update(changes, position); } } void MainWindow::shadowsActivate() { // _shadow->setColor(_shActive); shadowsUpdate(Ui::Platform::WindowShadow::Change::Activate); } void MainWindow::shadowsDeactivate() { // _shadow->setColor(_shInactive); } void MainWindow::psShowTrayMenu() { trayIconMenu->popup(QCursor::pos()); } int32 MainWindow::screenNameChecksum(const QString &name) const { constexpr int DeviceNameSize = base::array_size(MONITORINFOEX().szDevice); wchar_t buffer[DeviceNameSize] = { 0 }; if (name.size() < DeviceNameSize) { name.toWCharArray(buffer); } else { memcpy(buffer, name.toStdWString().data(), sizeof(buffer)); } return base::crc32(buffer, sizeof(buffer)); } void MainWindow::psRefreshTaskbarIcon() { const auto refresher = std::make_unique(this); refresher->setWindowFlags(static_cast(Qt::Tool) | Qt::FramelessWindowHint); refresher->setGeometry(x() + 1, y() + 1, 1, 1); auto palette = refresher->palette(); palette.setColor(QPalette::Background, (isActiveWindow() ? st::titleBgActive : st::titleBg)->c); refresher->setPalette(palette); refresher->show(); refresher->activateWindow(); updateIconCounters(); } void MainWindow::psTrayMenuUpdated() { } void MainWindow::psSetupTrayIcon() { if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); auto icon = QIcon(App::pixmapFromImageInPlace(Core::App().logoNoMargin())); trayIcon->setIcon(icon); connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(showFromTray())); attachToTrayIcon(trayIcon); } updateIconCounters(); trayIcon->show(); } void MainWindow::showTrayTooltip() { if (trayIcon && !cSeenTrayTooltip()) { trayIcon->showMessage( AppName.utf16(), tr::lng_tray_icon_text(tr::now), QSystemTrayIcon::Information, 10000); cSetSeenTrayTooltip(true); Local::writeSettings(); } } void MainWindow::workmodeUpdated(DBIWorkMode mode) { switch (mode) { case dbiwmWindowAndTray: { psSetupTrayIcon(); HWND psOwner = (HWND)GetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT); if (psOwner) { SetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT, 0); psRefreshTaskbarIcon(); } } break; case dbiwmTrayOnly: { psSetupTrayIcon(); HWND psOwner = (HWND)GetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT); if (!psOwner) { SetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT, (LONG)ps_tbHider_hWnd); } } break; case dbiwmWindowOnly: { if (trayIcon) { trayIcon->setContextMenu(0); trayIcon->deleteLater(); } trayIcon = 0; HWND psOwner = (HWND)GetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT); if (psOwner) { SetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT, 0); psRefreshTaskbarIcon(); } } break; } } void MainWindow::unreadCounterChangedHook() { setWindowTitle(titleText()); updateIconCounters(); } void MainWindow::updateIconCounters() { const auto counter = Core::App().unreadBadge(); const auto muted = Core::App().unreadBadgeMuted(); auto iconSizeSmall = QSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); auto iconSizeBig = QSize(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); auto &fg = st::trayCounterFg; auto iconSmallPixmap16 = App::pixmapFromImageInPlace(iconWithCounter(16, counter, bg, fg, true)); auto iconSmallPixmap32 = App::pixmapFromImageInPlace(iconWithCounter(32, counter, bg, fg, true)); QIcon iconSmall, iconBig; iconSmall.addPixmap(iconSmallPixmap16); iconSmall.addPixmap(iconSmallPixmap32); iconBig.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(32, taskbarList.Get() ? 0 : counter, bg, fg, false))); iconBig.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(64, taskbarList.Get() ? 0 : counter, bg, fg, false))); if (trayIcon) { // Force Qt to use right icon size, not the larger one. QIcon forTrayIcon; forTrayIcon.addPixmap(iconSizeSmall.width() >= 20 ? iconSmallPixmap32 : iconSmallPixmap16); trayIcon->setIcon(forTrayIcon); } psDestroyIcons(); ps_iconSmall = createHIconFromQIcon(iconSmall, iconSizeSmall.width(), iconSizeSmall.height()); ps_iconBig = createHIconFromQIcon(iconBig, iconSizeBig.width(), iconSizeBig.height()); SendMessage(ps_hWnd, WM_SETICON, 0, (LPARAM)ps_iconSmall); SendMessage(ps_hWnd, WM_SETICON, 1, (LPARAM)(ps_iconBig ? ps_iconBig : ps_iconSmall)); if (taskbarList.Get()) { if (counter > 0) { QIcon iconOverlay; iconOverlay.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(-16, counter, bg, fg, false))); iconOverlay.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(-32, counter, bg, fg, false))); ps_iconOverlay = createHIconFromQIcon(iconOverlay, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); } auto description = (counter > 0) ? tr::lng_unread_bar(tr::now, lt_count, counter) : QString(); taskbarList->SetOverlayIcon(ps_hWnd, ps_iconOverlay, description.toStdWString().c_str()); } SetWindowPos(ps_hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } void MainWindow::initHook() { if (const auto native = QGuiApplication::platformNativeInterface()) { ps_hWnd = static_cast(native->nativeResourceForWindow( QByteArrayLiteral("handle"), windowHandle())); } if (!ps_hWnd) { return; } handleSessionNotification = (Dlls::WTSRegisterSessionNotification != nullptr) && (Dlls::WTSUnRegisterSessionNotification != nullptr); if (handleSessionNotification) { Dlls::WTSRegisterSessionNotification(ps_hWnd, NOTIFY_FOR_THIS_SESSION); } psInitSysMenu(); _shadow.emplace(this, st::windowShadowFg->c); } void MainWindow::initShadows() { psUpdateMargins(); shadowsUpdate(Ui::Platform::WindowShadow::Change::Hidden); } void MainWindow::firstShadowsUpdate() { if (!(windowState() & Qt::WindowMinimized) && !isHidden()) { using Change = Ui::Platform::WindowShadow::Change; shadowsUpdate(Change::Moved | Change::Resized | Change::Shown); } } void MainWindow::stateChangedHook(Qt::WindowState state) { updateSystemMenu(state); } void MainWindow::psInitSysMenu() { Qt::WindowStates states = windowState(); ps_menu = GetSystemMenu(ps_hWnd, FALSE); updateSystemMenu(windowHandle()->windowState()); } void MainWindow::updateSystemMenu(Qt::WindowState state) { if (!ps_menu) return; int menuToDisable = SC_RESTORE; if (state == Qt::WindowMaximized) { menuToDisable = SC_MAXIMIZE; } else if (state == Qt::WindowMinimized) { menuToDisable = SC_MINIMIZE; } int itemCount = GetMenuItemCount(ps_menu); for (int i = 0; i < itemCount; ++i) { MENUITEMINFO itemInfo = { 0 }; itemInfo.cbSize = sizeof(itemInfo); itemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID; if (GetMenuItemInfo(ps_menu, i, TRUE, &itemInfo)) { if (itemInfo.fType & MFT_SEPARATOR) { continue; } if (itemInfo.wID && !(itemInfo.fState & MFS_DEFAULT)) { UINT fOldState = itemInfo.fState, fState = itemInfo.fState & ~MFS_DISABLED; if (itemInfo.wID == SC_CLOSE) { fState |= MFS_DEFAULT; } else if (itemInfo.wID == menuToDisable || (itemInfo.wID != SC_MINIMIZE && itemInfo.wID != SC_MAXIMIZE && itemInfo.wID != SC_RESTORE)) { fState |= MFS_DISABLED; } itemInfo.fMask = MIIM_STATE; itemInfo.fState = fState; if (!SetMenuItemInfo(ps_menu, i, TRUE, &itemInfo)) { DEBUG_LOG(("PS Error: could not set state %1 to menu item %2, old state %3, error %4").arg(fState).arg(itemInfo.wID).arg(fOldState).arg(GetLastError())); DestroyMenu(ps_menu); ps_menu = 0; break; } } } else { DEBUG_LOG(("PS Error: could not get state, menu item %1 of %2, error %3").arg(i).arg(itemCount).arg(GetLastError())); DestroyMenu(ps_menu); ps_menu = 0; break; } } } void MainWindow::psUpdateMargins() { if (!ps_hWnd || _inUpdateMargins) return; _inUpdateMargins = true; RECT r, a; GetClientRect(ps_hWnd, &r); a = r; LONG style = GetWindowLongPtr(ps_hWnd, GWL_STYLE), styleEx = GetWindowLongPtr(ps_hWnd, GWL_EXSTYLE); AdjustWindowRectEx(&a, style, false, styleEx); QMargins margins = QMargins(a.left - r.left, a.top - r.top, r.right - a.right, r.bottom - a.bottom); if (style & WS_MAXIMIZE) { RECT w, m; GetWindowRect(ps_hWnd, &w); m = w; HMONITOR hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST); if (hMonitor) { MONITORINFO mi; mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); m = mi.rcWork; } _deltaLeft = w.left - m.left; _deltaTop = w.top - m.top; _deltaRight = m.right - w.right; _deltaBottom = m.bottom - w.bottom; margins.setLeft(margins.left() - _deltaLeft); margins.setRight(margins.right() - _deltaRight); margins.setBottom(margins.bottom() - _deltaBottom); margins.setTop(margins.top() - _deltaTop); } else if (_deltaLeft != 0 || _deltaTop != 0 || _deltaRight != 0 || _deltaBottom != 0) { RECT w; GetWindowRect(ps_hWnd, &w); SetWindowPos(ps_hWnd, 0, 0, 0, w.right - w.left - _deltaLeft - _deltaRight, w.bottom - w.top - _deltaBottom - _deltaTop, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION); _deltaLeft = _deltaTop = _deltaRight = _deltaBottom = 0; } if (const auto native = QGuiApplication::platformNativeInterface()) { native->setWindowProperty( windowHandle()->handle(), qsl("WindowsCustomMargins"), QVariant::fromValue(margins)); } if (!_themeInited) { _themeInited = true; if (!IsWindows8OrGreater()) { if (Dlls::SetWindowTheme != nullptr) { Dlls::SetWindowTheme(ps_hWnd, L" ", L" "); QApplication::setStyle(QStyleFactory::create(qsl("Windows"))); } } } _inUpdateMargins = false; } HWND MainWindow::psHwnd() const { return ps_hWnd; } HMENU MainWindow::psMenu() const { return ps_menu; } void MainWindow::psDestroyIcons() { if (ps_iconBig) { DestroyIcon(ps_iconBig); ps_iconBig = 0; } if (ps_iconSmall) { DestroyIcon(ps_iconSmall); ps_iconSmall = 0; } if (ps_iconOverlay) { DestroyIcon(ps_iconOverlay); ps_iconOverlay = 0; } } MainWindow::~MainWindow() { if (handleSessionNotification) { Dlls::WTSUnRegisterSessionNotification(ps_hWnd); } if (taskbarList) { taskbarList.Reset(); } if (ps_menu) DestroyMenu(ps_menu); psDestroyIcons(); if (ps_tbHider_hWnd) DestroyWindow(ps_tbHider_hWnd); EventFilter::Destroy(); } } // namespace Platform