Repository: qutebrowser/qutebrowser

Issue:
[macOS] hide_decoration makes window nonresizable
As mentioned in my comment in #3750, there has been a regression with the `window.hide_decoration` option on macOS. The original functionality would let the titlebar be hidden and still have the browser window be resizable.

In v1.4.0 and later, the titlebar is removed as intended, but the window can no longer be resized.
The last working version was v1.3.3. This appears to have been the result of some change in Qt.

Version Info:
```
qutebrowser v1.4.1
Git commit: 75d153d6d (2018-07-11 17:16:50 +0200)
Backend: QtWebEngine (Chromium 65.0.3325.230)

CPython: 3.6.6
Qt: 5.11.1
PyQt: 5.11.2

sip: 4.19.11
colorama: 0.3.9
pypeg2: 2.15
jinja2: 2.10
pygments: 2.2.0
yaml: 3.13
cssutils: 1.0.2 $Id$
attr: 18.1.0
PyQt5.QtWebEngineWidgets: yes
PyQt5.QtWebKitWidgets: no
pdf.js: no
sqlite: 3.23.1
QtNetwork SSL: Secure Transport, macOS 10.14

Style: QWindowsStyle
Platform: Darwin-18.0.0-x86_64-i386-64bit, 64bit
Frozen: True
Imported from /Applications/qutebrowser.app/Contents/MacOS/qutebrowser
Using Python from /Applications/qutebrowser.app/Contents/MacOS/qutebrowser
Qt library executable path: /Applications/qutebrowser.app/Contents/MacOS/PyQt5/Qt/libexec, data path: /Applications/qutebrowser.app/Contents/MacOS/PyQt5/Qt
OS Version: 10.14, x86_64
```


Discussion:
Can anyone confirm that this only happens under macOS? It's indeed more of a Qt related issue, as some early test of mine revealed that the behaviour of `Qt.CustomizeWindow` changed under Qt 5.11.   I'm busy until sunday, after that I plan to take a look this.
I can't see any difference on KDE FWIW.
https://codereview.qt-project.org/#/c/221493/ looks like the culprit:

> Cocoa: Restore resizable window mask setting to 5.9 behavior
> 
> This should be tied to the CustomizeWindowHint flag:
> if that flag is set we start out without NSResizableWindowMask
> and possibly add it later on if WindowMaximizeButtonHint
> is set.

So I guess we just need to set the `Qt.WindowMaximizeButtonHint` [flag](http://doc.qt.io/qt-5/qt.html#WindowType-enum) (and possibly other buttons as well?) for it to work again?
> So I guess we just need to set the Qt.WindowMaximizeButtonHint flag (and possibly other buttons as well?) for it to work again?

That seems to have been the intention, but in practice this (and many other combinations) never achieved the desired effect of a resizable window with it's decoration hidden. I believe that this is caused by the fact that there is not further check for `Qt.WindowMaximizeButtonHint` in the function that handles `NSWindow` style mask flags in the [diff](https://codereview.qt-project.org/#/c/221493/3/src/plugins/platforms/cocoa/qcocoawindow.mm) corresponding to the commit message you mention. However, such a check _is_ made on the more recent [Qt github]():
```C
NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) {
...
const bool resizeable = !(flags & Qt::CustomizeWindowHint); // Remove zoom button by disabling resize
// Select base window type. Note that the value of NSBorderlessWindowMask is 0.
NSUInteger styleMask = (frameless || !resizeable) ? NSBorderlessWindowMask : NSResizableWindowMask;
...
if (flags & Qt::WindowMaximizeButtonHint)
    styleMask |= NSResizableWindowMask;
```

So could this be an oversight in Qt? (I have a different theory as well but this seems to be more likely)
That seems to be https://codereview.qt-project.org/#/c/232846/ which will make it to Qt 5.11.2. So probably not much we can do other than waiting for that?
Probably, yes. One way around is special native macOS window creation code as in for example the [Qt "TrueFramelessWindow"](https://github.com/dfct/TrueFramelessWindow/blob/master/winnativewindow.cpp) project, but that might  involve quite some code for this specific case, although maybe someone more knowledgeable in Qt(/c++?) can make it manageable for our purposes. I'm going to have a little try at it because basically the only thing that needs to be done is setting the `NSResizableWindowMask` flag, but waiting also seems like an okay solution. 
Maybe that can be done via [PyObjC](https://pypi.org/project/pyobjc/) somehow. FWIW any solution that requires qutebrowser compiling native C++ or Objective C code is not acceptable I'm afraid.
Probably have to wait til they fix this in Qt (5.11.2?). To set the required flag, you need to be able to access the underlaying *NSWindow* object. I haven't used Qt so I have no idea if they have a way of exposing the underlaying window object, but I doubt that they do.

What you really want to have happen in the end, is the following line:
```
    NSWindow *window = (NSWindow *) w; // get this from somewhere
    [window setStyleMask: [window styleMask] | NSWindowStyleMaskResizable];
```

There's [QWidget::winId](https://doc.qt.io/qt-5/qwidget.html#winId) - not sure how to get a `NSWindow` from that though.
I took a quick look at the project @rien333 referenced above, and they seem to do the following:

```
    NSView *nativeView = reinterpret_cast<NSView *>(winid);
    NSWindow* nativeWindow = [nativeView window];
```

Where *winid* is from:

```
    // On OS X / Linux, the widget can handle everything itself so just create Widget as normal
    Widget w;
    w.winId();
```

and *Widget* is defined as:

```
class Widget : public QMainWindow

// does not override the winId method
```

Not sure if any of this helps, but either way you would probably need a bridge to some Cocoa (objc compiled) function that does this operation. I understand if you want to wait for a Qt update to sort this out.

Edit:
@The-Compiler  It seems like PyObjC as you mentioned can be used for this. I can not really contribute much here, as I don't have experience using python.
Is there a fix for this yet?
@brorbw No, which is why the issue is still open.
Alright. Someone might have found at temporary fix for this issue. 
@koekeishiya this is, however not the only program where I have experinced this behaviour. Disableing the titlebar(window decoration) in the Helium browser for macOS, yeilds the same problem, the window is moveable, but not scaleable with chunkwm. Which leads me to believe that this isn't entirely the fault of Qt or qutebrowser.
The issue in Helium is likely unrelated. I'm pretty sure this will be fixed in Qt 5.11.2, as it worked in qutebrowser in earlier Qt versions. FWIW Qt 5.11.2 is currently planned for August/September, hopefully with a PyQt and then a qutebrowser release following soon after that.
@brorbw older versions (e.g. 1.3.0) hide the titlebar just fine, so given the above discussion this is almost certainly a Qt issue. 
:( I'm having the same problem
qutebrowser v1.5.0 is now out, which bundles PyQt 5.11.3 and Qt 5.11.2. How do things look there?
I'll check later when I'm at my computer. 

No, the issue is still present.
I can add that the window is completely ignored by chunkwm @koekeishiya
How do things look with [qutebrowser-1.5.0-4067-1.dmg](https://www.qutebrowser.org/tmp/qutebrowser-1.5.0-4067-1.dmg)?
@The-Compiler With that version the titlebar is no longer hidden;  the minimize and close buttons are greyed out however. Window can be moved and resized.
I suspected as much - we're back to square one then, I have no idea how to create a resizable window without a titlebar.
v1.6.0 on Mac cannot resize as well, movable with [Spectacle](https://github.com/eczarny/spectacle) app.
> v1.6.0 on Mac cannot resize as well, movable with [Spectacle](https://github.com/eczarny/spectacle) app.

Same with v1.6.1
Issue is still relevant in v1.6.3. Can be moved through the accessibility API, but not resized.
Running into this on v1.7.0 when trying to manage with [yabai](https://github.com/koekeishiya/yabai).
Same with v1.10.1. 
Does not work with [yabai](https://github.com/koekeishiya/yabai).
Still doesn't work with v1.12.0 :(.
2020 (the year we wish we could all skip over), issue is still present (tested with Caitlina w/ yabai).
Complaining here isn't likely to achieve all that much. When this issue first arose, it was because of a change in Qt, iirc. If you really  want to get this fixed, someone has to contact the Qt project about this. For hints on what sort of changes to request to the Qt peeps, see the original PR (https://github.com/qutebrowser/qutebrowser/pull/3750) and the comments at the start of this thread. 
 Hello and thanks for the response! Certainly wasn't my intention to come
across as complaining, more necro-bumping and confirming that the window
management with decorations off is still an issue.

I also appreciate you pointing me in the direction of the original PR. I
will admit that I did read it prior to posting, but did not take the
initiative to follow through with reaching out to the QT team. I'm working
on that now (happy to contribute in any way I can, as Qutebroswer is an
amazing project!).

Best!

On Sun, Jul 26, 2020 at 11:58 AM Rijnder Wever <notifications@github.com>
wrote:

> Complaining here isn't likely to achieve all that much. When this issue
> first arose, it was because of a change in Qt, iirc. If you really want to
> get this fixed, someone has to contact the Qt project about this. For hints
> on what sort of changes to request to the Qt peeps, see the original PR (
> #3750 <https://github.com/qutebrowser/qutebrowser/pull/3750>) and the
> comments at the start of this thread.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/qutebrowser/qutebrowser/issues/4067#issuecomment-664005645>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ACVREOMPCS4PVYSKOZC4UL3R5RHBRANCNFSM4FJ3JUTA>
> .
>

No problem, and thanks for helping look into this issue. I was just hoping to point out that from what I remember, Qt was more or less the source of this problem. 
@Nokodoko, have you been able to make any progress?
The v2.2.0 release sets `NSRequiresAquaSystemAppearance = False` in `Info.plist`. @ftpd mentioned that the `hide_decoration` option doesn't work at all anymore since then - can someone confirm?
> The v2.2.0 release sets `NSRequiresAquaSystemAppearance = False` in `Info.plist`. @ftpd mentioned that the `hide_decoration` option doesn't work at all anymore since then - can someone confirm?

Works for me on MacOS 11.2.1, apart from the bug covered throughout this thread.
Hello, and sorry for the bumping, but do we have any info if this will be fixed soon?
I cant stand the decoration when tiling with yabai, makes me sad.
No news so far. This is a regression in Qt, and it's unclear whether qutebrowser even can fix it in a non-hackish way. I'm afraid I'm not really familiar with macOS stuff at all and don't use macOS as a daily driver myself, so this isn't exactly on the top of my priorities.
Hello, I took a step away from all things programming for a while (so I missed this note). At the moment I no longer have a mac machine and will need to get catilina running in a vm or a hackintosh in order to re-check if this is a long standing issue (apologies for the necro-bump as well)
@Nokodoko I do have an intel powered mac machine but almost no Qt & qutebrowser development experience.
We can get on a jitsi call for example for you not go into a hustle of running macOS in a VM.
Sorry for bumping this, but I just wanna ask ... there is no hope anymore, is it ? 
@BR00l depends on whether someone with the appropriate macOS knowledge picks it up. We can't predict the future.

Resolving patch:
diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py
--- a/qutebrowser/mainwindow/mainwindow.py
+++ b/qutebrowser/mainwindow/mainwindow.py
@@ -569,6 +569,15 @@ def _set_decoration(self, hidden):
         if hidden:
             window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint
         self.setWindowFlags(window_flags)
+
+        if utils.is_mac and hidden:
+            from ctypes import c_void_p
+            # pylint: disable=import-error
+            from objc import objc_object
+            from AppKit import NSWindowStyleMaskResizable
+            win = objc_object(c_void_p=c_void_p(int(self.winId()))).window()
+            win.setStyleMask_(win.styleMask() | NSWindowStyleMaskResizable)
+
         if refresh_window:
             self.show()
 
diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py
--- a/qutebrowser/misc/earlyinit.py
+++ b/qutebrowser/misc/earlyinit.py
@@ -243,6 +243,10 @@ def check_libraries():
     if sys.version_info < (3, 9):
         # Backport required
         modules['importlib_resources'] = _missing_str("importlib_resources")
+    if sys.platform.startswith('darwin'):
+        # Used for resizable hide_decoration windows on macOS
+        modules['objc'] = _missing_str("pyobjc-core")
+        modules['AppKit'] = _missing_str("pyobjc-framework-Cocoa")
     _check_modules(modules)
 
 
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -407,6 +407,7 @@ def __str__(self) -> str:
         ('PyQt5.QtWebEngineWidgets', []),
         ('PyQt5.QtWebEngine', ['PYQT_WEBENGINE_VERSION_STR']),
         ('PyQt5.QtWebKitWidgets', []),
+        ('objc', ['__version__']),
     ]
 ])
 


Test patch:
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -751,6 +751,7 @@ def test_version_attribute(self, attribute, expected_modules, import_fake):
         ('adblock', True),
         ('dataclasses', False),
         ('importlib_resources', False),
+        ('objc', True),
     ])
     def test_existing_attributes(self, name, has_version):
         """Check if all dependencies have an expected __version__ attribute.