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

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

Return a WindowState to recreate this window, if supported.

def print(self, text: str, color: tuple[float, float, float]) -> None:
149    def print(self, text: str, color: tuple[float, float, float]) -> None:
150        """Print text to our console thingie."""
151        for line in text.splitlines():
152            txt = bui.textwidget(
153                parent=self._rows,
154                color=color,
155                text=line,
156                scale=0.75,
157                flatness=1.0,
158                shadow=0.0,
159                size=(0, 20),
160            )
161            bui.containerwidget(edit=self._rows, visible_child=txt)
162            self._printed_lines.append(line)

Print text to our console thingie.

def get_net_val_testing_window() -> bauiv1lib.settings.testing.TestingWindow:
472def get_net_val_testing_window() -> TestingWindow:
473    """Create a window for testing net values."""
474    entries = [
475        {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
476        {
477            'name': 'delaySampling',
478            'label': 'Delay Sampling',
479            'increment': 1.0,
480        },
481        {
482            'name': 'dynamicsSyncTime',
483            'label': 'Dynamics Sync Time',
484            'increment': 10,
485        },
486        {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
487    ]
488    return TestingWindow(
489        title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
490        entries=entries,
491    )

Create a window for testing net values.