Skip to content

Commit c8c1bd4

Browse files
authored
Persist remote connect dialog options (#18601)
Closes #18512 Summary of the issue: Since most users use their device as either follower or leader consistently, NVDA Remote Access should remember their selection. The same can also be said of the server type and port to use for self-hosted servers. Description of user facing changes: The "Connect to Another Computer" dialog persists the selection of the "Mode", "Server" and "Port" controls. Description of developer facing changes: NVDA now sets up `wx.lib.agw.persist` as follows: * persisted GUI state is saved to "guiState.ini" in the NVDA config directory. * If `NVDAState.shouldWriteToDisk` returns `False`, saving is disabled. Description of development approach: * Set up the `PersistenceManager` singleton in `gui.initialize`. Free it in `gui.terminate` out of an abundance of caution. * Add an (internal) list of controls that should be persisted to `_remoteClient.dialogs.DirectConnectDialog`. * Add (internal) methods to the same class to register and restore state, save state, and unregister these controls. * Register and restore state on init, save state on OK, and unregister on dialog destruction (used instead of window close as this is not fired on modal dialogs). Testing strategy: Connected to remote a number of times across a number of NVDA restarts and ensured mode was remembered. Manually edited the `guiState.ini` file to contain illegal values, and ran NVDA to ensure the defaults were used in this case. Ran NVDA with the `--launcher` flag and ensured that GUI state was read but not written. Known issues with pull request: None
1 parent 2e75301 commit c8c1bd4

4 files changed

Lines changed: 63 additions & 5 deletions

File tree

‎source/NVDAState.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2022-2023 NV Access Limited
2+
# Copyright (C) 2022-2025 NV Access Limited
33
# This file may be used under the terms of the GNU General Public License, version 2 or later.
44
# For more details see: https://www.gnu.org/licenses/gpl-2.0.html
55

@@ -83,6 +83,10 @@ def speechDictDefaultFile(self) -> str:
8383
def updateCheckStateFile(self) -> str:
8484
return os.path.join(self.configDir, "updateCheckState.pickle")
8585

86+
@property
87+
def guiStateFile(self) -> str:
88+
return os.path.join(self.configDir, "guiState.ini")
89+
8690
def getSymbolsConfigFile(self, locale: str) -> str:
8791
return os.path.join(self.configDir, f"symbols-{locale}.dic")
8892

‎source/_remoteClient/dialogs.py‎

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
from typing import TypedDict
1010
from urllib import request
1111

12+
import wx.lib
13+
import wx.lib.agw
14+
1215
import gui
1316
import wx
1417
from wx.lib.expando import ExpandoTextCtrl
18+
from wx.lib.agw import persist
1519
from gui.contextHelp import ContextHelpMixin
1620
from logHandler import log
1721
from gui.guiHelper import alwaysCallAfter, BoxSizerHelper
@@ -204,6 +208,7 @@ def __init__(self, parent: wx.Window | None = None, id: int = wx.ID_ANY):
204208
min=1,
205209
max=65535,
206210
initial=SERVER_PORT,
211+
name="remote.connect.port",
207212
)
208213
# Translators: Label of the edit field to enter key (password) to secure the Remote Access connection.
209214
self.key = sizerHelper.addLabeledControl(pgettext("remote", "&Key"), wx.TextCtrl)
@@ -313,32 +318,42 @@ def __init__(self, parent: wx.Window, id: int, title: str, hostnames: list[str]
313318
super().__init__(parent, id, title=title)
314319
mainSizer = wx.BoxSizer(wx.VERTICAL)
315320
contentsSizerHelper = BoxSizerHelper(self, wx.VERTICAL)
321+
self._persistentControls: list[wx.Window] = []
316322
self._connectionModeControl = contentsSizerHelper.addLabeledControl(
317323
# Translators: Label of the control allowing users to set whether they are the controlling or controlled computer in the Remote Access connection dialog.
318324
pgettext("remote", "&Mode:"),
319325
wx.Choice,
320326
choices=tuple(mode.displayString for mode in RemoteConnectionMode),
327+
# For persistence
328+
name="remote.connect.mode",
321329
)
322330
self._connectionModeControl.SetSelection(0)
331+
self._persistentControls.append(self._connectionModeControl)
323332
self._clientOrServerControl = contentsSizerHelper.addLabeledControl(
324333
# Translators: Label of the control allowing users to select whether to use a pre-existing Remote Access server, or to run their own.
325334
pgettext("remote", "&Server:"),
326335
wx.Choice,
327336
choices=tuple(serverType.displayString for serverType in RemoteServerType.__members__.values()),
337+
# For persistence
338+
name="remote.connect.server",
328339
)
329340
self._clientOrServerControl.Bind(wx.EVT_CHOICE, self._onClientOrServer)
341+
self._clientOrServerControl.SetSelection(0)
342+
self._persistentControls.append(self._clientOrServerControl)
330343
simpleBook = self._simpleBook = wx.Simplebook(self)
331344
self._clientPanel = ClientPanel(simpleBook)
332345
if hostnames:
333346
self._clientPanel.host.AppendItems(hostnames)
334347
self._clientPanel.host.SetSelection(0)
335348
self._serverPanel = ServerPanel(simpleBook)
349+
self._persistentControls.append(self._serverPanel.port)
336350
# Since wx.SimpleBook doesn't create a page switcher for us, the following page labels are not used in the GUI.
337351
simpleBook.AddPage(self._clientPanel, "Client")
338352
simpleBook.AddPage(self._serverPanel, "Server")
339-
self._clientOrServerControl.SetSelection(0)
340-
self._selectedPanel = self._clientPanel
341353
contentsSizerHelper.addItem(simpleBook)
354+
# Initialise persistence
355+
self._registerAndRestorePersistentControls()
356+
self._doSyncChoiceAndBook()
342357
contentsSizerHelper.addDialogDismissButtons(wx.OK | wx.CANCEL, True)
343358
self.Bind(wx.EVT_BUTTON, self._onOk, id=wx.ID_OK)
344359
mainSizer.Add(contentsSizerHelper.sizer, border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
@@ -347,15 +362,39 @@ def __init__(self, parent: wx.Window, id: int, title: str, hostnames: list[str]
347362
self.CenterOnScreen()
348363
self._connectionModeControl.SetFocus()
349364
self.Bind(wx.EVT_SHOW, self._onShow)
365+
# EVT_CLOSE is not fired on modal dialogs
366+
# Use EVT_WINDOW_DESTROY instead
367+
self.Bind(wx.EVT_WINDOW_DESTROY, self._onDestroy)
368+
369+
def _registerAndRestorePersistentControls(self):
370+
persistenceManager = persist.PersistenceManager.Get()
371+
for control in self._persistentControls:
372+
persistenceManager.RegisterAndRestore(control)
373+
374+
def _savePersistentControls(self):
375+
persistenceManager = persist.PersistenceManager.Get()
376+
for control in self._persistentControls:
377+
persistenceManager.Save(control)
378+
379+
def _unregisterPersistentControls(self):
380+
persistenceManager = persist.PersistenceManager.Get()
381+
for control in self._persistentControls:
382+
persistenceManager.Unregister(control)
350383

351384
def _onClientOrServer(self, evt: wx.CommandEvent) -> None:
352385
"""Respond to changing between using a control server or hosting it locally"""
386+
self._doSyncChoiceAndBook()
387+
evt.Skip()
388+
389+
def _doSyncChoiceAndBook(self):
390+
"""Set the page of the wx.SimpleBook to correspond to the selection in the server type control."""
353391
selectedIndex = self._clientOrServerControl.GetSelection()
354392
self._simpleBook.ChangeSelection(selectedIndex)
355-
# Hack: setting or changing the selection of a wx.SimpleBook seems to cause focus to jump to the first focusable control in the newly selected page, so force focus back to the control that caused the change.
393+
# Hack: setting or changing the selection of a wx.SimpleBook
394+
# seems to cause focus to jump to the first focusable control in the newly selected page,
395+
# so force focus back to the control that caused the change.
356396
self._clientOrServerControl.SetFocus()
357397
self._selectedPanel = self._simpleBook.GetPage(selectedIndex)
358-
evt.Skip()
359398

360399
def _onOk(self, evt: wx.CommandEvent) -> None:
361400
"""Respond to the OK button being pressed."""
@@ -387,8 +426,13 @@ def _onOk(self, evt: wx.CommandEvent) -> None:
387426
if focusTarget is not None:
388427
focusTarget.SetFocus()
389428
else:
429+
self._savePersistentControls()
390430
evt.Skip()
391431

432+
def _onDestroy(self, evt: wx.WindowDestroyEvent):
433+
self._unregisterPersistentControls()
434+
evt.Skip()
435+
392436
def _getKey(self) -> str:
393437
"""Get the connection key."""
394438
return self._selectedPanel.key.GetValue()

‎source/gui/__init__.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import warnings
1212
import wx
1313
import wx.adv
14+
import wx.lib.agw.persist
1415

1516
import globalVars
1617
import tones
@@ -886,9 +887,16 @@ def initialize():
886887

887888
monkeyPatches.applyWxMonkeyPatches(mainFrame, winUser, wx)
888889

890+
# Set up GUI persistence
891+
persistenceManager = wx.lib.agw.persist.PersistenceManager.Get()
892+
persistenceManager.SetPersistenceFile(NVDAState.WritePaths.guiStateFile)
893+
if not NVDAState.shouldWriteToDisk():
894+
persistenceManager.DisableSaving()
895+
889896

890897
def terminate():
891898
global mainFrame
899+
wx.lib.agw.persist.PersistenceManager.Free()
892900
mainFrame = None
893901

894902

‎user_docs/en/changes.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* NVDA no longer resets braille tables to automatic when changing its language. (#18538, @LeonarddeR)
2525
* The Dot Pad braille display driver now supports automatic detection of USB-connected devices.
2626
Note that this is disabled by default due to the device using generic USB identifiers, but can be enabled in braille settings. (#18444, @bramd)
27+
* The NVDA Remote Access connection dialog now remembers the most recent connection mode, server type and locally hosted port of manual connections. (#18512)
2728

2829
### Bug Fixes
2930

@@ -49,6 +50,7 @@ Please refer to [the developer guide](https://download.nvaccess.org/documentatio
4950
* detours to `9764cebcb1a75940e68fa83d6730ffaf0f669401`. (#18447, @LeonarddeR)
5051
* The `nvda_dmp` utility has been removed. (#18480, @codeofdusk)
5152
* `comInterfaces_sconscript` has been updated to make the generated files in `comInterfaces` work better with IDEs. (#17608, @gexgd0419)
53+
* NVDA now configures `wx.lib.agw.persist.PersistenceManager` on GUI initialisation. (#18601)
5254

5355
#### Deprecations
5456

0 commit comments

Comments
 (0)