Repository: vispy/vispy

Issue:
error with pyside6 6.4.0 backend on macOS arm64
Making a fresh env, python 3.9 or python 3.10, then installing pyside6 `pip install pyside6` and vispy (0.11.0) works, but running
https://vispy.org/gallery/scene/turntable_box.html#sphx-glr-gallery-scene-turntable-box-py
fails with error:
```
╰─ python turntable_box.py                                        (test-env3) ─╯
Traceback (most recent call last):
  File "/Users/piotrsobolewski/Downloads/turntable_box.py", line 17, in <module>
    canvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/test-env3/lib/python3.9/site-packages/vispy/scene/canvas.py", line 135, in __init__
    super(SceneCanvas, self).__init__(
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/test-env3/lib/python3.9/site-packages/vispy/app/canvas.py", line 211, in __init__
    self.create_native()
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/test-env3/lib/python3.9/site-packages/vispy/app/canvas.py", line 228, in create_native
    self._app.backend_module.CanvasBackend(self, **self._backend_kwargs)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/test-env3/lib/python3.9/site-packages/vispy/app/backends/_qt.py", line 372, in __init__
    self._init_specific(p, kwargs)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/test-env3/lib/python3.9/site-packages/vispy/app/backends/_qt.py", line 791, in _init_specific
    glformat = _set_config(p.context.config)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/test-env3/lib/python3.9/site-packages/vispy/app/backends/_qt.py", line 283, in _set_config
    glformat.setSwapBehavior(glformat.DoubleBuffer if c['double_buffer']
AttributeError: 'PySide6.QtGui.QSurfaceFormat' object has no attribute 'DoubleBuffer'
```

(by comparison the same but with `pip install pyqt6` works fine.)


Discussion:
Just so we keep a record: see related #2335 and #2406.

It seems like we might need to add yet another test environment for pyside2 (based on #2406). For this PySide6 stuff I'm hoping we can just live with user testing for now. I can't find it right now but I believe there was another issue around with someone getting a seg fault on macOS with Qt6 installed. That might have been specific to their environment though.

@psobolewskiPhD do you or someone on the napari team (@brisvag?) think you could add a new CI environment to vispy that uses pyside2 to run the examples? Or if you want pyside6 instead?
> Just so we keep a record: see related #2335 and #2406.
> 
> It seems like we might need to add yet another test environment for pyside2 (based on #2406). For this PySide6 stuff I'm hoping we can just live with user testing for now. I can't find it right now but I believe there was another issue around with someone getting a seg fault on macOS with Qt6 installed. That might have been specific to their environment though.
> 
> @psobolewskiPhD do you or someone on the napari team (@brisvag?) think you could add a new CI environment to vispy that uses pyside2 to run the examples? Or if you want pyside6 instead?

At least from a napari PoV Qt6 (so pyside6) is really experimental and not officially supported. But pyside2 is supported and maybe even cleaner from a licensing PoV, so I think CI tests with it would be a good idea. napari tests windows and ubuntu with PySide2==5.15.2.1 and ubuntu with pyside6, so this issue could be mac specific or conda-forge specific or version specific.
Based on some sleuthing by @czaki I tried python 3.10 and pyside6 (latest, 6.4.0) but it still gave the same error. However! Using pyside6 6.3.0 and 6.3.2 was fine, so this issue is specific to something that's changed in pyside 6 6.4.0 . I've edited the title.
Yes. This is pyside 6.4.0 proble and requires a new qtpy release
https://github.com/spyder-ide/qtpy/pull/374
> Yes. Phis is pyside 6.4.0 proble and requires a new qtpy release
> [spyder-ide/qtpy#374](https://github.com/spyder-ide/qtpy/pull/374)

Unfortunately installing that PR doesn't solve this issue. Again it's puzzling because it's in the docs:
https://doc.qt.io/qtforpython/PySide6/QtGui/QSurfaceFormat.html#PySide6.QtGui.PySide6.QtGui.QSurfaceFormat.SwapBehavior

could you show traceback? 
Is the code working with 6.3.1 and/or 6.3.2.
Traceback is in the OP and yes both 6.3.0 and 6.3.2 work (https://github.com/vispy/vispy/issues/2407#issuecomment-1283776193)
Could you report it to vispy?
> Could you report it to vispy?

How? This is a vispy issue... Sorry if i'm missing something, it's been a long day.
It is a long day, and this is the proper diagnosis for me. I war pretty sure that I'm in napari repository. 
@psobolewskiPhD I'm trying to look through pyside6 source code, but their git web UI is not helpful. Since I don't see it defined here, what are the minimal number of steps to reproduce this (what commands create the environment)? Are you able to do an import of the exact PySide6 module and access this attribute it is failing on?

```
from PySide6.QtGui import QSurfaceFormat
print(hasattr(QSurfaceFormat, "DoubleBuffer"))
```
Scratch that code, try this:

```
from PySide6.QtGui import QSurfaceFormat
qsf = QSurfaceFormat()
print(hasattr(qsf, "DoubleBuffer"))
print(hasattr(qsf.SwapBehavior, "DoubleBuffer"))
```

This "SwapBehavior" version matches what vispy is doing for PyQt6 so maybe PySide6 6.4.0+ is matching PyQt6 for this interface...or something similar.
Replicating the env:
```
mamba create -n vispy-clean -c conda-forge python=3.9 
conda activate wispy-clean
pip install pyside6
pip install vispy
```
Here's the output I get on my arm64 macOS 12.6:
```
>>> from PySide6.QtGui import QSurfaceFormat
>>> qsf = QSurfaceFormat()
>>> print(hasattr(qsf, "DoubleBuffer"))
False
>>> print(hasattr(qsf.SwapBehavior, "DoubleBuffer"))
True
>>> 
```
Great! I see the same thing on my Ubuntu system. Note that the vispy installation isn't actually necessary for this simple test.

So it looks like PySide6 6.4.0 uses the same interfaces as PyQt6. This should be a simple but yet another version check we need to do in `_qt.py`. @psobolewskiPhD or @Czaki would you be willing and have the time to make a PR for this?

I think the main change is here:

https://github.com/vispy/vispy/blob/8bf4f9e538dee5cb986779006b84f62eed81ed7c/vispy/app/backends/_qt.py#L281-L287

I'm not sure if this is the only change. We may need to rename `PYSIDE6_API` to `PYSIDE6_LEGACY_API` and create a new `PYSIDE6_API` that is used in the same locations as `PYQT6_API`. *If* there are more name changes that effect vispy then it may be that PyQt6 and PySide6 now have equivalent interfaces. In that case importing PySide6 6.4+ could just set `PYQT6_API` to True.
Awesome 👏 
I can try to take a look over the weekend. 
❤️ 

Resolving patch:
diff --git a/vispy/app/backends/_qt.py b/vispy/app/backends/_qt.py
--- a/vispy/app/backends/_qt.py
+++ b/vispy/app/backends/_qt.py
@@ -191,7 +191,7 @@ def _get_event_xy(ev):
     qt_keys.Key_Return: keys.ENTER,
     qt_keys.Key_Tab: keys.TAB,
 }
-if PYQT6_API:
+if PYQT6_API or PYSIDE6_API:
     BUTTONMAP = {
         QtCore.Qt.MouseButton.NoButton: 0,
         QtCore.Qt.MouseButton.LeftButton: 1,
@@ -278,11 +278,11 @@ def _set_config(c):
     glformat.setGreenBufferSize(c['green_size'])
     glformat.setBlueBufferSize(c['blue_size'])
     glformat.setAlphaBufferSize(c['alpha_size'])
-    if QT5_NEW_API or PYSIDE6_API:
+    if QT5_NEW_API:
         # Qt5 >= 5.4.0 - below options automatically enabled if nonzero.
         glformat.setSwapBehavior(glformat.DoubleBuffer if c['double_buffer']
                                  else glformat.SingleBuffer)
-    elif PYQT6_API:
+    elif PYQT6_API or PYSIDE6_API:
         glformat.setSwapBehavior(glformat.SwapBehavior.DoubleBuffer if c['double_buffer']
                                  else glformat.SwapBehavior.SingleBuffer)
     else:


Test patch:
diff --git a/vispy/app/tests/test_context.py b/vispy/app/tests/test_context.py
--- a/vispy/app/tests/test_context.py
+++ b/vispy/app/tests/test_context.py
@@ -17,6 +17,8 @@ def test_context_properties():
         return  # cannot set more than once on Pyglet
     if a.backend_name.lower() == 'osmesa':
         return  # cannot set config on OSMesa
+    if 'pyqt5' in a.backend_name.lower() or 'pyqt6' in a.backend_name.lower() or 'pyside2' in a.backend_name.lower() or 'pyside6' in a.backend_name.lower():
+        pytest.xfail("Context sharing is not supported in PyQt5, PyQt6, PySide2, or PySide6 at this time.")
 
     # stereo, double buffer won't work on every sys
     configs = [dict(samples=4), dict(stencil_size=8),
@@ -68,9 +70,9 @@ def check():
         # Check while c1 is active
         check()
 
-        # pyqt5 does not currently support context sharing
-        if 'pyqt5' in c1.app.backend_name.lower() or 'pyqt6' in c1.app.backend_name.lower():
-            pytest.xfail("Context sharing is not supported in PyQt5 at this time.")
+        # pyqt5 does not currently support context sharing, pyside6 seg faults on app tests
+        if 'pyqt5' in c1.app.backend_name.lower() or 'pyqt6' in c1.app.backend_name.lower() or 'pyside2' in c1.app.backend_name.lower() or 'pyside6' in c1.app.backend_name.lower():
+            pytest.xfail("Context sharing is not supported in PyQt5, PyQt6, PySide2, or PySide6 at this time.")
 
         # Tkinter does not currently support context sharing
         if 'tk' in c1.app.backend_name.lower():