Tests: Add UI tests that verify loading all default workspaces
While individual modes have UI tests related to undo, this new set of tests in this new file is intended to be a set of very broad sanity tests that catch the most egregious errors that cause crashing on start up, whether due to python errors, UI rendering issues, or otherwise. Running these tests takes approximately 4 seconds currently as it adds and verifies the loading of each of the workspaces available "out of the box" to a blender user. Pull Request: https://projects.blender.org/blender/blender/pulls/139318
This commit is contained in:
@@ -73,8 +73,8 @@ function(add_blender_test_io testname)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
if(WITH_UI_TESTS)
|
if(WITH_UI_TESTS)
|
||||||
|
set(_blender_headless_env_vars "BLENDER_BIN=${TEST_BLENDER_EXE}")
|
||||||
if(WITH_UI_TESTS_HEADLESS)
|
if(WITH_UI_TESTS_HEADLESS)
|
||||||
set(_blender_headless_env_vars "BLENDER_BIN=${TEST_BLENDER_EXE}")
|
|
||||||
|
|
||||||
# Currently only WAYLAND is supported, support for others may be added later.
|
# Currently only WAYLAND is supported, support for others may be added later.
|
||||||
# In this case none of the WESTON environment variables will be used.
|
# In this case none of the WESTON environment variables will be used.
|
||||||
@@ -104,43 +104,29 @@ if(WITH_UI_TESTS)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
function(add_blender_test_ui testname)
|
|
||||||
# Remove `--background` so headless execution uses a GUI
|
|
||||||
# (within a headless graphical environment).
|
|
||||||
set(EXE_PARAMS ${TEST_BLENDER_EXE_PARAMS})
|
|
||||||
list(REMOVE_ITEM EXE_PARAMS --background)
|
|
||||||
add_blender_test_impl(
|
|
||||||
"${testname}"
|
|
||||||
"${_blender_headless_env_vars}"
|
|
||||||
"${TEST_PYTHON_EXE}"
|
|
||||||
"${CMAKE_SOURCE_DIR}/tests/utils/blender_headless.py"
|
|
||||||
# NOTE: attempting to maximize the window causes problems with a headless `weston`,
|
|
||||||
# while this could be investigated, use windowed mode instead.
|
|
||||||
# Use a window size that balances software GPU rendering with enough room to use the UI.
|
|
||||||
--factory-startup
|
|
||||||
-p 0 0 800 600
|
|
||||||
"${EXE_PARAMS}"
|
|
||||||
"${ARGN}"
|
|
||||||
)
|
|
||||||
endfunction()
|
|
||||||
else()
|
else()
|
||||||
function(add_blender_test_ui testname)
|
list(APPEND _blender_headless_env_vars
|
||||||
# Remove `--background`
|
"PASS_THROUGH=1"
|
||||||
set(EXE_PARAMS ${TEST_BLENDER_EXE_PARAMS})
|
)
|
||||||
list(REMOVE_ITEM EXE_PARAMS --background)
|
|
||||||
|
|
||||||
add_blender_test_impl(
|
|
||||||
"${testname}"
|
|
||||||
""
|
|
||||||
"${TEST_BLENDER_EXE}"
|
|
||||||
--factory-startup
|
|
||||||
-p 0 0 800 600
|
|
||||||
${EXE_PARAMS}
|
|
||||||
${ARGN}
|
|
||||||
)
|
|
||||||
endfunction()
|
|
||||||
endif()
|
endif()
|
||||||
|
function(add_blender_test_ui testname)
|
||||||
|
# Remove `--background`a
|
||||||
|
set(EXE_PARAMS ${TEST_BLENDER_EXE_PARAMS})
|
||||||
|
list(REMOVE_ITEM EXE_PARAMS --background)
|
||||||
|
add_blender_test_impl(
|
||||||
|
"${testname}"
|
||||||
|
"${_blender_headless_env_vars}"
|
||||||
|
"${TEST_PYTHON_EXE}"
|
||||||
|
"${CMAKE_SOURCE_DIR}/tests/utils/blender_headless.py"
|
||||||
|
# NOTE: attempting to maximize the window causes problems with a headless `weston`,
|
||||||
|
# while this could be investigated, use windowed mode instead.
|
||||||
|
# Use a window size that balances software GPU rendering with enough room to use the UI.
|
||||||
|
--factory-startup
|
||||||
|
-p 0 0 800 600
|
||||||
|
"${EXE_PARAMS}"
|
||||||
|
"${ARGN}"
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Run Python script outside Blender.
|
# Run Python script outside Blender.
|
||||||
@@ -1298,7 +1284,12 @@ if(WITH_UI_TESTS)
|
|||||||
# This could be generated with:
|
# This could be generated with:
|
||||||
# `"${TEST_PYTHON_EXE}" "${CMAKE_CURRENT_LIST_DIR}/ui_simulate/run.py" --list-tests`
|
# `"${TEST_PYTHON_EXE}" "${CMAKE_CURRENT_LIST_DIR}/ui_simulate/run.py" --list-tests`
|
||||||
# list explicitly so changes bisecting/updated are sure to re-run CMake.
|
# list explicitly so changes bisecting/updated are sure to re-run CMake.
|
||||||
set(_undo_tests
|
set(_ui_tests
|
||||||
|
test_workspace.sanity_check_general
|
||||||
|
test_workspace.sanity_check_2d_animation
|
||||||
|
test_workspace.sanity_check_sculpting
|
||||||
|
test_workspace.sanity_check_vfx
|
||||||
|
test_workspace.sanity_check_video_editing
|
||||||
test_undo.text_editor_edit_mode_mix
|
test_undo.text_editor_edit_mode_mix
|
||||||
test_undo.text_editor_simple
|
test_undo.text_editor_simple
|
||||||
test_undo.view3d_edit_mode_multi_window
|
test_undo.view3d_edit_mode_multi_window
|
||||||
@@ -1316,7 +1307,7 @@ if(WITH_UI_TESTS)
|
|||||||
test_undo.view3d_texture_paint_complex
|
test_undo.view3d_texture_paint_complex
|
||||||
test_undo.view3d_texture_paint_simple
|
test_undo.view3d_texture_paint_simple
|
||||||
)
|
)
|
||||||
foreach(ui_test ${_undo_tests})
|
foreach(ui_test ${_ui_tests})
|
||||||
add_blender_test_ui(
|
add_blender_test_ui(
|
||||||
"ui_${ui_test}"
|
"ui_${ui_test}"
|
||||||
--enable-event-simulate
|
--enable-event-simulate
|
||||||
@@ -1325,7 +1316,7 @@ if(WITH_UI_TESTS)
|
|||||||
--tests "${ui_test}"
|
--tests "${ui_test}"
|
||||||
)
|
)
|
||||||
endforeach()
|
endforeach()
|
||||||
unset(_undo_tests)
|
unset(_ui_tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ For an editor to follow the tests:
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
def create_parser():
|
def create_parser():
|
||||||
@@ -137,7 +138,7 @@ def _process_test_id_fn(env, args, test_id):
|
|||||||
return test_id, callproc.returncode == 0
|
return test_id, callproc.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def run(empty_user_dir):
|
||||||
directory = os.path.dirname(__file__)
|
directory = os.path.dirname(__file__)
|
||||||
if "--list-tests" in sys.argv:
|
if "--list-tests" in sys.argv:
|
||||||
list_tests(directory)
|
list_tests(directory)
|
||||||
@@ -164,6 +165,7 @@ def main():
|
|||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env.update({
|
env.update({
|
||||||
"LSAN_OPTIONS": "exitcode=0",
|
"LSAN_OPTIONS": "exitcode=0",
|
||||||
|
"BLENDER_USER_RESOURCES": empty_user_dir,
|
||||||
})
|
})
|
||||||
|
|
||||||
# We could support multiple tests per Blender session.
|
# We could support multiple tests per Blender session.
|
||||||
@@ -188,6 +190,13 @@ def main():
|
|||||||
for test_id, ok in results:
|
for test_id, ok in results:
|
||||||
print("OK: " if ok else "FAIL:", test_id)
|
print("OK: " if ok else "FAIL:", test_id)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with tempfile.TemporaryDirectory() as empty_user_dir:
|
||||||
|
sys.exit(run(empty_user_dir))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
174
tests/python/ui_simulate/test_workspace.py
Normal file
174
tests/python/ui_simulate/test_workspace.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2025 Blender Authors
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file does not run anything, it's methods are accessed for tests by: ``run.py``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _test_window(windows_exclude=None):
|
||||||
|
import bpy
|
||||||
|
wm = bpy.data.window_managers[0]
|
||||||
|
if windows_exclude is None:
|
||||||
|
return wm.windows[0]
|
||||||
|
for window in wm.windows:
|
||||||
|
if window not in windows_exclude:
|
||||||
|
return window
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _test_vars(window):
|
||||||
|
import unittest
|
||||||
|
from modules.easy_keys import EventGenerate
|
||||||
|
return (
|
||||||
|
EventGenerate(window),
|
||||||
|
unittest.TestCase(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _call_by_name(e, text: str):
|
||||||
|
yield e.f3()
|
||||||
|
yield e.text(text)
|
||||||
|
yield e.ret()
|
||||||
|
|
||||||
|
|
||||||
|
def _call_menu(e, text: str):
|
||||||
|
yield e.f3()
|
||||||
|
yield e.text_unicode(text.replace(" -> ", " \u25b8 "))
|
||||||
|
yield e.ret()
|
||||||
|
|
||||||
|
|
||||||
|
def sanity_check_general():
|
||||||
|
e, t = _test_vars(window := _test_window())
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.a() # Animation
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Animation")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.c() # Compositing
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Compositing")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.g() # Geometry Nodes
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Geometry Nodes")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.l() # Layout
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Layout")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.m() # Modeling
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Modeling")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.r() # Rendering
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.s() # Scripting
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Scripting")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.p() # Sculpting
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Sculpting")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.h() # Shading
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Shading")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.t() # Texture Paint
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Texture Paint")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.g() # General
|
||||||
|
yield e.u() # UV Editing
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "UV Editing")
|
||||||
|
|
||||||
|
|
||||||
|
def sanity_check_2d_animation():
|
||||||
|
e, t = _test_vars(window := _test_window())
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.d() # 2D Animation
|
||||||
|
yield e.d() # 2D Animation
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "2D Animation")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.d() # 2D Animation
|
||||||
|
yield e.f() # 2D Full Canvas
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "2D Full Canvas")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.d() # 2D Animation
|
||||||
|
yield e.c() # Compositing
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Compositing")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.d() # 2D Animation
|
||||||
|
yield e.r() # Rendering
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
|
||||||
|
|
||||||
|
|
||||||
|
def sanity_check_sculpting():
|
||||||
|
e, t = _test_vars(window := _test_window())
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.s() # Sculpting
|
||||||
|
yield e.s() # Sculpting
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Sculpting")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.s() # Sculpting
|
||||||
|
yield e.h() # Shading
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Shading")
|
||||||
|
|
||||||
|
|
||||||
|
def sanity_check_vfx():
|
||||||
|
e, t = _test_vars(window := _test_window())
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.v() # VFX
|
||||||
|
yield e.c() # Compositing
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Compositing")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.v() # VFX
|
||||||
|
yield e.m() # Masking
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Masking")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.v() # VFX
|
||||||
|
yield e.t() # Motion Tracking
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Motion Tracking")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.v() # VFX
|
||||||
|
yield e.r() # Rendering
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
|
||||||
|
|
||||||
|
|
||||||
|
def sanity_check_video_editing():
|
||||||
|
e, t = _test_vars(window := _test_window())
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.e() # Video Editing
|
||||||
|
yield e.r() # Rendering
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
|
||||||
|
|
||||||
|
yield from _call_by_name(e, "Add Workspace")
|
||||||
|
yield e.e() # Video Editing
|
||||||
|
yield e.v() # Video Editing
|
||||||
|
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Video Editing")
|
||||||
@@ -27,6 +27,9 @@ Environment Variables:
|
|||||||
where the headless session doesn't define a seat.
|
where the headless session doesn't define a seat.
|
||||||
- ``USE_DEBUG``: When nonzero:
|
- ``USE_DEBUG``: When nonzero:
|
||||||
Run Blender in a debugger.
|
Run Blender in a debugger.
|
||||||
|
- ``PASS_THROUGH``: When nonzero:
|
||||||
|
Don't start a display server to run Blender in.
|
||||||
|
It's useful to execute Blender from this wrapper script to provide additional control of the environment.
|
||||||
|
|
||||||
WAYLAND Environment Variables:
|
WAYLAND Environment Variables:
|
||||||
|
|
||||||
@@ -67,6 +70,9 @@ def environ_nonzero(var: str) -> bool:
|
|||||||
|
|
||||||
BLENDER_BIN = os.environ.get("BLENDER_BIN", "blender")
|
BLENDER_BIN = os.environ.get("BLENDER_BIN", "blender")
|
||||||
|
|
||||||
|
# Skips starting a display server, run Blender in the user's environment.
|
||||||
|
PASS_THROUGH = environ_nonzero("PASS_THROUGH")
|
||||||
|
|
||||||
# For debugging, print out all information.
|
# For debugging, print out all information.
|
||||||
VERBOSE = environ_nonzero("VERBOSE")
|
VERBOSE = environ_nonzero("VERBOSE")
|
||||||
|
|
||||||
@@ -98,6 +104,33 @@ class backend_base:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class backend_passthrough(backend_base):
|
||||||
|
@staticmethod
|
||||||
|
def run(blender_args: Sequence[str]) -> int:
|
||||||
|
with tempfile.TemporaryDirectory() as empty_user_dir:
|
||||||
|
blender_env = {**os.environ, "BLENDER_USER_RESOURCES": empty_user_dir}
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
# "strace", # Can be useful for debugging any startup issues.
|
||||||
|
BLENDER_BIN,
|
||||||
|
*blender_args,
|
||||||
|
]
|
||||||
|
|
||||||
|
if USE_DEBUG:
|
||||||
|
cmd = ["gdb", BLENDER_BIN, "--ex=run", "--args", *cmd]
|
||||||
|
|
||||||
|
if VERBOSE:
|
||||||
|
print("Env:", blender_env)
|
||||||
|
print("Run:", cmd)
|
||||||
|
with subprocess.Popen(cmd, env=blender_env) as proc_blender:
|
||||||
|
proc_blender.communicate()
|
||||||
|
blender_exit_code = proc_blender.returncode
|
||||||
|
del cmd
|
||||||
|
|
||||||
|
# Forward Blender's exit code.
|
||||||
|
return blender_exit_code
|
||||||
|
|
||||||
|
|
||||||
class backend_wayland(backend_base):
|
class backend_wayland(backend_base):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _wait_for_wayland_server(*, socket: str, timeout: float) -> bool:
|
def _wait_for_wayland_server(*, socket: str, timeout: float) -> bool:
|
||||||
@@ -303,29 +336,29 @@ class backend_wayland(backend_base):
|
|||||||
# Wait for the interrupt to be handled.
|
# Wait for the interrupt to be handled.
|
||||||
proc_server.communicate()
|
proc_server.communicate()
|
||||||
return 1
|
return 1
|
||||||
|
with tempfile.TemporaryDirectory() as empty_user_dir:
|
||||||
|
blender_env = {**os.environ, "WAYLAND_DISPLAY": socket, "BLENDER_USER_RESOURCES": empty_user_dir}
|
||||||
|
|
||||||
blender_env = {**os.environ, "WAYLAND_DISPLAY": socket}
|
# Needed so Blender can find WAYLAND libraries such as `libwayland-cursor.so`.
|
||||||
|
if weston_env is not None and "LD_LIBRARY_PATH" in weston_env:
|
||||||
|
blender_env["LD_LIBRARY_PATH"] = weston_env["LD_LIBRARY_PATH"]
|
||||||
|
|
||||||
# Needed so Blender can find WAYLAND libraries such as `libwayland-cursor.so`.
|
cmd = [
|
||||||
if weston_env is not None and "LD_LIBRARY_PATH" in weston_env:
|
# "strace", # Can be useful for debugging any startup issues.
|
||||||
blender_env["LD_LIBRARY_PATH"] = weston_env["LD_LIBRARY_PATH"]
|
BLENDER_BIN,
|
||||||
|
*blender_args,
|
||||||
|
]
|
||||||
|
|
||||||
cmd = [
|
if USE_DEBUG:
|
||||||
# "strace", # Can be useful for debugging any startup issues.
|
cmd = ["gdb", BLENDER_BIN, "--ex=run", "--args", *cmd]
|
||||||
BLENDER_BIN,
|
|
||||||
*blender_args,
|
|
||||||
]
|
|
||||||
|
|
||||||
if USE_DEBUG:
|
if VERBOSE:
|
||||||
cmd = ["gdb", BLENDER_BIN, "--ex=run", "--args", *cmd]
|
print("Env:", blender_env)
|
||||||
|
print("Run:", cmd)
|
||||||
if VERBOSE:
|
with subprocess.Popen(cmd, env=blender_env) as proc_blender:
|
||||||
print("Env:", blender_env)
|
proc_blender.communicate()
|
||||||
print("Run:", cmd)
|
blender_exit_code = proc_blender.returncode
|
||||||
with subprocess.Popen(cmd, env=blender_env) as proc_blender:
|
del cmd
|
||||||
proc_blender.communicate()
|
|
||||||
blender_exit_code = proc_blender.returncode
|
|
||||||
del cmd
|
|
||||||
|
|
||||||
# Blender has finished, close the server.
|
# Blender has finished, close the server.
|
||||||
proc_server.send_signal(signal.SIGINT)
|
proc_server.send_signal(signal.SIGINT)
|
||||||
@@ -340,13 +373,17 @@ class backend_wayland(backend_base):
|
|||||||
# Main Function
|
# Main Function
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
match sys.platform:
|
backend: type[backend_base]
|
||||||
case "darwin":
|
if PASS_THROUGH:
|
||||||
backend = backend_base
|
backend = backend_passthrough
|
||||||
case "win32":
|
else:
|
||||||
backend = backend_base
|
match sys.platform:
|
||||||
case _:
|
case "darwin":
|
||||||
backend = backend_wayland
|
backend = backend_base
|
||||||
|
case "win32":
|
||||||
|
backend = backend_base
|
||||||
|
case _:
|
||||||
|
backend = backend_wayland
|
||||||
return backend.run(sys.argv[1:])
|
return backend.run(sys.argv[1:])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user