Repository: napari/napari

Issue:
Napari window not showing on laptop screen after working with external display
## 🐛 Bug

I regularly switch from working with napari on my laptop that is connected to an external screen to working on just my laptop (no screen connected). I have a problem with displaying the napari viewer when I work on my just my laptop after it has been connected to an external screen the last time I worked with napari.

When I call the napari viewer on my laptop, the viewer does not show on my laptop screen. The viewer get's created and I can see it's icon in the Windows taskbar, but the viewer itself does not open. Even when I click on the icon in the taskbar, I cannot make it appear.

## To Reproduce

I work on a Windows 10 Enterprise computer that is regularly connected to a larger external display.

1. When connected to the external display, I call the napari viewer from a Jupyter Notebook using `from napari.viewer import Viewer` `napari_viewer = Viewer()` and display the viewer on my external screen.
2. If I, next time that I work, am disconnect from this external display and call the napari viewer (`from napari.viewer import Viewer` `napari_viewer = Viewer()`) again, the bug arises.

## Expected behavior

When I call the napari viewer for the second time (while disconnected from my external display), I expect the viewer to open as normal and display on my laptop screen.

## Environment

The bug happens with both the environments in which I have napari installed.

Environment 1:

napari: 0.4.16
Platform: Windows-10-10.0.19044-SP0
Python: 3.8.13 (default, Oct 19 2022, 22:38:03) [MSC v.1916 64 bit (AMD64)]
Qt: 5.15.2
PyQt5: 5.15.7
NumPy: 1.24.3
SciPy: 1.9.1
Dask: 2022.02.1
VisPy: 0.10.0

OpenGL:
- GL version: 4.6.0 - Build 30.0.101.1692
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1920x1080, scale 1.0

Plugins:
- console: 0.0.6
- napari-svg: 0.1.6
- scikit-image: 0.4.16

Environment 2:

napari: 0.4.16
Platform: Windows-10-10.0.19044-SP0
Python: 3.9.13 (main, Aug 25 2022, 23:51:50) [MSC v.1916 64 bit (AMD64)]
Qt: 5.12.9
PyQt5: 5.12.3
NumPy: 1.23.2
SciPy: 1.8.1
Dask: 2022.6.1
VisPy: 0.10.0

OpenGL:
- GL version: 4.6.0 - Build 30.0.101.1692
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1920x1080, scale 1.0

Plugins:
- console: 0.0.6
- morphometrics: 0.0.6.dev9+g7c2bb20
- napari-svg: 0.1.6
- napari-tools-menu: 0.1.17
- napari_skimage_regionprops1: 0.5.4
- napari_skimage_regionprops2: 0.5.4
- scikit-image: 0.4.16

## Additional context

My workaround at this moment is:
1. Close all my Jupyter Notebooks
2. In my command prompt run `napari --reset`
3. Reopen whichever Jupyter Notebooks I was running / working on



Discussion:
The napari 0.4.16 is not the latest released. Could you check if it still happens in 0.4.17? 
Thanks for the quick reply!
The napari viewer still partially opens outside of my laptop screen, but with the newer version I am able to use the Windows taskbar icon to maximize it on my laptop screen and then continue working without any problems.
Great to hear you have it working--btw is that in the same env? so updated napari in place with the same Qt version? or new napari in new env, with new Qt?
You can post `napari --info again` I guess
Ok. So I think that I know the source of the problem. We restore the previous position of viewer without checking if the coordinates are fitting on the current screen. 
Yes, I used the same environment!

napari: 0.4.17
Platform: Windows-10-10.0.19044-SP0
Python: 3.8.13 (default, Oct 19 2022, 22:38:03) [MSC v.1916 64 bit (AMD64)]
Qt: 5.15.2
PyQt5: 5.15.7
NumPy: 1.24.3
SciPy: 1.9.1
Dask: 2022.02.1
VisPy: 0.11.0
magicgui: 0.6.0
superqt: 0.4.0
in-n-out: 0.1.7
app-model: 0.1.4
npe2: 0.6.1

OpenGL:
- GL version: 4.6.0 - Build 30.0.101.1692
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1920x1080, scale 1.0

----------

I agree with your proposed source of the problem. I was suspecting something similar.
@seraweevers Could you attach a sketch of how you position your displays? 

I think that the source of the problem is in these lines:
https://github.com/napari/napari/blob/5bced982e57d8d5876884a8f18df6e3efb5084d7/napari/_qt/qt_main_window.py#L288-L291

W should not use only the primary screen but all screens and check if the image fits on any available screen. 

I also meet negative coordinates that we do not check in any way. 
![setup-napari](https://github.com/napari/napari/assets/120113212/4bfeb9c6-f93c-48a1-b223-0e57c7939528)

Forgive me for my mediocre drawing. But this is the setup I use. The external display is positioned (vertically) above the laptop screen.
When you will have access to this monitor could you move the viewer on it open console and execute this line `viewer.window._qt_window.pos()` and paste output of command.

In my case it looks like this:

```python
viewer.window._qt_window.pos()
Out[1]: PyQt5.QtCore.QPoint(2459, 353)
```

It indeed produces negative values, already when connected to the external screen.

```
napari_viewer.window._qt_window.pos()
Out[1]: PyQt5.QtCore.QPoint(-15, -2040)
```
Hi @seraweevers, 

I am working on your issue at the moment. Could you confirm please whether this only happens when napari was not set to full screen on the external screen?

Resolving patch:
diff --git a/napari/_qt/qt_main_window.py b/napari/_qt/qt_main_window.py
--- a/napari/_qt/qt_main_window.py
+++ b/napari/_qt/qt_main_window.py
@@ -285,9 +285,10 @@ def _load_window_settings(self):
         if not window_position:
             window_position = (self.x(), self.y())
         else:
-            width, height = window_position
-            screen_geo = QApplication.primaryScreen().geometry()
-            if screen_geo.width() < width or screen_geo.height() < height:
+            origin_x, origin_y = window_position
+            screen = QApplication.screenAt(QPoint(origin_x, origin_y))
+            screen_geo = screen.geometry() if screen else None
+            if not screen_geo:
                 window_position = (self.x(), self.y())
 
         return (


Test patch:
diff --git a/napari/_tests/test_windowsettings.py b/napari/_tests/test_windowsettings.py
new file mode 100644
--- /dev/null
+++ b/napari/_tests/test_windowsettings.py
@@ -0,0 +1,26 @@
+from napari.settings import get_settings
+
+
+def test_singlescreen_window_settings(make_napari_viewer):
+    """Test whether valid screen position is returned even after disconnected secondary screen."""
+    settings = get_settings()
+    viewer = make_napari_viewer()
+    default_window_position = (
+        viewer.window._qt_window.x(),
+        viewer.window._qt_window.y(),
+    )
+
+    # Valid position
+    settings.application.window_position = (60, 50)
+    window_position = viewer.window._qt_window._load_window_settings()[2]
+    assert window_position == (60, 50)
+
+    # Invalid left of screen
+    settings.application.window_position = (0, -400)
+    window_position = viewer.window._qt_window._load_window_settings()[2]
+    assert window_position == default_window_position
+
+    # Invalid right of screen
+    settings.application.window_position = (0, 40000)
+    window_position = viewer.window._qt_window._load_window_settings()[2]
+    assert window_position == default_window_position