fix: avoid promoting child widgets to child windows (#5161)

This commit is contained in:
nerix 2024-02-10 12:43:59 +01:00 committed by GitHub
parent f34a371576
commit 10aabd39e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 94 additions and 30 deletions

View file

@ -79,6 +79,7 @@
- Bugfix: Fixed splits not retaining their focus after minimizing. (#5080) - Bugfix: Fixed splits not retaining their focus after minimizing. (#5080)
- Bugfix: Fixed _Copy message_ copying the channel name in global search. (#5106) - Bugfix: Fixed _Copy message_ copying the channel name in global search. (#5106)
- Bugfix: Reply contexts now use the color of the replied-to message. (#5145) - Bugfix: Reply contexts now use the color of the replied-to message. (#5145)
- Bugfix: Fixed top-level window getting stuck after opening settings. (#5161)
- Dev: Run miniaudio in a separate thread, and simplify it to not manage the device ourselves. There's a chance the simplification is a bad idea. (#4978) - Dev: Run miniaudio in a separate thread, and simplify it to not manage the device ourselves. There's a chance the simplification is a bad idea. (#4978)
- Dev: Change clang-format from v14 to v16. (#4929) - Dev: Change clang-format from v14 to v16. (#4929)
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791) - Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)

View file

@ -78,6 +78,14 @@ namespace {
{ {
// set up the QApplication flags // set up the QApplication flags
QApplication::setAttribute(Qt::AA_Use96Dpi, true); QApplication::setAttribute(Qt::AA_Use96Dpi, true);
#ifdef Q_OS_WIN32
// Avoid promoting child widgets to child windows
// This causes bugs with frameless windows as not all child events
// get sent to the parent - effectively making the window immovable.
QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
#endif
#if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif #endif

View file

@ -201,7 +201,7 @@ void BaseWindow::init()
} }
// DPI // DPI
// auto dpi = getWindowDpi(this->winId()); // auto dpi = getWindowDpi(this->safeHWND());
// if (dpi) { // if (dpi) {
// this->scale = dpi.value() / 96.f; // this->scale = dpi.value() / 96.f;
@ -232,12 +232,13 @@ void BaseWindow::setTopMost(bool topMost)
{ {
return; return;
} }
this->isTopMost_ = topMost;
#ifdef USEWINSDK #ifdef USEWINSDK
::SetWindowPos(reinterpret_cast<HWND>(this->winId()), if (!this->waitingForTopMost_)
topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, {
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); this->tryApplyTopMost();
}
#else #else
auto isVisible = this->isVisible(); auto isVisible = this->isVisible();
this->setWindowFlag(Qt::WindowStaysOnTopHint, topMost); this->setWindowFlag(Qt::WindowStaysOnTopHint, topMost);
@ -247,10 +248,26 @@ void BaseWindow::setTopMost(bool topMost)
} }
#endif #endif
this->isTopMost_ = topMost;
this->topMostChanged(this->isTopMost_); this->topMostChanged(this->isTopMost_);
} }
#ifdef USEWINSDK
void BaseWindow::tryApplyTopMost()
{
auto hwnd = this->safeHWND();
if (!hwnd)
{
this->waitingForTopMost_ = true;
QTimer::singleShot(50, this, &BaseWindow::tryApplyTopMost);
return;
}
this->waitingForTopMost_ = false;
::SetWindowPos(*hwnd, this->isTopMost_ ? HWND_TOPMOST : HWND_NOTOPMOST, 0,
0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
#endif
bool BaseWindow::isTopMost() const bool BaseWindow::isTopMost() const
{ {
return this->isTopMost_ || this->flags_.has(TopMost); return this->isTopMost_ || this->flags_.has(TopMost);
@ -490,14 +507,17 @@ void BaseWindow::changeEvent(QEvent *)
} }
if (this->isVisible() && this->hasCustomWindowFrame()) if (this->isVisible() && this->hasCustomWindowFrame())
{
auto hwnd = this->safeHWND();
if (hwnd)
{ {
auto palette = this->palette(); auto palette = this->palette();
palette.setColor(QPalette::Window, palette.setColor(QPalette::Window, GetForegroundWindow() == *hwnd
GetForegroundWindow() == HWND(this->winId())
? QColor(90, 90, 90) ? QColor(90, 90, 90)
: QColor(50, 50, 50)); : QColor(50, 50, 50));
this->setPalette(palette); this->setPalette(palette);
} }
}
#endif #endif
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
@ -532,14 +552,18 @@ void BaseWindow::resizeEvent(QResizeEvent *)
{ {
this->isResizeFixing_ = true; this->isResizeFixing_ = true;
QTimer::singleShot(50, this, [this] { QTimer::singleShot(50, this, [this] {
auto hwnd = this->safeHWND();
if (!hwnd)
{
this->isResizeFixing_ = false;
return;
}
RECT rect; RECT rect;
::GetWindowRect((HWND)this->winId(), &rect); ::GetWindowRect(*hwnd, &rect);
::SetWindowPos((HWND)this->winId(), nullptr, 0, 0, ::SetWindowPos(*hwnd, nullptr, 0, 0, rect.right - rect.left + 1,
rect.right - rect.left + 1, rect.bottom - rect.top, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER);
SWP_NOMOVE | SWP_NOZORDER); ::SetWindowPos(*hwnd, nullptr, 0, 0, rect.right - rect.left,
::SetWindowPos((HWND)this->winId(), nullptr, 0, 0, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER);
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOMOVE | SWP_NOZORDER);
QTimer::singleShot(10, this, [this] { QTimer::singleShot(10, this, [this] {
this->isResizeFixing_ = false; this->isResizeFixing_ = false;
}); });
@ -579,9 +603,10 @@ void BaseWindow::showEvent(QShowEvent *)
if (!this->flags_.has(TopMost)) if (!this->flags_.has(TopMost))
{ {
QTimer::singleShot(1, this, [this] { QTimer::singleShot(1, this, [this] {
::SetWindowPos(reinterpret_cast<HWND>(this->winId()), if (!this->waitingForTopMost_)
this->isTopMost_ ? HWND_TOPMOST : HWND_NOTOPMOST, 0, {
0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); this->tryApplyTopMost();
}
}); });
} }
#endif #endif
@ -653,7 +678,7 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
long y = GET_Y_LPARAM(msg->lParam); long y = GET_Y_LPARAM(msg->lParam);
RECT winrect; RECT winrect;
GetWindowRect(HWND(winId()), &winrect); GetWindowRect(msg->hwnd, &winrect);
QPoint globalPos(x, y); QPoint globalPos(x, y);
this->ui_.titlebarButtons->hover(msg->wParam, globalPos); this->ui_.titlebarButtons->hover(msg->wParam, globalPos);
this->lastEventWasNcMouseMove_ = true; this->lastEventWasNcMouseMove_ = true;
@ -704,7 +729,7 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
long y = GET_Y_LPARAM(msg->lParam); long y = GET_Y_LPARAM(msg->lParam);
RECT winrect; RECT winrect;
GetWindowRect(HWND(winId()), &winrect); GetWindowRect(msg->hwnd, &winrect);
QPoint globalPos(x, y); QPoint globalPos(x, y);
if (msg->message == WM_NCLBUTTONDOWN) if (msg->message == WM_NCLBUTTONDOWN)
{ {
@ -852,7 +877,7 @@ bool BaseWindow::handleSHOWWINDOW(MSG *msg)
{ {
// disable OS window border // disable OS window border
const MARGINS margins = {-1}; const MARGINS margins = {-1};
DwmExtendFrameIntoClientArea(HWND(this->winId()), &margins); DwmExtendFrameIntoClientArea(msg->hwnd, &margins);
} }
if (!this->initalBounds_.isNull()) if (!this->initalBounds_.isNull())
@ -888,8 +913,8 @@ bool BaseWindow::handleNCCALCSIZE(MSG *msg, long *result)
auto *ncp = reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam); auto *ncp = reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam);
if (ncp) if (ncp)
{ {
ncp->lppos->flags |= SWP_NOREDRAW; // ncp->lppos->flags |= SWP_NOREDRAW;
ncp->rgrc[0].top -= 1; // ncp->rgrc[0].top -= 1;
} }
} }
@ -915,8 +940,8 @@ bool BaseWindow::handleSIZE(MSG *msg)
{ {
if (msg->wParam == SIZE_MAXIMIZED) if (msg->wParam == SIZE_MAXIMIZED)
{ {
auto offset = int( auto offset =
getWindowDpi(HWND(this->winId())).value_or(96) * 8 / 96); int(getWindowDpi(msg->hwnd).value_or(96) * 8 / 96);
this->ui_.windowLayout->setContentsMargins(offset, offset, this->ui_.windowLayout->setContentsMargins(offset, offset,
offset, offset); offset, offset);
@ -970,7 +995,7 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result)
#ifdef USEWINSDK #ifdef USEWINSDK
const LONG border_width = 8; // in pixels const LONG border_width = 8; // in pixels
RECT winrect; RECT winrect;
GetWindowRect(HWND(winId()), &winrect); GetWindowRect(msg->hwnd, &winrect);
long x = GET_X_LPARAM(msg->lParam); long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam); long y = GET_Y_LPARAM(msg->lParam);
@ -1149,4 +1174,15 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result)
#endif #endif
} }
#ifdef USEWINSDK
std::optional<HWND> BaseWindow::safeHWND() const
{
if (!this->testAttribute(Qt::WA_WState_Created))
{
return std::nullopt;
}
return reinterpret_cast<HWND>(this->winId());
}
#endif
} // namespace chatterino } // namespace chatterino

View file

@ -153,6 +153,25 @@ private:
} ui_; } ui_;
#ifdef USEWINSDK #ifdef USEWINSDK
/// @brief Returns the HWND of this window if it has one
///
/// A QWidget only has an HWND if it has been created. Before that,
/// accessing `winID()` will create the window which can lead to unintended
/// bugs.
std::optional<HWND> safeHWND() const;
/// @brief Tries to apply the `isTopMost_` setting
///
/// If the setting couldn't be applied (because the window wasn't created
/// yet), the operation is repeated after a short delay.
///
/// @pre When calling from outside this method, `waitingForTopMost_` must
/// be `false` to avoid too many pending calls.
/// @post If an operation was queued to be executed after some delay,
/// `waitingForTopMost_` will be set to `true`.
void tryApplyTopMost();
bool waitingForTopMost_ = false;
QRect initalBounds_; QRect initalBounds_;
QRect currentBounds_; QRect currentBounds_;
QRect nextBounds_; QRect nextBounds_;