bauiv1lib.settings.nettesting

Provides ui for network related testing.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides ui for network related testing."""
  4
  5from __future__ import annotations
  6
  7import time
  8import copy
  9import weakref
 10from threading import Thread
 11from typing import TYPE_CHECKING
 12
 13from efro.error import CleanError
 14from bauiv1lib.settings.testing import TestingWindow
 15import bauiv1 as bui
 16
 17if TYPE_CHECKING:
 18    from typing import Callable, Any
 19
 20# We generally want all net tests to timeout on their own, but we add
 21# sort of sane max in case they don't.
 22MAX_TEST_SECONDS = 60 * 2
 23
 24
 25class NetTestingWindow(bui.Window):
 26    """Window that runs a networking test suite to help diagnose issues."""
 27
 28    def __init__(self, transition: str = 'in_right'):
 29        self._width = 820
 30        self._height = 500
 31        self._printed_lines: list[str] = []
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        super().__init__(
 35            root_widget=bui.containerwidget(
 36                size=(self._width, self._height),
 37                scale=(
 38                    1.56
 39                    if uiscale is bui.UIScale.SMALL
 40                    else 1.2
 41                    if uiscale is bui.UIScale.MEDIUM
 42                    else 0.8
 43                ),
 44                stack_offset=(0.0, -7 if uiscale is bui.UIScale.SMALL else 0.0),
 45                transition=transition,
 46            )
 47        )
 48        self._done_button = bui.buttonwidget(
 49            parent=self._root_widget,
 50            position=(40, self._height - 77),
 51            size=(120, 60),
 52            scale=0.8,
 53            autoselect=True,
 54            label=bui.Lstr(resource='doneText'),
 55            on_activate_call=self._done,
 56        )
 57
 58        self._copy_button = bui.buttonwidget(
 59            parent=self._root_widget,
 60            position=(self._width - 200, self._height - 77),
 61            size=(100, 60),
 62            scale=0.8,
 63            autoselect=True,
 64            label=bui.Lstr(resource='copyText'),
 65            on_activate_call=self._copy,
 66        )
 67
 68        self._settings_button = bui.buttonwidget(
 69            parent=self._root_widget,
 70            position=(self._width - 100, self._height - 77),
 71            size=(60, 60),
 72            scale=0.8,
 73            autoselect=True,
 74            label=bui.Lstr(value='...'),
 75            on_activate_call=self._show_val_testing,
 76        )
 77
 78        twidth = self._width - 450
 79        bui.textwidget(
 80            parent=self._root_widget,
 81            position=(self._width * 0.5, self._height - 55),
 82            size=(0, 0),
 83            text=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
 84            color=(0.8, 0.8, 0.8, 1.0),
 85            h_align='center',
 86            v_align='center',
 87            maxwidth=twidth,
 88        )
 89
 90        self._scroll = bui.scrollwidget(
 91            parent=self._root_widget,
 92            position=(50, 50),
 93            size=(self._width - 100, self._height - 140),
 94            capture_arrows=True,
 95            autoselect=True,
 96        )
 97        self._rows = bui.columnwidget(parent=self._scroll)
 98
 99        bui.containerwidget(
100            edit=self._root_widget, cancel_button=self._done_button
101        )
102
103        # Now kick off the tests.
104        # Pass a weak-ref to this window so we don't keep it alive
105        # if we back out before it completes. Also set is as daemon
106        # so it doesn't keep the app running if the user is trying to quit.
107        Thread(
108            daemon=True,
109            target=bui.Call(_run_diagnostics, weakref.ref(self)),
110        ).start()
111
112    def print(self, text: str, color: tuple[float, float, float]) -> None:
113        """Print text to our console thingie."""
114        for line in text.splitlines():
115            txt = bui.textwidget(
116                parent=self._rows,
117                color=color,
118                text=line,
119                scale=0.75,
120                flatness=1.0,
121                shadow=0.0,
122                size=(0, 20),
123            )
124            bui.containerwidget(edit=self._rows, visible_child=txt)
125            self._printed_lines.append(line)
126
127    def _copy(self) -> None:
128        if not bui.clipboard_is_supported():
129            bui.screenmessage(
130                'Clipboard not supported on this platform.', color=(1, 0, 0)
131            )
132            return
133        bui.clipboard_set_text('\n'.join(self._printed_lines))
134        bui.screenmessage(f'{len(self._printed_lines)} lines copied.')
135
136    def _show_val_testing(self) -> None:
137        assert bui.app.classic is not None
138        bui.app.ui_v1.set_main_menu_window(
139            NetValTestingWindow().get_root_widget()
140        )
141        bui.containerwidget(edit=self._root_widget, transition='out_left')
142
143    def _done(self) -> None:
144        # pylint: disable=cyclic-import
145        from bauiv1lib.settings.advanced import AdvancedSettingsWindow
146
147        assert bui.app.classic is not None
148        bui.app.ui_v1.set_main_menu_window(
149            AdvancedSettingsWindow(transition='in_left').get_root_widget()
150        )
151        bui.containerwidget(edit=self._root_widget, transition='out_right')
152
153
154def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
155    # pylint: disable=too-many-statements
156
157    from efro.util import utc_now
158
159    have_error = [False]
160
161    # We're running in a background thread but UI stuff needs to run
162    # in the logic thread; give ourself a way to pass stuff to it.
163    def _print(
164        text: str, color: tuple[float, float, float] | None = None
165    ) -> None:
166        def _print_in_logic_thread() -> None:
167            win = weakwin()
168            if win is not None:
169                win.print(text, (1.0, 1.0, 1.0) if color is None else color)
170
171        bui.pushcall(_print_in_logic_thread, from_other_thread=True)
172
173    def _print_test_results(call: Callable[[], Any]) -> bool:
174        """Run the provided call, print result, & return success."""
175        starttime = time.monotonic()
176        try:
177            call()
178            duration = time.monotonic() - starttime
179            _print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0))
180            return True
181        except Exception as exc:
182            import traceback
183
184            duration = time.monotonic() - starttime
185            msg = (
186                str(exc)
187                if isinstance(exc, CleanError)
188                else traceback.format_exc()
189            )
190            _print(msg, color=(1.0, 1.0, 0.3))
191            _print(f'Failed in {duration:.2f}s.', color=(1, 0, 0))
192            have_error[0] = True
193            return False
194
195    try:
196        plus = bui.app.plus
197        assert plus is not None
198
199        assert bui.app.classic is not None
200
201        _print(
202            f'Running network diagnostics...\n'
203            f'ua: {bui.app.classic.legacy_user_agent_string}\n'
204            f'time: {utc_now()}.'
205        )
206
207        if bool(False):
208            _print('\nRunning dummy success test...')
209            _print_test_results(_dummy_success)
210
211            _print('\nRunning dummy fail test...')
212            _print_test_results(_dummy_fail)
213
214        # V1 ping
215        baseaddr = plus.get_master_server_address(source=0, version=1)
216        _print(f'\nContacting V1 master-server src0 ({baseaddr})...')
217        v1worked = _print_test_results(lambda: _test_fetch(baseaddr))
218
219        # V1 alternate ping (only if primary fails since this often fails).
220        if v1worked:
221            _print('\nSkipping V1 master-server src1 test since src0 worked.')
222        else:
223            baseaddr = plus.get_master_server_address(source=1, version=1)
224            _print(f'\nContacting V1 master-server src1 ({baseaddr})...')
225            _print_test_results(lambda: _test_fetch(baseaddr))
226
227        if 'none succeeded' in bui.app.net.v1_test_log:
228            _print(
229                f'\nV1-test-log failed: {bui.app.net.v1_test_log}',
230                color=(1, 0, 0),
231            )
232            have_error[0] = True
233        else:
234            _print(f'\nV1-test-log ok: {bui.app.net.v1_test_log}')
235
236        for srcid, result in sorted(bui.app.net.v1_ctest_results.items()):
237            _print(f'\nV1 src{srcid} result: {result}')
238
239        curv1addr = plus.get_master_server_address(version=1)
240        _print(f'\nUsing V1 address: {curv1addr}')
241
242        _print('\nRunning V1 transaction...')
243        _print_test_results(_test_v1_transaction)
244
245        # V2 ping
246        baseaddr = plus.get_master_server_address(version=2)
247        _print(f'\nContacting V2 master-server ({baseaddr})...')
248        _print_test_results(lambda: _test_fetch(baseaddr))
249
250        _print('\nComparing local time to V2 server...')
251        _print_test_results(_test_v2_time)
252
253        # Get V2 nearby zone
254        with bui.app.net.zone_pings_lock:
255            zone_pings = copy.deepcopy(bui.app.net.zone_pings)
256        nearest_zone = (
257            None
258            if not zone_pings
259            else sorted(zone_pings.items(), key=lambda i: i[1])[0]
260        )
261
262        if nearest_zone is not None:
263            nearstr = f'{nearest_zone[0]}: {nearest_zone[1]:.0f}ms'
264        else:
265            nearstr = '-'
266        _print(f'\nChecking nearest V2 zone ping ({nearstr})...')
267        _print_test_results(lambda: _test_nearby_zone_ping(nearest_zone))
268
269        _print('\nSending V2 cloud message...')
270        _print_test_results(_test_v2_cloud_message)
271
272        if have_error[0]:
273            _print(
274                '\nDiagnostics complete. Some diagnostics failed.',
275                color=(10, 0, 0),
276            )
277        else:
278            _print(
279                '\nDiagnostics complete. Everything looks good!',
280                color=(0, 1, 0),
281            )
282    except Exception:
283        import traceback
284
285        _print(
286            f'An unexpected error occurred during testing;'
287            f' please report this.\n'
288            f'{traceback.format_exc()}',
289            color=(1, 0, 0),
290        )
291
292
293def _dummy_success() -> None:
294    """Dummy success test."""
295    time.sleep(1.2)
296
297
298def _dummy_fail() -> None:
299    """Dummy fail test case."""
300    raise RuntimeError('fail-test')
301
302
303def _test_v1_transaction() -> None:
304    """Dummy fail test case."""
305    plus = bui.app.plus
306    assert plus is not None
307
308    if plus.get_v1_account_state() != 'signed_in':
309        raise RuntimeError('Not signed in.')
310
311    starttime = time.monotonic()
312
313    # Gets set to True on success or string on error.
314    results: list[Any] = [False]
315
316    def _cb(cbresults: Any) -> None:
317        # Simply set results here; our other thread acts on them.
318        if not isinstance(cbresults, dict) or 'party_code' not in cbresults:
319            results[0] = 'Unexpected transaction response'
320            return
321        results[0] = True  # Success!
322
323    def _do_it() -> None:
324        assert plus is not None
325        # Fire off a transaction with a callback.
326        plus.add_v1_account_transaction(
327            {
328                'type': 'PRIVATE_PARTY_QUERY',
329                'expire_time': time.time() + 20,
330            },
331            callback=_cb,
332        )
333        plus.run_v1_account_transactions()
334
335    bui.pushcall(_do_it, from_other_thread=True)
336
337    while results[0] is False:
338        time.sleep(0.01)
339        if time.monotonic() - starttime > MAX_TEST_SECONDS:
340            raise RuntimeError(
341                f'test timed out after {MAX_TEST_SECONDS} seconds'
342            )
343
344    # If we got left a string, its an error.
345    if isinstance(results[0], str):
346        raise RuntimeError(results[0])
347
348
349def _test_v2_cloud_message() -> None:
350    from dataclasses import dataclass
351    import bacommon.cloud
352
353    @dataclass
354    class _Results:
355        errstr: str | None = None
356        send_time: float | None = None
357        response_time: float | None = None
358
359    results = _Results()
360
361    def _cb(response: bacommon.cloud.PingResponse | Exception) -> None:
362        # Note: this runs in another thread so need to avoid exceptions.
363        results.response_time = time.monotonic()
364        if isinstance(response, Exception):
365            results.errstr = str(response)
366        if not isinstance(response, bacommon.cloud.PingResponse):
367            results.errstr = f'invalid response type: {type(response)}.'
368
369    def _send() -> None:
370        # Note: this runs in another thread so need to avoid exceptions.
371        results.send_time = time.monotonic()
372        assert bui.app.plus is not None
373        bui.app.plus.cloud.send_message_cb(bacommon.cloud.PingMessage(), _cb)
374
375    # This stuff expects to be run from the logic thread.
376    bui.pushcall(_send, from_other_thread=True)
377
378    wait_start_time = time.monotonic()
379    while True:
380        if results.response_time is not None:
381            break
382        time.sleep(0.01)
383        if time.monotonic() - wait_start_time > MAX_TEST_SECONDS:
384            raise RuntimeError(
385                f'Timeout ({MAX_TEST_SECONDS} seconds)'
386                f' waiting for cloud message response'
387            )
388    if results.errstr is not None:
389        raise RuntimeError(results.errstr)
390
391
392def _test_v2_time() -> None:
393    offset = bui.app.net.server_time_offset_hours
394    if offset is None:
395        raise RuntimeError(
396            'no time offset found;'
397            ' perhaps unable to communicate with v2 server?'
398        )
399    if abs(offset) >= 2.0:
400        raise CleanError(
401            f'Your device time is off from world time by {offset:.1f} hours.\n'
402            'This may cause network operations to fail due to your device\n'
403            ' incorrectly treating SSL certificates as not-yet-valid, etc.\n'
404            'Check your device time and time-zone settings to fix this.\n'
405        )
406
407
408def _test_fetch(baseaddr: str) -> None:
409    # pylint: disable=consider-using-with
410    import urllib.request
411
412    assert bui.app.classic is not None
413    response = urllib.request.urlopen(
414        urllib.request.Request(
415            f'{baseaddr}/ping',
416            None,
417            {'User-Agent': bui.app.classic.legacy_user_agent_string},
418        ),
419        context=bui.app.net.sslcontext,
420        timeout=MAX_TEST_SECONDS,
421    )
422    if response.getcode() != 200:
423        raise RuntimeError(
424            f'Got unexpected response code {response.getcode()}.'
425        )
426    data = response.read()
427    if data != b'pong':
428        raise RuntimeError('Got unexpected response data.')
429
430
431def _test_nearby_zone_ping(nearest_zone: tuple[str, float] | None) -> None:
432    """Try to ping nearest v2 zone."""
433    if nearest_zone is None:
434        raise RuntimeError('No nearest zone.')
435    if nearest_zone[1] > 500:
436        raise RuntimeError('Ping too high.')
437
438
439class NetValTestingWindow(TestingWindow):
440    """Window to test network related settings."""
441
442    def __init__(self, transition: str = 'in_right'):
443        entries = [
444            {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
445            {
446                'name': 'delaySampling',
447                'label': 'Delay Sampling',
448                'increment': 1.0,
449            },
450            {
451                'name': 'dynamicsSyncTime',
452                'label': 'Dynamics Sync Time',
453                'increment': 10,
454            },
455            {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
456        ]
457        super().__init__(
458            title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
459            entries=entries,
460            transition=transition,
461            back_call=lambda: NetTestingWindow(transition='in_left'),
462        )
MAX_TEST_SECONDS = 120
class NetTestingWindow(bauiv1._uitypes.Window):
 26class NetTestingWindow(bui.Window):
 27    """Window that runs a networking test suite to help diagnose issues."""
 28
 29    def __init__(self, transition: str = 'in_right'):
 30        self._width = 820
 31        self._height = 500
 32        self._printed_lines: list[str] = []
 33        assert bui.app.classic is not None
 34        uiscale = bui.app.ui_v1.uiscale
 35        super().__init__(
 36            root_widget=bui.containerwidget(
 37                size=(self._width, self._height),
 38                scale=(
 39                    1.56
 40                    if uiscale is bui.UIScale.SMALL
 41                    else 1.2
 42                    if uiscale is bui.UIScale.MEDIUM
 43                    else 0.8
 44                ),
 45                stack_offset=(0.0, -7 if uiscale is bui.UIScale.SMALL else 0.0),
 46                transition=transition,
 47            )
 48        )
 49        self._done_button = bui.buttonwidget(
 50            parent=self._root_widget,
 51            position=(40, self._height - 77),
 52            size=(120, 60),
 53            scale=0.8,
 54            autoselect=True,
 55            label=bui.Lstr(resource='doneText'),
 56            on_activate_call=self._done,
 57        )
 58
 59        self._copy_button = bui.buttonwidget(
 60            parent=self._root_widget,
 61            position=(self._width - 200, self._height - 77),
 62            size=(100, 60),
 63            scale=0.8,
 64            autoselect=True,
 65            label=bui.Lstr(resource='copyText'),
 66            on_activate_call=self._copy,
 67        )
 68
 69        self._settings_button = bui.buttonwidget(
 70            parent=self._root_widget,
 71            position=(self._width - 100, self._height - 77),
 72            size=(60, 60),
 73            scale=0.8,
 74            autoselect=True,
 75            label=bui.Lstr(value='...'),
 76            on_activate_call=self._show_val_testing,
 77        )
 78
 79        twidth = self._width - 450
 80        bui.textwidget(
 81            parent=self._root_widget,
 82            position=(self._width * 0.5, self._height - 55),
 83            size=(0, 0),
 84            text=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
 85            color=(0.8, 0.8, 0.8, 1.0),
 86            h_align='center',
 87            v_align='center',
 88            maxwidth=twidth,
 89        )
 90
 91        self._scroll = bui.scrollwidget(
 92            parent=self._root_widget,
 93            position=(50, 50),
 94            size=(self._width - 100, self._height - 140),
 95            capture_arrows=True,
 96            autoselect=True,
 97        )
 98        self._rows = bui.columnwidget(parent=self._scroll)
 99
100        bui.containerwidget(
101            edit=self._root_widget, cancel_button=self._done_button
102        )
103
104        # Now kick off the tests.
105        # Pass a weak-ref to this window so we don't keep it alive
106        # if we back out before it completes. Also set is as daemon
107        # so it doesn't keep the app running if the user is trying to quit.
108        Thread(
109            daemon=True,
110            target=bui.Call(_run_diagnostics, weakref.ref(self)),
111        ).start()
112
113    def print(self, text: str, color: tuple[float, float, float]) -> None:
114        """Print text to our console thingie."""
115        for line in text.splitlines():
116            txt = bui.textwidget(
117                parent=self._rows,
118                color=color,
119                text=line,
120                scale=0.75,
121                flatness=1.0,
122                shadow=0.0,
123                size=(0, 20),
124            )
125            bui.containerwidget(edit=self._rows, visible_child=txt)
126            self._printed_lines.append(line)
127
128    def _copy(self) -> None:
129        if not bui.clipboard_is_supported():
130            bui.screenmessage(
131                'Clipboard not supported on this platform.', color=(1, 0, 0)
132            )
133            return
134        bui.clipboard_set_text('\n'.join(self._printed_lines))
135        bui.screenmessage(f'{len(self._printed_lines)} lines copied.')
136
137    def _show_val_testing(self) -> None:
138        assert bui.app.classic is not None
139        bui.app.ui_v1.set_main_menu_window(
140            NetValTestingWindow().get_root_widget()
141        )
142        bui.containerwidget(edit=self._root_widget, transition='out_left')
143
144    def _done(self) -> None:
145        # pylint: disable=cyclic-import
146        from bauiv1lib.settings.advanced import AdvancedSettingsWindow
147
148        assert bui.app.classic is not None
149        bui.app.ui_v1.set_main_menu_window(
150            AdvancedSettingsWindow(transition='in_left').get_root_widget()
151        )
152        bui.containerwidget(edit=self._root_widget, transition='out_right')

Window that runs a networking test suite to help diagnose issues.

NetTestingWindow(transition: str = 'in_right')
 29    def __init__(self, transition: str = 'in_right'):
 30        self._width = 820
 31        self._height = 500
 32        self._printed_lines: list[str] = []
 33        assert bui.app.classic is not None
 34        uiscale = bui.app.ui_v1.uiscale
 35        super().__init__(
 36            root_widget=bui.containerwidget(
 37                size=(self._width, self._height),
 38                scale=(
 39                    1.56
 40                    if uiscale is bui.UIScale.SMALL
 41                    else 1.2
 42                    if uiscale is bui.UIScale.MEDIUM
 43                    else 0.8
 44                ),
 45                stack_offset=(0.0, -7 if uiscale is bui.UIScale.SMALL else 0.0),
 46                transition=transition,
 47            )
 48        )
 49        self._done_button = bui.buttonwidget(
 50            parent=self._root_widget,
 51            position=(40, self._height - 77),
 52            size=(120, 60),
 53            scale=0.8,
 54            autoselect=True,
 55            label=bui.Lstr(resource='doneText'),
 56            on_activate_call=self._done,
 57        )
 58
 59        self._copy_button = bui.buttonwidget(
 60            parent=self._root_widget,
 61            position=(self._width - 200, self._height - 77),
 62            size=(100, 60),
 63            scale=0.8,
 64            autoselect=True,
 65            label=bui.Lstr(resource='copyText'),
 66            on_activate_call=self._copy,
 67        )
 68
 69        self._settings_button = bui.buttonwidget(
 70            parent=self._root_widget,
 71            position=(self._width - 100, self._height - 77),
 72            size=(60, 60),
 73            scale=0.8,
 74            autoselect=True,
 75            label=bui.Lstr(value='...'),
 76            on_activate_call=self._show_val_testing,
 77        )
 78
 79        twidth = self._width - 450
 80        bui.textwidget(
 81            parent=self._root_widget,
 82            position=(self._width * 0.5, self._height - 55),
 83            size=(0, 0),
 84            text=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
 85            color=(0.8, 0.8, 0.8, 1.0),
 86            h_align='center',
 87            v_align='center',
 88            maxwidth=twidth,
 89        )
 90
 91        self._scroll = bui.scrollwidget(
 92            parent=self._root_widget,
 93            position=(50, 50),
 94            size=(self._width - 100, self._height - 140),
 95            capture_arrows=True,
 96            autoselect=True,
 97        )
 98        self._rows = bui.columnwidget(parent=self._scroll)
 99
100        bui.containerwidget(
101            edit=self._root_widget, cancel_button=self._done_button
102        )
103
104        # Now kick off the tests.
105        # Pass a weak-ref to this window so we don't keep it alive
106        # if we back out before it completes. Also set is as daemon
107        # so it doesn't keep the app running if the user is trying to quit.
108        Thread(
109            daemon=True,
110            target=bui.Call(_run_diagnostics, weakref.ref(self)),
111        ).start()
def print(self, text: str, color: tuple[float, float, float]) -> None:
113    def print(self, text: str, color: tuple[float, float, float]) -> None:
114        """Print text to our console thingie."""
115        for line in text.splitlines():
116            txt = bui.textwidget(
117                parent=self._rows,
118                color=color,
119                text=line,
120                scale=0.75,
121                flatness=1.0,
122                shadow=0.0,
123                size=(0, 20),
124            )
125            bui.containerwidget(edit=self._rows, visible_child=txt)
126            self._printed_lines.append(line)

Print text to our console thingie.

Inherited Members
bauiv1._uitypes.Window
get_root_widget
class NetValTestingWindow(bauiv1lib.settings.testing.TestingWindow):
440class NetValTestingWindow(TestingWindow):
441    """Window to test network related settings."""
442
443    def __init__(self, transition: str = 'in_right'):
444        entries = [
445            {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
446            {
447                'name': 'delaySampling',
448                'label': 'Delay Sampling',
449                'increment': 1.0,
450            },
451            {
452                'name': 'dynamicsSyncTime',
453                'label': 'Dynamics Sync Time',
454                'increment': 10,
455            },
456            {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
457        ]
458        super().__init__(
459            title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
460            entries=entries,
461            transition=transition,
462            back_call=lambda: NetTestingWindow(transition='in_left'),
463        )

Window to test network related settings.

NetValTestingWindow(transition: str = 'in_right')
443    def __init__(self, transition: str = 'in_right'):
444        entries = [
445            {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
446            {
447                'name': 'delaySampling',
448                'label': 'Delay Sampling',
449                'increment': 1.0,
450            },
451            {
452                'name': 'dynamicsSyncTime',
453                'label': 'Dynamics Sync Time',
454                'increment': 10,
455            },
456            {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
457        ]
458        super().__init__(
459            title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
460            entries=entries,
461            transition=transition,
462            back_call=lambda: NetTestingWindow(transition='in_left'),
463        )
Inherited Members
bauiv1._uitypes.Window
get_root_widget