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

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

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

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
131    @override
132    def get_main_window_state(self) -> bui.MainWindowState:
133        # Support recreating our window for back/refresh purposes.
134        cls = type(self)
135        return bui.BasicMainWindowState(
136            create_call=lambda transition, origin_widget: cls(
137                transition=transition, origin_widget=origin_widget
138            )
139        )

Return a WindowState to recreate this window, if supported.

def print(self, text: str, color: tuple[float, float, float]) -> None:
141    def print(self, text: str, color: tuple[float, float, float]) -> None:
142        """Print text to our console thingie."""
143        for line in text.splitlines():
144            txt = bui.textwidget(
145                parent=self._rows,
146                color=color,
147                text=line,
148                scale=0.75,
149                flatness=1.0,
150                shadow=0.0,
151                size=(0, 20),
152            )
153            bui.containerwidget(edit=self._rows, visible_child=txt)
154            self._printed_lines.append(line)

Print text to our console thingie.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_close
can_change_main_window
main_window_back
main_window_replace
on_main_window_close
bauiv1._uitypes.Window
get_root_widget
class NetValTestingWindow(bauiv1lib.settings.testing.TestingWindow):
481class NetValTestingWindow(TestingWindow):
482    """Window to test network related settings."""
483
484    def __init__(self, transition: str = 'in_right'):
485        entries = [
486            {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
487            {
488                'name': 'delaySampling',
489                'label': 'Delay Sampling',
490                'increment': 1.0,
491            },
492            {
493                'name': 'dynamicsSyncTime',
494                'label': 'Dynamics Sync Time',
495                'increment': 10,
496            },
497            {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
498        ]
499        super().__init__(
500            title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
501            entries=entries,
502            transition=transition,
503        )

Window to test network related settings.

NetValTestingWindow(transition: str = 'in_right')
484    def __init__(self, transition: str = 'in_right'):
485        entries = [
486            {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
487            {
488                'name': 'delaySampling',
489                'label': 'Delay Sampling',
490                'increment': 1.0,
491            },
492            {
493                'name': 'dynamicsSyncTime',
494                'label': 'Dynamics Sync Time',
495                'increment': 10,
496            },
497            {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
498        ]
499        super().__init__(
500            title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
501            entries=entries,
502            transition=transition,
503        )

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_close
can_change_main_window
main_window_back
main_window_replace
on_main_window_close
get_main_window_state
bauiv1._uitypes.Window
get_root_widget