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

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=(60, 60),
 61            scale=0.9,
 62            label=bui.charstr(bui.SpecialChar.BACK),
 63            button_type='backSmall',
 64            autoselect=True,
 65            on_activate_call=self.main_window_back,
 66        )
 67
 68        # Avoid squads button on small mode.
 69        xinset = -50 if uiscale is bui.UIScale.SMALL else 0
 70
 71        self._copy_button = bui.buttonwidget(
 72            parent=self._root_widget,
 73            position=(self._width - 200 + xinset, self._height - 77),
 74            size=(100, 60),
 75            scale=0.8,
 76            autoselect=True,
 77            label=bui.Lstr(resource='copyText'),
 78            on_activate_call=self._copy,
 79        )
 80
 81        self._settings_button = bui.buttonwidget(
 82            parent=self._root_widget,
 83            position=(self._width - 100 + xinset, self._height - 77),
 84            size=(60, 60),
 85            scale=0.8,
 86            autoselect=True,
 87            label=bui.Lstr(value='...'),
 88            on_activate_call=self._show_val_testing,
 89        )
 90
 91        twidth = self._width - 540
 92        bui.textwidget(
 93            parent=self._root_widget,
 94            position=(self._width * 0.5, self._height - 55),
 95            size=(0, 0),
 96            text=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
 97            color=(0.8, 0.8, 0.8, 1.0),
 98            h_align='center',
 99            v_align='center',
100            maxwidth=twidth,
101        )
102
103        self._scroll = bui.scrollwidget(
104            parent=self._root_widget,
105            position=(50, 50),
106            size=(self._width - 100, self._height - 140),
107            capture_arrows=True,
108            autoselect=True,
109        )
110        self._rows = bui.columnwidget(parent=self._scroll)
111
112        if uiscale is bui.UIScale.SMALL:
113            bui.containerwidget(
114                edit=self._root_widget, on_cancel_call=self.main_window_back
115            )
116            self._done_button.delete()
117            self._done_button = None
118        else:
119            bui.containerwidget(
120                edit=self._root_widget, cancel_button=self._done_button
121            )
122
123        # Now kick off the tests.
124        # Pass a weak-ref to this window so we don't keep it alive
125        # if we back out before it completes. Also set is as daemon
126        # so it doesn't keep the app running if the user is trying to quit.
127        Thread(
128            daemon=True,
129            target=bui.Call(_run_diagnostics, weakref.ref(self)),
130        ).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:
132    @override
133    def get_main_window_state(self) -> bui.MainWindowState:
134        # Support recreating our window for back/refresh purposes.
135        cls = type(self)
136        return bui.BasicMainWindowState(
137            create_call=lambda transition, origin_widget: cls(
138                transition=transition, origin_widget=origin_widget
139            )
140        )

Return a WindowState to recreate this window, if supported.

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

Print text to our console thingie.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_is_top_level
main_window_close
main_window_has_control
main_window_back
main_window_replace
on_main_window_close
bauiv1._uitypes.Window
get_root_widget
class NetValTestingWindow(bauiv1lib.settings.testing.TestingWindow):
465class NetValTestingWindow(TestingWindow):
466    """Window to test network related settings."""
467
468    def __init__(self, transition: str = 'in_right'):
469        entries = [
470            {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
471            {
472                'name': 'delaySampling',
473                'label': 'Delay Sampling',
474                'increment': 1.0,
475            },
476            {
477                'name': 'dynamicsSyncTime',
478                'label': 'Dynamics Sync Time',
479                'increment': 10,
480            },
481            {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
482        ]
483        super().__init__(
484            title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
485            entries=entries,
486            transition=transition,
487        )

Window to test network related settings.

NetValTestingWindow(transition: str = 'in_right')
468    def __init__(self, transition: str = 'in_right'):
469        entries = [
470            {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
471            {
472                'name': 'delaySampling',
473                'label': 'Delay Sampling',
474                'increment': 1.0,
475            },
476            {
477                'name': 'dynamicsSyncTime',
478                'label': 'Dynamics Sync Time',
479                'increment': 10,
480            },
481            {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
482        ]
483        super().__init__(
484            title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
485            entries=entries,
486            transition=transition,
487        )

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_is_top_level
main_window_close
main_window_has_control
main_window_back
main_window_replace
on_main_window_close
get_main_window_state
bauiv1._uitypes.Window
get_root_widget