diff options
Diffstat (limited to 'platform/linuxbsd')
25 files changed, 1264 insertions, 438 deletions
diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index 3c5dc78c60..4dd74ff9d0 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -11,23 +11,30 @@ common_linuxbsd = [ "joypad_linux.cpp", "freedesktop_portal_desktop.cpp", "freedesktop_screensaver.cpp", - "xkbcommon-so_wrap.c", ] +if env["use_sowrap"]: + common_linuxbsd.append("xkbcommon-so_wrap.c") + if env["x11"]: common_linuxbsd += SConscript("x11/SCsub") if env["speechd"]: - common_linuxbsd.append(["speechd-so_wrap.c", "tts_linux.cpp"]) + common_linuxbsd.append("tts_linux.cpp") + if env["use_sowrap"]: + common_linuxbsd.append("speechd-so_wrap.c") if env["fontconfig"]: - common_linuxbsd.append("fontconfig-so_wrap.c") + if env["use_sowrap"]: + common_linuxbsd.append("fontconfig-so_wrap.c") if env["udev"]: - common_linuxbsd.append("libudev-so_wrap.c") + if env["use_sowrap"]: + common_linuxbsd.append("libudev-so_wrap.c") if env["dbus"]: - common_linuxbsd.append("dbus-so_wrap.c") + if env["use_sowrap"]: + common_linuxbsd.append("dbus-so_wrap.c") prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_linuxbsd) diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 36e149f2b4..dadc03685b 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -43,6 +43,7 @@ def get_opts(): BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN)", False), BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False), + BoolVariable("use_sowrap", "Dynamically load system libraries", True), BoolVariable("alsa", "Use ALSA", True), BoolVariable("pulseaudio", "Use PulseAudio", True), BoolVariable("dbus", "Use D-Bus to handle screensaver and portal desktop settings", True), @@ -55,6 +56,16 @@ def get_opts(): ] +def get_doc_classes(): + return [ + "EditorExportPlatformLinuxBSD", + ] + + +def get_doc_path(): + return "doc_classes" + + def get_flags(): return [ ("arch", detect_arch()), @@ -69,7 +80,7 @@ def configure(env: "Environment"): 'Unsupported CPU architecture "%s" for Linux / *BSD. Supported architectures are: %s.' % (env["arch"], ", ".join(supported_arches)) ) - sys.exit() + sys.exit(255) ## Build type @@ -184,25 +195,30 @@ def configure(env: "Environment"): ## Dependencies + if env["use_sowrap"]: + env.Append(CPPDEFINES=["SOWRAP_ENABLED"]) + if env["touch"]: env.Append(CPPDEFINES=["TOUCH_ENABLED"]) # FIXME: Check for existence of the libs before parsing their flags with pkg-config # freetype depends on libpng and zlib, so bundling one of them while keeping others - # as shared libraries leads to weird issues - if ( - env["builtin_freetype"] - or env["builtin_libpng"] - or env["builtin_zlib"] - or env["builtin_graphite"] - or env["builtin_harfbuzz"] - ): - env["builtin_freetype"] = True - env["builtin_libpng"] = True - env["builtin_zlib"] = True - env["builtin_graphite"] = True - env["builtin_harfbuzz"] = True + # as shared libraries leads to weird issues. And graphite and harfbuzz need freetype. + ft_linked_deps = [ + env["builtin_freetype"], + env["builtin_libpng"], + env["builtin_zlib"], + env["builtin_graphite"], + env["builtin_harfbuzz"], + ] + if (not all(ft_linked_deps)) and any(ft_linked_deps): # All or nothing. + print( + "These libraries should be either all builtin, or all system provided:\n" + "freetype, libpng, zlib, graphite, harfbuzz.\n" + "Please specify `builtin_<name>=no` for all of them, or none." + ) + sys.exit(255) if not env["builtin_freetype"]: env.ParseConfig("pkg-config freetype2 --cflags --libs") @@ -210,8 +226,8 @@ def configure(env: "Environment"): if not env["builtin_graphite"]: env.ParseConfig("pkg-config graphite2 --cflags --libs") - if not env["builtin_icu"]: - env.ParseConfig("pkg-config icu-uc --cflags --libs") + if not env["builtin_icu4c"]: + env.ParseConfig("pkg-config icu-i18n icu-uc --cflags --libs") if not env["builtin_harfbuzz"]: env.ParseConfig("pkg-config harfbuzz harfbuzz-icu --cflags --libs") @@ -266,31 +282,93 @@ def configure(env: "Environment"): if not env["builtin_pcre2"]: env.ParseConfig("pkg-config libpcre2-32 --cflags --libs") - if not env["builtin_embree"]: + if not env["builtin_recastnavigation"]: + # No pkgconfig file so far, hardcode default paths. + env.Prepend(CPPPATH=["/usr/include/recastnavigation"]) + env.Append(LIBS=["Recast"]) + + if not env["builtin_embree"] and env["arch"] in ["x86_64", "arm64"]: # No pkgconfig file so far, hardcode expected lib name. env.Append(LIBS=["embree3"]) - ## Flags - if env["fontconfig"]: - env.Append(CPPDEFINES=["FONTCONFIG_ENABLED"]) + if not env["use_sowrap"]: + if os.system("pkg-config --exists fontconfig") == 0: # 0 means found + env.ParseConfig("pkg-config fontconfig --cflags --libs") + env.Append(CPPDEFINES=["FONTCONFIG_ENABLED"]) + else: + print("Warning: fontconfig development libraries not found. Disabling the system fonts support.") + env["fontconfig"] = False + else: + env.Append(CPPDEFINES=["FONTCONFIG_ENABLED"]) if env["alsa"]: - env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"]) + if not env["use_sowrap"]: + if os.system("pkg-config --exists alsa") == 0: # 0 means found + env.ParseConfig("pkg-config alsa --cflags --libs") + env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"]) + else: + print("Warning: ALSA development libraries not found. Disabling the ALSA audio driver.") + env["alsa"] = False + else: + env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"]) if env["pulseaudio"]: - env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED", "_REENTRANT"]) + if not env["use_sowrap"]: + if os.system("pkg-config --exists libpulse") == 0: # 0 means found + env.ParseConfig("pkg-config libpulse --cflags --libs") + env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"]) + else: + print("Warning: PulseAudio development libraries not found. Disabling the PulseAudio audio driver.") + env["pulseaudio"] = False + else: + env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED", "_REENTRANT"]) if env["dbus"]: - env.Append(CPPDEFINES=["DBUS_ENABLED"]) + if not env["use_sowrap"]: + if os.system("pkg-config --exists dbus-1") == 0: # 0 means found + env.ParseConfig("pkg-config dbus-1 --cflags --libs") + env.Append(CPPDEFINES=["DBUS_ENABLED"]) + else: + print("Warning: D-Bus development libraries not found. Disabling screensaver prevention.") + env["dbus"] = False + else: + env.Append(CPPDEFINES=["DBUS_ENABLED"]) if env["speechd"]: - env.Append(CPPDEFINES=["SPEECHD_ENABLED"]) + if not env["use_sowrap"]: + if os.system("pkg-config --exists speech-dispatcher") == 0: # 0 means found + env.ParseConfig("pkg-config speech-dispatcher --cflags --libs") + env.Append(CPPDEFINES=["SPEECHD_ENABLED"]) + else: + print("Warning: speech-dispatcher development libraries not found. Disabling text to speech support.") + env["speechd"] = False + else: + env.Append(CPPDEFINES=["SPEECHD_ENABLED"]) + + if not env["use_sowrap"]: + if os.system("pkg-config --exists xkbcommon") == 0: # 0 means found + env.ParseConfig("pkg-config xkbcommon --cflags --libs") + env.Append(CPPDEFINES=["XKB_ENABLED"]) + else: + print( + "Warning: libxkbcommon development libraries not found. Disabling dead key composition and key label support." + ) + else: + env.Append(CPPDEFINES=["XKB_ENABLED"]) if platform.system() == "Linux": env.Append(CPPDEFINES=["JOYDEV_ENABLED"]) if env["udev"]: - env.Append(CPPDEFINES=["UDEV_ENABLED"]) + if not env["use_sowrap"]: + if os.system("pkg-config --exists libudev") == 0: # 0 means found + env.ParseConfig("pkg-config libudev --cflags --libs") + env.Append(CPPDEFINES=["UDEV_ENABLED"]) + else: + print("Warning: libudev development libraries not found. Disabling controller hotplugging support.") + env["udev"] = False + else: + env.Append(CPPDEFINES=["UDEV_ENABLED"]) else: env["udev"] = False # Linux specific @@ -298,7 +376,9 @@ def configure(env: "Environment"): if not env["builtin_zlib"]: env.ParseConfig("pkg-config zlib --cflags --libs") - env.Prepend(CPPPATH=["#platform/linuxbsd", "#thirdparty/linuxbsd_headers"]) + env.Prepend(CPPPATH=["#platform/linuxbsd"]) + if env["use_sowrap"]: + env.Prepend(CPPPATH=["#thirdparty/linuxbsd_headers"]) env.Append( CPPDEFINES=[ @@ -309,6 +389,35 @@ def configure(env: "Environment"): ) if env["x11"]: + if not env["use_sowrap"]: + if os.system("pkg-config --exists x11"): + print("Error: X11 libraries not found. Aborting.") + sys.exit(255) + env.ParseConfig("pkg-config x11 --cflags --libs") + if os.system("pkg-config --exists xcursor"): + print("Error: Xcursor library not found. Aborting.") + sys.exit(255) + env.ParseConfig("pkg-config xcursor --cflags --libs") + if os.system("pkg-config --exists xinerama"): + print("Error: Xinerama library not found. Aborting.") + sys.exit(255) + env.ParseConfig("pkg-config xinerama --cflags --libs") + if os.system("pkg-config --exists xext"): + print("Error: Xext library not found. Aborting.") + sys.exit(255) + env.ParseConfig("pkg-config xext --cflags --libs") + if os.system("pkg-config --exists xrandr"): + print("Error: XrandR library not found. Aborting.") + sys.exit(255) + env.ParseConfig("pkg-config xrandr --cflags --libs") + if os.system("pkg-config --exists xrender"): + print("Error: XRender library not found. Aborting.") + sys.exit(255) + env.ParseConfig("pkg-config xrender --cflags --libs") + if os.system("pkg-config --exists xi"): + print("Error: Xi library not found. Aborting.") + sys.exit(255) + env.ParseConfig("pkg-config xi --cflags --libs") env.Append(CPPDEFINES=["X11_ENABLED"]) if env["vulkan"]: @@ -346,7 +455,7 @@ def configure(env: "Environment"): gnu_ld_version = re.search("^GNU ld [^$]*(\d+\.\d+)$", linker_version_str, re.MULTILINE) if not gnu_ld_version: print( - "Warning: Creating template binaries enabled for PCK embedding is currently only supported with GNU ld, not gold or LLD." + "Warning: Creating export template binaries enabled for PCK embedding is currently only supported with GNU ld, not gold, LLD or mold." ) else: if float(gnu_ld_version.group(1)) >= 2.30: @@ -354,6 +463,9 @@ def configure(env: "Environment"): else: env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.legacy.ld"]) + if platform.system() == "FreeBSD": + env.Append(LINKFLAGS=["-lkvm"]) + ## Cross-compilation # TODO: Support cross-compilation on architectures other than x86. host_is_64_bit = sys.maxsize > 2**32 diff --git a/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml new file mode 100644 index 0000000000..77a6ece756 --- /dev/null +++ b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformLinuxBSD" inherits="EditorExportPlatformPC" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Exporter for Linux/BSD. + </brief_description> + <description> + </description> + <tutorials> + <link title="Exporting for Linux">$DOCS_URL/tutorials/export/exporting_for_linux.html</link> + </tutorials> + <members> + <member name="binary_format/architecture" type="String" setter="" getter=""> + Application executable architecture. + Supported architectures: [code]x86_32[/code], [code]x86_64[/code], [code]arm64[/code], [code]arm32[/code], [code]rv64[/code], [code]ppc64[/code], and [code]ppc32[/code]. + Official export templates include [code]x86_32[/code] and [code]x86_64[/code] binaries only. + </member> + <member name="binary_format/embed_pck" type="bool" setter="" getter=""> + If [code]true[/code], project resources are embedded into the executable. + </member> + <member name="custom_template/debug" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="custom_template/release" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="debug/export_console_wrapper" type="int" setter="" getter=""> + If [code]true[/code], a console wrapper is exported alongside the main executable, which allows running the project with enabled console output. + </member> + <member name="ssh_remote_deploy/cleanup_script" type="String" setter="" getter=""> + Script code to execute on the remote host when app is finished. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="ssh_remote_deploy/enabled" type="bool" setter="" getter=""> + Enables remote deploy using SSH/SCP. + </member> + <member name="ssh_remote_deploy/extra_args_scp" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SCP. + </member> + <member name="ssh_remote_deploy/extra_args_ssh" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SSH. + </member> + <member name="ssh_remote_deploy/host" type="String" setter="" getter=""> + Remote host SSH user name and address, in [code]user@address[/code] format. + </member> + <member name="ssh_remote_deploy/port" type="String" setter="" getter=""> + Remote host SSH port number. + </member> + <member name="ssh_remote_deploy/run_script" type="String" setter="" getter=""> + Script code to execute on the remote host when running the app. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="texture_format/bptc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the BPTC format. + </member> + <member name="texture_format/etc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC format. + </member> + <member name="texture_format/etc2" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC2 format. + </member> + <member name="texture_format/s3tc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the S3TC format. + </member> + </members> +</class> diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index 2c5a945b6c..f72c079d1d 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -30,9 +30,14 @@ #include "export.h" -#include "editor/export/editor_export.h" #include "export_plugin.h" +#include "editor/export/editor_export.h" + +void register_linuxbsd_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformLinuxBSD); +} + void register_linuxbsd_exporter() { Ref<EditorExportPlatformLinuxBSD> platform; platform.instantiate(); diff --git a/platform/linuxbsd/export/export.h b/platform/linuxbsd/export/export.h index a2d70a73e3..f493047815 100644 --- a/platform/linuxbsd/export/export.h +++ b/platform/linuxbsd/export/export.h @@ -31,6 +31,7 @@ #ifndef LINUXBSD_EXPORT_H #define LINUXBSD_EXPORT_H +void register_linuxbsd_exporter_types(); void register_linuxbsd_exporter(); #endif // LINUXBSD_EXPORT_H diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 2528bb2b99..082040c8d6 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -30,12 +30,14 @@ #include "export_plugin.h" +#include "../logo_svg.gen.h" +#include "../run_icon_svg.gen.h" + #include "core/config/project_settings.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_scale.h" -#include "platform/linuxbsd/logo_svg.gen.h" -#include "platform/linuxbsd/run_icon_svg.gen.h" +#include "editor/export/editor_export.h" #include "modules/modules_enabled.gen.h" // For svg. #ifdef MODULE_SVG_ENABLED @@ -93,15 +95,15 @@ Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> return err; } - // Save console script. + // Save console wrapper. if (err == OK) { - int con_scr = p_preset->get("debug/export_console_script"); + int con_scr = p_preset->get("debug/export_console_wrapper"); if ((con_scr == 1 && p_debug) || (con_scr == 2)) { String scr_path = path.get_basename() + ".sh"; err = _export_debug_script(p_preset, pkg_name, path.get_file(), scr_path); FileAccess::set_unix_permissions(scr_path, 0755); if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), TTR("Could not create console script.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Console Export"), TTR("Could not create console wrapper.")); } } } @@ -142,7 +144,18 @@ List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<Edito return list; } -void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_options) { +bool EditorExportPlatformLinuxBSD::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_preset) { + // Hide SSH options. + bool ssh = p_preset->get("ssh_remote_deploy/enabled"); + if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/")) { + return false; + } + } + return true; +} + +void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_options) const { EditorExportPlatformPC::get_export_options(r_options); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64,arm32,rv64,ppc64,ppc32"), "x86_64")); @@ -156,7 +169,7 @@ void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_opti "kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")\n" "rm -rf \"{temp_dir}\""; - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22")); @@ -503,22 +516,24 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, } EditorExportPlatformLinuxBSD::EditorExportPlatformLinuxBSD() { + if (EditorNode::get_singleton()) { #ifdef MODULE_SVG_ENABLED - Ref<Image> img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); - set_logo(ImageTexture::create_from_image(img)); + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); + set_logo(ImageTexture::create_from_image(img)); - img_loader.create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); - run_icon = ImageTexture::create_from_image(img); + img_loader.create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); #endif - Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); - if (theme.is_valid()) { - stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); - } else { - stop_icon.instantiate(); + Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); + if (theme.is_valid()) { + stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); + } else { + stop_icon.instantiate(); + } } } diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index 4f860c3fd0..cef714e86e 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -37,6 +37,8 @@ #include "scene/resources/texture.h" class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { + GDCLASS(EditorExportPlatformLinuxBSD, EditorExportPlatformPC); + HashMap<String, String> extensions; struct SSHCleanupCommand { @@ -69,8 +71,9 @@ class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); public: - virtual void get_export_options(List<ExportOption> *r_options) override; + virtual void get_export_options(List<ExportOption> *r_options) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; virtual String get_template_file_name(const String &p_target, const String &p_arch) const override; virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override; diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 72d4e3772f..c3cb0f411d 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -35,10 +35,13 @@ #include "core/error/error_macros.h" #include "core/os/os.h" #include "core/string/ustring.h" +#include "core/variant/variant.h" +#ifdef SOWRAP_ENABLED #include "dbus-so_wrap.h" - -#include "core/variant/variant.h" +#else +#include <dbus/dbus.h> +#endif #define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop" #define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop" @@ -124,12 +127,32 @@ uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() { } FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { +#ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED int dylibloader_verbose = 1; #else int dylibloader_verbose = 0; #endif unsupported = (initialize_dbus(dylibloader_verbose) != 0); +#else + unsupported = false; +#endif + + if (unsupported) { + return; + } + + bool ver_ok = false; + int version_major = 0; + int version_minor = 0; + int version_rev = 0; + dbus_get_version(&version_major, &version_minor, &version_rev); + ver_ok = (version_major == 1 && version_minor >= 10) || (version_major > 1); // 1.10.0 + print_verbose(vformat("PortalDesktop: DBus %d.%d.%d detected.", version_major, version_minor, version_rev)); + if (!ver_ok) { + print_verbose("PortalDesktop: Unsupported DBus library version!"); + unsupported = true; + } } #endif // DBUS_ENABLED diff --git a/platform/linuxbsd/freedesktop_screensaver.cpp b/platform/linuxbsd/freedesktop_screensaver.cpp index 159fd0df61..cf179b5735 100644 --- a/platform/linuxbsd/freedesktop_screensaver.cpp +++ b/platform/linuxbsd/freedesktop_screensaver.cpp @@ -34,7 +34,11 @@ #include "core/config/project_settings.h" +#ifdef SOWRAP_ENABLED #include "dbus-so_wrap.h" +#else +#include <dbus/dbus.h> +#endif #define BUS_OBJECT_NAME "org.freedesktop.ScreenSaver" #define BUS_OBJECT_PATH "/org/freedesktop/ScreenSaver" @@ -127,12 +131,32 @@ void FreeDesktopScreenSaver::uninhibit() { } FreeDesktopScreenSaver::FreeDesktopScreenSaver() { +#ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED int dylibloader_verbose = 1; #else int dylibloader_verbose = 0; #endif unsupported = (initialize_dbus(dylibloader_verbose) != 0); +#else + unsupported = false; +#endif + + if (unsupported) { + return; + } + + bool ver_ok = false; + int version_major = 0; + int version_minor = 0; + int version_rev = 0; + dbus_get_version(&version_major, &version_minor, &version_rev); + ver_ok = (version_major == 1 && version_minor >= 10) || (version_major > 1); // 1.10.0 + print_verbose(vformat("ScreenSaver: DBus %d.%d.%d detected.", version_major, version_minor, version_rev)); + if (!ver_ok) { + print_verbose("ScreenSaver:: Unsupported DBus library version!"); + unsupported = true; + } } #endif // DBUS_ENABLED diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index 76d579dd53..d059d60b72 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -28,6 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +#include "os_linuxbsd.h" + +#include "main/main.h" + #include <limits.h> #include <locale.h> #include <stdlib.h> @@ -37,9 +41,6 @@ #include <sys/resource.h> #endif -#include "main/main.h" -#include "os_linuxbsd.h" - int main(int argc, char *argv[]) { #if defined(SANITIZERS_ENABLED) // Note: Set stack size to be at least 30 MB (vs 8 MB default) to avoid overflow, address sanitizer can increase stack usage up to 3 times. diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index b77f989677..ab79885fb4 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -32,6 +32,8 @@ #include "joypad_linux.h" +#include "core/os/os.h" + #include <dirent.h> #include <errno.h> #include <fcntl.h> @@ -39,7 +41,11 @@ #include <unistd.h> #ifdef UDEV_ENABLED +#ifdef SOWRAP_ENABLED #include "libudev-so_wrap.h" +#else +#include <libudev.h> +#endif #endif #define LONG_BITS (sizeof(long) * 8) @@ -68,19 +74,57 @@ void JoypadLinux::Joypad::reset() { events.clear(); } +#ifdef UDEV_ENABLED +// This function is derived from SDL: +// https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_sandbox.c#L28-L45 +static bool detect_sandbox() { + if (access("/.flatpak-info", F_OK) == 0) { + return true; + } + + // For Snap, we check multiple variables because they might be set for + // unrelated reasons. This is the same thing WebKitGTK does. + if (OS::get_singleton()->has_environment("SNAP") && OS::get_singleton()->has_environment("SNAP_NAME") && OS::get_singleton()->has_environment("SNAP_REVISION")) { + return true; + } + + if (access("/run/host/container-manager", F_OK) == 0) { + return true; + } + + return false; +} +#endif // UDEV_ENABLED + JoypadLinux::JoypadLinux(Input *in) { #ifdef UDEV_ENABLED +#ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED int dylibloader_verbose = 1; #else int dylibloader_verbose = 0; #endif - use_udev = initialize_libudev(dylibloader_verbose) == 0; - if (use_udev) { - print_verbose("JoypadLinux: udev enabled and loaded successfully."); + if (detect_sandbox()) { + // Linux binaries in sandboxes / containers need special handling because + // libudev doesn't work there. So we need to fallback to manual parsing + // of /dev/input in such case. + use_udev = false; + print_verbose("JoypadLinux: udev enabled, but detected incompatible sandboxed mode. Falling back to /dev/input to detect joypads."); } else { - print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads."); + use_udev = initialize_libudev(dylibloader_verbose) == 0; + if (use_udev) { + if (!udev_new || !udev_unref || !udev_enumerate_new || !udev_enumerate_add_match_subsystem || !udev_enumerate_scan_devices || !udev_enumerate_get_list_entry || !udev_list_entry_get_next || !udev_list_entry_get_name || !udev_device_new_from_syspath || !udev_device_get_devnode || !udev_device_get_action || !udev_device_unref || !udev_enumerate_unref || !udev_monitor_new_from_netlink || !udev_monitor_filter_add_match_subsystem_devtype || !udev_monitor_enable_receiving || !udev_monitor_get_fd || !udev_monitor_receive_device || !udev_monitor_unref) { + // There's no API to check version, check if functions are available instead. + use_udev = false; + print_verbose("JoypadLinux: Unsupported udev library version!"); + } else { + print_verbose("JoypadLinux: udev enabled and loaded successfully."); + } + } else { + print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads."); + } } +#endif #else print_verbose("JoypadLinux: udev disabled, parsing /dev/input to detect joypads."); #endif diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index 6661abdb37..26a9908d4e 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -32,6 +32,7 @@ #define JOYPAD_LINUX_H #ifdef JOYDEV_ENABLED + #include "core/input/input.h" #include "core/os/mutex.h" #include "core/os/thread.h" diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 41d1f1d050..310778388b 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -30,21 +30,18 @@ #include "os_linuxbsd.h" +#include "core/io/certs_compressed.gen.h" #include "core/io/dir_access.h" #include "main/main.h" #include "servers/display_server.h" -#include "modules/modules_enabled.gen.h" // For regex. -#ifdef MODULE_REGEX_ENABLED -#include "modules/regex/regex.h" -#endif - #ifdef X11_ENABLED #include "x11/display_server_x11.h" #endif -#ifdef HAVE_MNTENT -#include <mntent.h> +#include "modules/modules_enabled.gen.h" // For regex. +#ifdef MODULE_REGEX_ENABLED +#include "modules/regex/regex.h" #endif #include <dlfcn.h> @@ -56,6 +53,10 @@ #include <sys/utsname.h> #include <unistd.h> +#ifdef HAVE_MNTENT +#include <mntent.h> +#endif + void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; @@ -195,6 +196,10 @@ void OS_LinuxBSD::set_main_loop(MainLoop *p_main_loop) { main_loop = p_main_loop; } +String OS_LinuxBSD::get_identifier() const { + return "linuxbsd"; +} + String OS_LinuxBSD::get_name() const { #ifdef __linux__ return "Linux"; @@ -210,48 +215,54 @@ String OS_LinuxBSD::get_name() const { } String OS_LinuxBSD::get_systemd_os_release_info_value(const String &key) const { - static String info; - if (info.is_empty()) { - Ref<FileAccess> f = FileAccess::open("/etc/os-release", FileAccess::READ); - if (f.is_valid()) { - while (!f->eof_reached()) { - const String line = f->get_line(); - if (line.find(key) != -1) { - return line.split("=")[1].strip_edges(); - } + Ref<FileAccess> f = FileAccess::open("/etc/os-release", FileAccess::READ); + if (f.is_valid()) { + while (!f->eof_reached()) { + const String line = f->get_line(); + if (line.find(key) != -1) { + String value = line.split("=")[1].strip_edges(); + value = value.trim_prefix("\""); + return value.trim_suffix("\""); } } } - return info; + return ""; } String OS_LinuxBSD::get_distribution_name() const { - static String systemd_name = get_systemd_os_release_info_value("NAME"); // returns a value for systemd users, otherwise an empty string. - if (!systemd_name.is_empty()) { - return systemd_name; + static String distribution_name = get_systemd_os_release_info_value("NAME"); // returns a value for systemd users, otherwise an empty string. + if (!distribution_name.is_empty()) { + return distribution_name; } struct utsname uts; // returns a decent value for BSD family. uname(&uts); - return uts.sysname; + distribution_name = uts.sysname; + return distribution_name; } String OS_LinuxBSD::get_version() const { - static String systemd_version = get_systemd_os_release_info_value("VERSION"); // returns a value for systemd users, otherwise an empty string. - if (!systemd_version.is_empty()) { - return systemd_version; + static String release_version = get_systemd_os_release_info_value("VERSION"); // returns a value for systemd users, otherwise an empty string. + if (!release_version.is_empty()) { + return release_version; } struct utsname uts; // returns a decent value for BSD family. uname(&uts); - return uts.version; + release_version = uts.version; + return release_version; } Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const { - if (RenderingServer::get_singleton()->get_rendering_device() == nullptr) { + if (RenderingServer::get_singleton() == nullptr) { return Vector<String>(); } - const String rendering_device_name = RenderingServer::get_singleton()->get_rendering_device()->get_device_name(); // e.g. `NVIDIA GeForce GTX 970` - const String rendering_device_vendor = RenderingServer::get_singleton()->get_rendering_device()->get_device_vendor_name(); // e.g. `NVIDIA` + static Vector<String> info; + if (!info.is_empty()) { + return info; + } + + const String rendering_device_name = RenderingServer::get_singleton()->get_video_adapter_name(); // e.g. `NVIDIA GeForce GTX 970` + const String rendering_device_vendor = RenderingServer::get_singleton()->get_video_adapter_vendor(); // e.g. `NVIDIA` const String card_name = rendering_device_name.trim_prefix(rendering_device_vendor).strip_edges(); // -> `GeForce GTX 970` String vendor_device_id_mappings; @@ -315,8 +326,8 @@ Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const { Vector<String> class_display_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_display_device_candidates, kernel_lit, dummys); Vector<String> class_3d_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_3d_device_candidates, kernel_lit, dummys); - static String driver_name; - static String driver_version; + String driver_name; + String driver_version; // Use first valid value: for (const String &driver : class_3d_device_drivers) { @@ -336,7 +347,6 @@ Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const { } } - Vector<String> info; info.push_back(driver_name); String modinfo; @@ -486,10 +496,23 @@ bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { return font_config_initialized; } #endif + +#ifndef __linux__ + // `bsd` includes **all** BSD, not only "other BSD" (see `get_name()`). + if (p_feature == "bsd") { + return true; + } +#endif + if (p_feature == "pc") { return true; } + // Match against the specific OS (`linux`, `freebsd`, `netbsd`, `openbsd`). + if (p_feature == get_name().to_lower()) { + return true; + } + return false; } @@ -677,40 +700,45 @@ Vector<String> OS_LinuxBSD::get_system_font_path_for_text(const String &p_font_n } Vector<String> ret; - FcPattern *pattern = FcPatternCreate(); - if (pattern) { - FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>(p_font_name.utf8().get_data())); - FcPatternAddInteger(pattern, FC_WEIGHT, _weight_to_fc(p_weight)); - FcPatternAddInteger(pattern, FC_WIDTH, _stretch_to_fc(p_stretch)); - FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); - - FcCharSet *char_set = FcCharSetCreate(); - for (int i = 0; i < p_text.size(); i++) { - FcCharSetAddChar(char_set, p_text[i]); - } - FcPatternAddCharSet(pattern, FC_CHARSET, char_set); - - FcLangSet *lang_set = FcLangSetCreate(); - FcLangSetAdd(lang_set, reinterpret_cast<const FcChar8 *>(p_locale.utf8().get_data())); - FcPatternAddLangSet(pattern, FC_LANG, lang_set); - - FcConfigSubstitute(0, pattern, FcMatchPattern); - FcDefaultSubstitute(pattern); - - FcResult result; - FcPattern *match = FcFontMatch(0, pattern, &result); - if (match) { - char *file_name = nullptr; - if (FcPatternGetString(match, FC_FILE, 0, reinterpret_cast<FcChar8 **>(&file_name)) == FcResultMatch) { - if (file_name) { - ret.push_back(String::utf8(file_name)); + static const char *allowed_formats[] = { "TrueType", "CFF" }; + for (size_t i = 0; i < sizeof(allowed_formats) / sizeof(const char *); i++) { + FcPattern *pattern = FcPatternCreate(); + if (pattern) { + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + FcPatternAddString(pattern, FC_FONTFORMAT, reinterpret_cast<const FcChar8 *>(allowed_formats[i])); + FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>(p_font_name.utf8().get_data())); + FcPatternAddInteger(pattern, FC_WEIGHT, _weight_to_fc(p_weight)); + FcPatternAddInteger(pattern, FC_WIDTH, _stretch_to_fc(p_stretch)); + FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); + + FcCharSet *char_set = FcCharSetCreate(); + for (int j = 0; j < p_text.size(); j++) { + FcCharSetAddChar(char_set, p_text[j]); + } + FcPatternAddCharSet(pattern, FC_CHARSET, char_set); + + FcLangSet *lang_set = FcLangSetCreate(); + FcLangSetAdd(lang_set, reinterpret_cast<const FcChar8 *>(p_locale.utf8().get_data())); + FcPatternAddLangSet(pattern, FC_LANG, lang_set); + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + FcPattern *match = FcFontMatch(0, pattern, &result); + if (match) { + char *file_name = nullptr; + if (FcPatternGetString(match, FC_FILE, 0, reinterpret_cast<FcChar8 **>(&file_name)) == FcResultMatch) { + if (file_name) { + ret.push_back(String::utf8(file_name)); + } } + FcPatternDestroy(match); } - FcPatternDestroy(match); + FcPatternDestroy(pattern); + FcCharSetDestroy(char_set); + FcLangSetDestroy(lang_set); } - FcPatternDestroy(pattern); - FcCharSetDestroy(char_set); - FcLangSetDestroy(lang_set); } return ret; @@ -725,47 +753,51 @@ String OS_LinuxBSD::get_system_font_path(const String &p_font_name, int p_weight ERR_FAIL_V_MSG(String(), "Unable to load fontconfig, system font support is disabled."); } - String ret; - FcPattern *pattern = FcPatternCreate(); - if (pattern) { - bool allow_substitutes = (p_font_name.to_lower() == "sans-serif") || (p_font_name.to_lower() == "serif") || (p_font_name.to_lower() == "monospace") || (p_font_name.to_lower() == "cursive") || (p_font_name.to_lower() == "fantasy"); - - FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); - FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>(p_font_name.utf8().get_data())); - FcPatternAddInteger(pattern, FC_WEIGHT, _weight_to_fc(p_weight)); - FcPatternAddInteger(pattern, FC_WIDTH, _stretch_to_fc(p_stretch)); - FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); - - FcConfigSubstitute(0, pattern, FcMatchPattern); - FcDefaultSubstitute(pattern); - - FcResult result; - FcPattern *match = FcFontMatch(0, pattern, &result); - if (match) { - if (!allow_substitutes) { - char *family_name = nullptr; - if (FcPatternGetString(match, FC_FAMILY, 0, reinterpret_cast<FcChar8 **>(&family_name)) == FcResultMatch) { - if (family_name && String::utf8(family_name).to_lower() != p_font_name.to_lower()) { + static const char *allowed_formats[] = { "TrueType", "CFF" }; + for (size_t i = 0; i < sizeof(allowed_formats) / sizeof(const char *); i++) { + FcPattern *pattern = FcPatternCreate(); + if (pattern) { + bool allow_substitutes = (p_font_name.to_lower() == "sans-serif") || (p_font_name.to_lower() == "serif") || (p_font_name.to_lower() == "monospace") || (p_font_name.to_lower() == "cursive") || (p_font_name.to_lower() == "fantasy"); + + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + FcPatternAddString(pattern, FC_FONTFORMAT, reinterpret_cast<const FcChar8 *>(allowed_formats[i])); + FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>(p_font_name.utf8().get_data())); + FcPatternAddInteger(pattern, FC_WEIGHT, _weight_to_fc(p_weight)); + FcPatternAddInteger(pattern, FC_WIDTH, _stretch_to_fc(p_stretch)); + FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + FcPattern *match = FcFontMatch(0, pattern, &result); + if (match) { + if (!allow_substitutes) { + char *family_name = nullptr; + if (FcPatternGetString(match, FC_FAMILY, 0, reinterpret_cast<FcChar8 **>(&family_name)) == FcResultMatch) { + if (family_name && String::utf8(family_name).to_lower() != p_font_name.to_lower()) { + FcPatternDestroy(match); + FcPatternDestroy(pattern); + continue; + } + } + } + char *file_name = nullptr; + if (FcPatternGetString(match, FC_FILE, 0, reinterpret_cast<FcChar8 **>(&file_name)) == FcResultMatch) { + if (file_name) { + String ret = String::utf8(file_name); FcPatternDestroy(match); FcPatternDestroy(pattern); - - return String(); + return ret; } } + FcPatternDestroy(match); } - char *file_name = nullptr; - if (FcPatternGetString(match, FC_FILE, 0, reinterpret_cast<FcChar8 **>(&file_name)) == FcResultMatch) { - if (file_name) { - ret = String::utf8(file_name); - } - } - - FcPatternDestroy(match); + FcPatternDestroy(pattern); } - FcPatternDestroy(pattern); } - return ret; + return String(); #else ERR_FAIL_V_MSG(String(), "Godot was compiled without fontconfig, system font support is disabled."); #endif @@ -929,32 +961,41 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { args.push_back(path); args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args. Error result = execute("gio", args, nullptr, &err_code); // For GNOME based machines. - if (result == OK && !err_code) { - return OK; - } else if (err_code == 2) { - return ERR_FILE_NOT_FOUND; + if (result == OK) { // The `execute` function has done its job without errors. + if (!err_code) { // The shell command has been executed without errors. + return OK; + } else if (err_code == 1) { + ERR_PRINT("move_to_trash: No such file or directory as " + path + "."); + return ERR_FILE_NOT_FOUND; + } } args.pop_front(); args.push_front("move"); args.push_back("trash:/"); // The command is `kioclient5 move <file_name> trash:/`. result = execute("kioclient5", args, nullptr, &err_code); // For KDE based machines. - if (result == OK && !err_code) { - return OK; - } else if (err_code == 2) { - return ERR_FILE_NOT_FOUND; + if (result == OK) { // The `execute` function has done its job without errors. + if (!err_code) { // The shell command has been executed without errors. + return OK; + } else if (err_code == 1) { + ERR_PRINT("move_to_trash: No such file or directory as " + path + "."); + return ERR_FILE_NOT_FOUND; + } } args.pop_front(); args.pop_back(); result = execute("gvfs-trash", args, nullptr, &err_code); // For older Linux machines. - if (result == OK && !err_code) { - return OK; - } else if (err_code == 2) { - return ERR_FILE_NOT_FOUND; + if (result == OK) { // The `execute` function has done its job without errors. + if (!err_code) { // The shell command has been executed without errors. + return OK; + } else if (err_code == 1) { + ERR_PRINT("move_to_trash: No such file or directory as " + path + "."); + return ERR_FILE_NOT_FOUND; + } } - // If the commands `kioclient5`, `gio` or `gvfs-trash` don't exist on the system we do it manually. + // If the commands `kioclient5`, `gio` or `gvfs-trash` don't work on the system we do it manually. String trash_path = ""; String mnt = get_mountpoint(path); @@ -1067,6 +1108,40 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { return OK; } +String OS_LinuxBSD::get_system_ca_certificates() { + String certfile; + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + // Compile time preferred certificates path. + if (!String(_SYSTEM_CERTS_PATH).is_empty() && da->file_exists(_SYSTEM_CERTS_PATH)) { + certfile = _SYSTEM_CERTS_PATH; + } else if (da->file_exists("/etc/ssl/certs/ca-certificates.crt")) { + // Debian/Ubuntu + certfile = "/etc/ssl/certs/ca-certificates.crt"; + } else if (da->file_exists("/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem")) { + // Fedora + certfile = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"; + } else if (da->file_exists("/etc/ca-certificates/extracted/tls-ca-bundle.pem")) { + // Arch Linux + certfile = "/etc/ca-certificates/extracted/tls-ca-bundle.pem"; + } else if (da->file_exists("/var/lib/ca-certificates/ca-bundle.pem")) { + // openSUSE + certfile = "/var/lib/ca-certificates/ca-bundle.pem"; + } else if (da->file_exists("/etc/ssl/cert.pem")) { + // FreeBSD/OpenBSD + certfile = "/etc/ssl/cert.pem"; + } + + if (certfile.is_empty()) { + return ""; + } + + Ref<FileAccess> f = FileAccess::open(certfile, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), "", vformat("Failed to open system CA certificates file: '%s'", certfile)); + + return f->get_as_text(); +} + OS_LinuxBSD::OS_LinuxBSD() { main_loop = nullptr; @@ -1083,12 +1158,26 @@ OS_LinuxBSD::OS_LinuxBSD() { #endif #ifdef FONTCONFIG_ENABLED +#ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED int dylibloader_verbose = 1; #else int dylibloader_verbose = 0; #endif font_config_initialized = (initialize_fontconfig(dylibloader_verbose) == 0); +#else + font_config_initialized = true; +#endif + if (font_config_initialized) { + bool ver_ok = false; + int version = FcGetVersion(); + ver_ok = ((version / 100 / 100) == 2 && (version / 100 % 100) >= 11) || ((version / 100 / 100) > 2); // 2.11.0 + print_verbose(vformat("FontConfig %d.%d.%d detected.", version / 100 / 100, version / 100 % 100, version % 100)); + if (!ver_ok) { + font_config_initialized = false; + } + } + if (font_config_initialized) { config = FcInitLoadConfigAndFonts(); if (!config) { diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 045d3d95ba..007b90b82b 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -31,17 +31,22 @@ #ifndef OS_LINUXBSD_H #define OS_LINUXBSD_H -#include "core/input/input.h" #include "crash_handler_linuxbsd.h" +#include "joypad_linux.h" + +#include "core/input/input.h" #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" #include "drivers/unix/os_unix.h" -#include "joypad_linux.h" #include "servers/audio_server.h" #ifdef FONTCONFIG_ENABLED +#ifdef SOWRAP_ENABLED #include "fontconfig-so_wrap.h" +#else +#include <fontconfig/fontconfig.h> +#endif #endif class OS_LinuxBSD : public OS_Unix { @@ -92,6 +97,7 @@ protected: virtual void set_main_loop(MainLoop *p_main_loop) override; public: + virtual String get_identifier() const override; virtual String get_name() const override; virtual String get_distribution_name() const override; virtual String get_version() const override; @@ -128,6 +134,8 @@ public: virtual Error move_to_trash(const String &p_path) override; + virtual String get_system_ca_certificates() override; + OS_LinuxBSD(); ~OS_LinuxBSD(); }; diff --git a/platform/linuxbsd/tts_linux.cpp b/platform/linuxbsd/tts_linux.cpp index 4662aaf02d..8bc83f5b9c 100644 --- a/platform/linuxbsd/tts_linux.cpp +++ b/platform/linuxbsd/tts_linux.cpp @@ -39,12 +39,23 @@ void TTS_Linux::speech_init_thread_func(void *p_userdata) { TTS_Linux *tts = (TTS_Linux *)p_userdata; if (tts) { MutexLock thread_safe_method(tts->_thread_safe_); +#ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED int dylibloader_verbose = 1; #else int dylibloader_verbose = 0; #endif - if (initialize_speechd(dylibloader_verbose) == 0) { + if (initialize_speechd(dylibloader_verbose) != 0) { + print_verbose("Text-to-Speech: Cannot load Speech Dispatcher library!"); + } else { + if (!spd_open || !spd_set_notification_on || !spd_list_synthesis_voices || !free_spd_voices || !spd_set_synthesis_voice || !spd_set_volume || !spd_set_voice_pitch || !spd_set_voice_rate || !spd_set_data_mode || !spd_say || !spd_pause || !spd_resume || !spd_cancel) { + // There's no API to check version, check if functions are available instead. + print_verbose("Text-to-Speech: Unsupported Speech Dispatcher library version!"); + return; + } +#else + { +#endif CharString class_str; String config_name = GLOBAL_GET("application/config/name"); if (config_name.length() == 0) { @@ -64,84 +75,103 @@ void TTS_Linux::speech_init_thread_func(void *p_userdata) { } else { print_verbose("Text-to-Speech: Cannot initialize Speech Dispatcher synthesizer!"); } - } else { - print_verbose("Text-to-Speech: Cannot load Speech Dispatcher library!"); } } } void TTS_Linux::speech_event_index_mark(size_t p_msg_id, size_t p_client_id, SPDNotificationType p_type, char *p_index_mark) { TTS_Linux *tts = TTS_Linux::get_singleton(); - if (tts && tts->ids.has(p_msg_id)) { - MutexLock thread_safe_method(tts->_thread_safe_); - // Get word offset from the index mark injected to the text stream. - String mark = String::utf8(p_index_mark); - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, tts->ids[p_msg_id], mark.to_int()); + if (tts) { + callable_mp(tts, &TTS_Linux::_speech_index_mark).call_deferred(p_msg_id, p_client_id, (int)p_type, String::utf8(p_index_mark)); + } +} + +void TTS_Linux::_speech_index_mark(size_t p_msg_id, size_t p_client_id, int p_type, const String &p_index_mark) { + _THREAD_SAFE_METHOD_ + + if (ids.has(p_msg_id)) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[p_msg_id], p_index_mark.to_int()); } } void TTS_Linux::speech_event_callback(size_t p_msg_id, size_t p_client_id, SPDNotificationType p_type) { TTS_Linux *tts = TTS_Linux::get_singleton(); if (tts) { - MutexLock thread_safe_method(tts->_thread_safe_); - List<DisplayServer::TTSUtterance> &queue = tts->queue; - if (!tts->paused && tts->ids.has(p_msg_id)) { - if (p_type == SPD_EVENT_END) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, tts->ids[p_msg_id]); - tts->ids.erase(p_msg_id); - tts->last_msg_id = -1; - tts->speaking = false; - } else if (p_type == SPD_EVENT_CANCEL) { - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, tts->ids[p_msg_id]); - tts->ids.erase(p_msg_id); - tts->last_msg_id = -1; - tts->speaking = false; + callable_mp(tts, &TTS_Linux::_speech_event).call_deferred(p_msg_id, p_client_id, (int)p_type); + } +} + +void TTS_Linux::_load_voices() { + if (!voices_loaded) { + SPDVoice **spd_voices = spd_list_synthesis_voices(synth); + if (spd_voices != nullptr) { + SPDVoice **voices_ptr = spd_voices; + while (*voices_ptr != nullptr) { + VoiceInfo vi; + vi.language = String::utf8((*voices_ptr)->language); + vi.variant = String::utf8((*voices_ptr)->variant); + voices[String::utf8((*voices_ptr)->name)] = vi; + voices_ptr++; } + free_spd_voices(spd_voices); } - if (!tts->speaking && queue.size() > 0) { - DisplayServer::TTSUtterance &message = queue.front()->get(); - - // Inject index mark after each word. - String text; - String language; - SPDVoice **voices = spd_list_synthesis_voices(tts->synth); - if (voices != nullptr) { - SPDVoice **voices_ptr = voices; - while (*voices_ptr != nullptr) { - if (String::utf8((*voices_ptr)->name) == message.voice) { - language = String::utf8((*voices_ptr)->language); - break; - } - voices_ptr++; - } - free_spd_voices(voices); - } - PackedInt32Array breaks = TS->string_get_word_breaks(message.text, language); - for (int i = 0; i < breaks.size(); i += 2) { - const int start = breaks[i]; - const int end = breaks[i + 1]; - text += message.text.substr(start, end - start + 1); - text += "<mark name=\"" + String::num_int64(end, 10) + "\"/>"; - } + voices_loaded = true; + } +} - spd_set_synthesis_voice(tts->synth, message.voice.utf8().get_data()); - spd_set_volume(tts->synth, message.volume * 2 - 100); - spd_set_voice_pitch(tts->synth, (message.pitch - 1) * 100); - float rate = 0; - if (message.rate > 1.f) { - rate = log10(MIN(message.rate, 2.5f)) / log10(2.5f) * 100; - } else if (message.rate < 1.f) { - rate = log10(MAX(message.rate, 0.5f)) / log10(0.5f) * -100; - } - spd_set_voice_rate(tts->synth, rate); - spd_set_data_mode(tts->synth, SPD_DATA_SSML); - tts->last_msg_id = spd_say(tts->synth, SPD_TEXT, text.utf8().get_data()); - tts->ids[tts->last_msg_id] = message.id; - DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); - - queue.pop_front(); - tts->speaking = true; +void TTS_Linux::_speech_event(size_t p_msg_id, size_t p_client_id, int p_type) { + _THREAD_SAFE_METHOD_ + + if (!paused && ids.has(p_msg_id)) { + if ((SPDNotificationType)p_type == SPD_EVENT_END) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[p_msg_id]); + ids.erase(p_msg_id); + last_msg_id = -1; + speaking = false; + } else if ((SPDNotificationType)p_type == SPD_EVENT_CANCEL) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[p_msg_id]); + ids.erase(p_msg_id); + last_msg_id = -1; + speaking = false; + } + } + if (!speaking && queue.size() > 0) { + DisplayServer::TTSUtterance &message = queue.front()->get(); + + // Inject index mark after each word. + String text; + String language; + + _load_voices(); + const VoiceInfo *voice = voices.getptr(message.voice); + if (voice) { + language = voice->language; + } + + PackedInt32Array breaks = TS->string_get_word_breaks(message.text, language); + for (int i = 0; i < breaks.size(); i += 2) { + const int start = breaks[i]; + const int end = breaks[i + 1]; + text += message.text.substr(start, end - start + 1); + text += "<mark name=\"" + String::num_int64(end, 10) + "\"/>"; + } + spd_set_synthesis_voice(synth, message.voice.utf8().get_data()); + spd_set_volume(synth, message.volume * 2 - 100); + spd_set_voice_pitch(synth, (message.pitch - 1) * 100); + float rate = 0; + if (message.rate > 1.f) { + rate = log10(MIN(message.rate, 2.5f)) / log10(2.5f) * 100; + } else if (message.rate < 1.f) { + rate = log10(MAX(message.rate, 0.5f)) / log10(0.5f) * -100; } + spd_set_voice_rate(synth, rate); + spd_set_data_mode(synth, SPD_DATA_SSML); + last_msg_id = spd_say(synth, SPD_TEXT, text.utf8().get_data()); + ids[last_msg_id] = message.id; + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id); + + queue.pop_front(); + speaking = true; } } @@ -157,21 +187,17 @@ Array TTS_Linux::get_voices() const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!synth, Array()); + const_cast<TTS_Linux *>(this)->_load_voices(); + Array list; - SPDVoice **voices = spd_list_synthesis_voices(synth); - if (voices != nullptr) { - SPDVoice **voices_ptr = voices; - while (*voices_ptr != nullptr) { - Dictionary voice_d; - voice_d["name"] = String::utf8((*voices_ptr)->name); - voice_d["id"] = String::utf8((*voices_ptr)->name); - voice_d["language"] = String::utf8((*voices_ptr)->language) + "_" + String::utf8((*voices_ptr)->variant); - list.push_back(voice_d); - - voices_ptr++; - } - free_spd_voices(voices); + for (const KeyValue<String, VoiceInfo> &E : voices) { + Dictionary voice_d; + voice_d["name"] = E.key; + voice_d["id"] = E.key; + voice_d["language"] = E.value.language + "_" + E.value.variant; + list.push_back(voice_d); } + return list; } @@ -200,7 +226,7 @@ void TTS_Linux::speak(const String &p_text, const String &p_voice, int p_volume, if (is_paused()) { resume(); } else { - speech_event_callback(0, 0, SPD_EVENT_BEGIN); + _speech_event(0, 0, (int)SPD_EVENT_BEGIN); } } diff --git a/platform/linuxbsd/tts_linux.h b/platform/linuxbsd/tts_linux.h index 425654d975..ec68219689 100644 --- a/platform/linuxbsd/tts_linux.h +++ b/platform/linuxbsd/tts_linux.h @@ -34,14 +34,18 @@ #include "core/os/thread.h" #include "core/os/thread_safe.h" #include "core/string/ustring.h" +#include "core/templates/hash_map.h" #include "core/templates/list.h" -#include "core/templates/rb_map.h" #include "core/variant/array.h" #include "servers/display_server.h" +#ifdef SOWRAP_ENABLED #include "speechd-so_wrap.h" +#else +#include <libspeechd.h> +#endif -class TTS_Linux { +class TTS_Linux : public Object { _THREAD_SAFE_CLASS_ List<DisplayServer::TTSUtterance> queue; @@ -51,6 +55,13 @@ class TTS_Linux { int last_msg_id = -1; HashMap<int, int> ids; + struct VoiceInfo { + String language; + String variant; + }; + bool voices_loaded = false; + HashMap<String, VoiceInfo> voices; + Thread init_thread; static void speech_init_thread_func(void *p_userdata); @@ -59,6 +70,11 @@ class TTS_Linux { static TTS_Linux *singleton; +protected: + void _load_voices(); + void _speech_event(size_t p_msg_id, size_t p_client_id, int p_type); + void _speech_index_mark(size_t p_msg_id, size_t p_client_id, int p_type, const String &p_index_mark); + public: static TTS_Linux *get_singleton(); diff --git a/platform/linuxbsd/x11/SCsub b/platform/linuxbsd/x11/SCsub index 8b2e2aabe4..a4890391ce 100644 --- a/platform/linuxbsd/x11/SCsub +++ b/platform/linuxbsd/x11/SCsub @@ -5,15 +5,21 @@ Import("env") source_files = [ "display_server_x11.cpp", "key_mapping_x11.cpp", - "dynwrappers/xlib-so_wrap.c", - "dynwrappers/xcursor-so_wrap.c", - "dynwrappers/xinerama-so_wrap.c", - "dynwrappers/xinput2-so_wrap.c", - "dynwrappers/xrandr-so_wrap.c", - "dynwrappers/xrender-so_wrap.c", - "dynwrappers/xext-so_wrap.c", ] +if env["use_sowrap"]: + source_files.append( + [ + "dynwrappers/xlib-so_wrap.c", + "dynwrappers/xcursor-so_wrap.c", + "dynwrappers/xinerama-so_wrap.c", + "dynwrappers/xinput2-so_wrap.c", + "dynwrappers/xrandr-so_wrap.c", + "dynwrappers/xrender-so_wrap.c", + "dynwrappers/xext-so_wrap.c", + ] + ) + if env["vulkan"]: source_files.append("vulkan_context_x11.cpp") diff --git a/platform/linuxbsd/x11/detect_prime_x11.cpp b/platform/linuxbsd/x11/detect_prime_x11.cpp index 8d586599e6..2b5776ce54 100644 --- a/platform/linuxbsd/x11/detect_prime_x11.cpp +++ b/platform/linuxbsd/x11/detect_prime_x11.cpp @@ -28,23 +28,26 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifdef X11_ENABLED -#if defined(GLES3_ENABLED) +#if defined(X11_ENABLED) && defined(GLES3_ENABLED) #include "detect_prime_x11.h" #include "core/string/print_string.h" #include "core/string/ustring.h" -#include <stdlib.h> - #include "thirdparty/glad/glad/gl.h" #include "thirdparty/glad/glad/glx.h" -#include "dynwrappers/xlib-so_wrap.h" - -#include <cstring> +#ifdef SOWRAP_ENABLED +#include "x11/dynwrappers/xlib-so_wrap.h" +#else +#include <X11/XKBlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#endif +#include <stdlib.h> +#include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> @@ -54,6 +57,9 @@ typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); +// To prevent shadowing warnings +#undef glGetString + struct vendor { const char *glxvendor = nullptr; int priority = 0; @@ -256,5 +262,4 @@ int detect_prime() { return preferred; } -#endif -#endif +#endif // X11_ENABLED && GLES3_ENABLED diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 8377e81a53..a607e26ac5 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -32,12 +32,13 @@ #ifdef X11_ENABLED +#include "x11/detect_prime_x11.h" +#include "x11/key_mapping_x11.h" + #include "core/config/project_settings.h" #include "core/math/math_funcs.h" #include "core/string/print_string.h" #include "core/string/ustring.h" -#include "detect_prime_x11.h" -#include "key_mapping_x11.h" #include "main/main.h" #include "scene/resources/texture.h" @@ -58,6 +59,9 @@ #include <sys/types.h> #include <unistd.h> +#undef CursorShape +#include <X11/XKBlib.h> + // ICCCM #define WM_NormalState 1L // window normal state #define WM_IconicState 3L // window minimized @@ -65,9 +69,6 @@ #define _NET_WM_STATE_REMOVE 0L // remove/unset property #define _NET_WM_STATE_ADD 1L // add/set property -#undef CursorShape -#include <X11/XKBlib.h> - // 2.2 is the first release with multitouch #define XINPUT_CLIENT_VERSION_MAJOR 2 #define XINPUT_CLIENT_VERSION_MINOR 2 @@ -128,6 +129,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { #endif case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_TEXT_TO_SPEECH: + case FEATURE_SCREEN_CAPTURE: return true; default: { } @@ -185,6 +187,7 @@ bool DisplayServerX11::_refresh_device_info() { xi.absolute_devices.clear(); xi.touch_devices.clear(); xi.pen_inverted_devices.clear(); + xi.last_relative_time = 0; int dev_count; XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count); @@ -301,37 +304,37 @@ void DisplayServerX11::_flush_mouse_motion() { #ifdef SPEECHD_ENABLED bool DisplayServerX11::tts_is_speaking() const { - ERR_FAIL_COND_V(!tts, false); + ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); return tts->is_speaking(); } bool DisplayServerX11::tts_is_paused() const { - ERR_FAIL_COND_V(!tts, false); + ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); return tts->is_paused(); } TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const { - ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>()); + ERR_FAIL_COND_V_MSG(!tts, TypedArray<Dictionary>(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); return tts->get_voices(); } void DisplayServerX11::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { - ERR_FAIL_COND(!tts); + ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt); } void DisplayServerX11::tts_pause() { - ERR_FAIL_COND(!tts); + ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); tts->pause(); } void DisplayServerX11::tts_resume() { - ERR_FAIL_COND(!tts); + ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); tts->resume(); } void DisplayServerX11::tts_stop() { - ERR_FAIL_COND(!tts); + ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); tts->stop(); } @@ -604,7 +607,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A success = true; } } else { - printf("Failed to get selection data chunk.\n"); + print_verbose("Failed to get selection data chunk."); done = true; } @@ -631,7 +634,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A if (result == Success) { ret.parse_utf8((const char *)data); } else { - printf("Failed to get selection data.\n"); + print_verbose("Failed to get selection data."); } if (data) { @@ -752,23 +755,56 @@ int DisplayServerX11::get_screen_count() const { } int DisplayServerX11::get_primary_screen() const { - return XDefaultScreen(x11_display); + int event_base, error_base; + if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + return 0; + } else { + return XDefaultScreen(x11_display); + } } -Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { - Rect2i rect(0, 0, 0, 0); +int DisplayServerX11::get_keyboard_focus_screen() const { + int count = get_screen_count(); + if (count < 2) { + // Early exit with single monitor. + return 0; + } - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; + Window focus = 0; + int revert_to = 0; + + XGetInputFocus(x11_display, &focus, &revert_to); + if (focus) { + Window focus_child = 0; + int x = 0, y = 0; + XTranslateCoordinates(x11_display, focus, DefaultRootWindow(x11_display), 0, 0, &x, &y, &focus_child); + + XWindowAttributes xwa; + XGetWindowAttributes(x11_display, focus, &xwa); + Rect2i window_rect = Rect2i(x, y, xwa.width, xwa.height); + + // Find which monitor has the largest overlap with the given window. + int screen_index = 0; + int max_area = 0; + for (int i = 0; i < count; i++) { + Rect2i screen_rect = _screen_get_rect(i); + Rect2i intersection = screen_rect.intersection(window_rect); + int area = intersection.get_area(); + if (area > max_area) { + max_area = area; + screen_index = i; + } + } + return screen_index; } + return get_primary_screen(); +} + +Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { + Rect2i rect(0, 0, 0, 0); + + p_screen = _get_screen_index(p_screen); ERR_FAIL_COND_V(p_screen < 0, rect); // Using Xinerama Extension. @@ -820,12 +856,30 @@ Size2i DisplayServerX11::screen_get_size(int p_screen) const { return _screen_get_rect(p_screen).size; } +// A Handler to avoid crashing on non-fatal X errors by default. +// +// The original X11 error formatter `_XPrintDefaultError` is defined here: +// https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/e45ca7b41dcd3ace7681d6897505f85d374640f2/src/XlibInt.c#L1322 +// It is not exposed through the API, accesses X11 internals, +// and is much more complex, so this is a less complete simplified error X11 printer. +int default_window_error_handler(Display *display, XErrorEvent *error) { + static char message[1024]; + XGetErrorText(display, error->error_code, message, sizeof(message)); + + ERR_PRINT(vformat("Unhandled XServer error: %s" + "\n Major opcode of failed request: %d" + "\n Serial number of failed request: %d" + "\n Current serial number in output stream: %d", + String::utf8(message), (uint64_t)error->request_code, (uint64_t)error->minor_code, (uint64_t)error->serial)); + return 0; +} + bool g_bad_window = false; int bad_window_error_handler(Display *display, XErrorEvent *error) { if (error->error_code == BadWindow) { g_bad_window = true; } else { - ERR_PRINT("Unhandled XServer error code: " + itos(error->error_code)); + return default_window_error_handler(display, error); } return 0; } @@ -833,17 +887,7 @@ int bad_window_error_handler(Display *display, XErrorEvent *error) { Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); int screen_count = get_screen_count(); // Check if screen is valid. @@ -1120,18 +1164,7 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { int DisplayServerX11::screen_get_dpi(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - - //invalid screen? + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0); //Get physical monitor Dimensions through XRandR and calculate dpi @@ -1169,8 +1202,31 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const { return 96; } -float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { - _THREAD_SAFE_METHOD_ +Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const { + Point2i pos = p_position; + + int number_of_screens = XScreenCount(x11_display); + for (int i = 0; i < number_of_screens; i++) { + Window root = XRootWindow(x11_display, i); + XWindowAttributes root_attrs; + XGetWindowAttributes(x11_display, root, &root_attrs); + if ((pos.x >= root_attrs.x) && (pos.x <= root_attrs.x + root_attrs.width) && (pos.y >= root_attrs.y) && (pos.y <= root_attrs.y + root_attrs.height)) { + XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap); + if (image) { + XColor c; + c.pixel = XGetPixel(image, 0, 0); + XFree(image); + XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c); + return Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0); + } + } + } + + return Color(); +} + +Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const { + ERR_FAIL_INDEX_V(p_screen, get_screen_count(), Ref<Image>()); switch (p_screen) { case SCREEN_PRIMARY: { @@ -1183,7 +1239,95 @@ float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { break; } - //invalid screen? + ERR_FAIL_COND_V(p_screen < 0, Ref<Image>()); + + XImage *image = nullptr; + + int event_base, error_base; + if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + int xin_count; + XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &xin_count); + if (p_screen < xin_count) { + int x_count = XScreenCount(x11_display); + for (int i = 0; i < x_count; i++) { + Window root = XRootWindow(x11_display, i); + XWindowAttributes root_attrs; + XGetWindowAttributes(x11_display, root, &root_attrs); + if ((xsi[p_screen].x_org >= root_attrs.x) && (xsi[p_screen].x_org <= root_attrs.x + root_attrs.width) && (xsi[p_screen].y_org >= root_attrs.y) && (xsi[p_screen].y_org <= root_attrs.y + root_attrs.height)) { + image = XGetImage(x11_display, root, xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height, AllPlanes, ZPixmap); + break; + } + } + } else { + ERR_FAIL_V_MSG(Ref<Image>(), "Invalid screen index: " + itos(p_screen) + "(count: " + itos(xin_count) + ")."); + } + } else { + int x_count = XScreenCount(x11_display); + if (p_screen < x_count) { + Window root = XRootWindow(x11_display, p_screen); + + XWindowAttributes root_attrs; + XGetWindowAttributes(x11_display, root, &root_attrs); + + image = XGetImage(x11_display, root, root_attrs.x, root_attrs.y, root_attrs.width, root_attrs.height, AllPlanes, ZPixmap); + } else { + ERR_FAIL_V_MSG(Ref<Image>(), "Invalid screen index: " + itos(p_screen) + "(count: " + itos(x_count) + ")."); + } + } + + Ref<Image> img; + if (image) { + int width = image->width; + int height = image->height; + + Vector<uint8_t> img_data; + img_data.resize(height * width * 4); + + uint8_t *sr = (uint8_t *)image->data; + uint8_t *wr = (uint8_t *)img_data.ptrw(); + + if (image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2]; + wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1]; + wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0]; + wr[(y * width + x) * 4 + 3] = 255; + } + } + } else if (image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 3 + 2]; + wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 3 + 1]; + wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 3 + 0]; + wr[(y * width + x) * 4 + 3] = 255; + } + } + } else if (image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + wr[(y * width + x) * 4 + 0] = sr[(y * width + x) * 4 + 2]; + wr[(y * width + x) * 4 + 1] = sr[(y * width + x) * 4 + 1]; + wr[(y * width + x) * 4 + 2] = sr[(y * width + x) * 4 + 0]; + wr[(y * width + x) * 4 + 3] = 255; + } + } + } else { + XFree(image); + ERR_FAIL_V_MSG(Ref<Image>(), vformat("XImage with RGB mask %x %x %x and depth %d is not supported.", (uint64_t)image->red_mask, (uint64_t)image->green_mask, (uint64_t)image->blue_mask, (int64_t)image->bits_per_pixel)); + } + img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data); + XFree(image); + } + + return img; +} + +float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK); //Use xrandr to get screen refresh rate. @@ -1329,12 +1473,14 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { wd.xic = nullptr; } XDestroyWindow(x11_display, wd.x11_xim_window); - if (xkb_loaded) { +#ifdef XKB_ENABLED + if (xkb_loaded_v05p) { if (wd.xkb_state) { xkb_compose_state_unref(wd.xkb_state); wd.xkb_state = nullptr; } } +#endif XUnmapWindow(x11_display, wd.x11_window); XDestroyWindow(x11_display, wd.x11_window); @@ -1544,18 +1690,7 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - - // Check if screen is valid + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX(p_screen, get_screen_count()); if (window_get_current_screen(p_window) == p_screen) { @@ -2055,6 +2190,22 @@ void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { } else if (wd.minimized && !_window_minimize_check(p_window)) { _set_wm_minimized(p_window, true); } + + if (wd.on_top) { + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False); + + XClientMessageEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.window = wd.x11_window; + xev.message_type = wm_state; + xev.format = 32; + xev.data.l[0] = _NET_WM_STATE_ADD; + xev.data.l[1] = wm_above; + xev.data.l[3] = 1; + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev); + } } bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { @@ -2475,6 +2626,15 @@ void DisplayServerX11::window_move_to_foreground(WindowID p_window) { XFlush(x11_display); } +bool DisplayServerX11::window_is_focused(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; + + return wd.focused; +} + bool DisplayServerX11::window_can_draw(WindowID p_window) const { //this seems to be all that is provided by X11 return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; @@ -2597,6 +2757,8 @@ DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const { void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { _THREAD_SAFE_METHOD_ + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + if (p_cursor.is_valid()) { HashMap<CursorShape, Vector<Variant>>::Iterator cursor_c = cursors_cache.find(p_shape); @@ -2610,16 +2772,12 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu } Ref<Texture2D> texture = p_cursor; + ERR_FAIL_COND(!texture.is_valid()); Ref<AtlasTexture> atlas_texture = p_cursor; - Ref<Image> image; Size2i texture_size; Rect2i atlas_rect; - if (texture.is_valid()) { - image = texture->get_image(); - } - - if (!image.is_valid() && atlas_texture.is_valid()) { + if (atlas_texture.is_valid()) { texture = atlas_texture->get_atlas(); atlas_rect.size.width = texture->get_width(); @@ -2629,19 +2787,23 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu texture_size.width = atlas_texture->get_region().size.x; texture_size.height = atlas_texture->get_region().size.y; - } else if (image.is_valid()) { + } else { texture_size.width = texture->get_width(); texture_size.height = texture->get_height(); } - ERR_FAIL_COND(!texture.is_valid()); ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - image = texture->get_image(); + Ref<Image> image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); + if (image->is_compressed()) { + image = image->duplicate(true); + Error err = image->decompress(); + ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); + } // Create the cursor structure XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); @@ -2690,8 +2852,8 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu XcursorImageDestroy(cursor_image); } else { // Reset to default system cursor - if (img[p_shape]) { - cursors[p_shape] = XcursorImageLoadCursor(x11_display, img[p_shape]); + if (cursor_img[p_shape]) { + cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_img[p_shape]); } CursorShape c = current_cursor; @@ -2804,7 +2966,7 @@ Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod); - KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); + KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0); if (is_ascii_lower_case(xkeysym)) { xkeysym -= ('a' - 'A'); } @@ -2897,7 +3059,7 @@ BitField<MouseButtonMask> DisplayServerX11::_get_mouse_button_state(MouseButton } void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) { - WindowData wd = windows[p_window]; + WindowData &wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; @@ -2942,11 +3104,13 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr); String keysym; - if (xkb_loaded) { +#ifdef XKB_ENABLED + if (xkb_loaded_v08p) { KeySym keysym_unicode_nm = 0; // keysym used to find unicode XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr); keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm))); } +#endif // Meanwhile, XLookupString returns keysyms useful for unicode. @@ -3035,7 +3199,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, } } while (status == XBufferOverflow); #endif - } else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded) { +#ifdef XKB_ENABLED + } else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded_v05p) { xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode); if (res == XKB_COMPOSE_FEED_ACCEPTED) { if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) { @@ -3093,6 +3258,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, return; } } +#endif } /* Phase 2, obtain a Godot keycode from the keysym */ @@ -3295,7 +3461,7 @@ Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p return p_property; } else { char *target_name = XGetAtomName(x11_display, p_target); - printf("Target '%s' not supported.\n", target_name); + print_verbose(vformat("Target '%s' not supported.", target_name)); if (target_name) { XFree(target_name); } @@ -3459,8 +3625,17 @@ void DisplayServerX11::_window_changed(XEvent *event) { // Query display server about a possible new window state. wd.fullscreen = _window_fullscreen_check(window_id); - wd.minimized = _window_minimize_check(window_id); - wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE"); + wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE") && !wd.fullscreen; + wd.minimized = _window_minimize_check(window_id) && !wd.fullscreen && !wd.maximized; + + // Readjusting the window position if the window is being reparented by the window manager for decoration + Window root, parent, *children; + unsigned int nchildren; + if (XQueryTree(x11_display, wd.x11_window, &root, &parent, &children, &nchildren) && wd.parent != parent) { + wd.parent = parent; + window_set_position(wd.position, window_id); + } + XFree(children); { //the position in xconfigure is not useful here, obtain it manually @@ -3834,10 +4009,6 @@ void DisplayServerX11::process_events() { for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { XEvent &event = events[event_index]; - if (ignore_events) { - XFreeEventData(x11_display, &event.xcookie); - continue; - } bool ime_window_event = false; WindowID window_id = MAIN_WINDOW_ID; @@ -3867,7 +4038,7 @@ void DisplayServerX11::process_events() { _refresh_device_info(); } break; case XI_RawMotion: { - if (ime_window_event) { + if (ime_window_event || ignore_events) { break; } XIRawEvent *raw_event = (XIRawEvent *)event_data; @@ -3972,7 +4143,7 @@ void DisplayServerX11::process_events() { #ifdef TOUCH_ENABLED case XI_TouchBegin: case XI_TouchEnd: { - if (ime_window_event) { + if (ime_window_event || ignore_events) { break; } bool is_begin = event_data->evtype == XI_TouchBegin; @@ -4005,7 +4176,7 @@ void DisplayServerX11::process_events() { } break; case XI_TouchUpdate: { - if (ime_window_event) { + if (ime_window_event || ignore_events) { break; } HashMap<int, Vector2>::Iterator curr_pos_elem = xi.state.find(index); @@ -4109,7 +4280,7 @@ void DisplayServerX11::process_events() { case FocusIn: { DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); - if (ime_window_event) { + if (ime_window_event || (event.xfocus.detail == NotifyInferior)) { break; } @@ -4157,7 +4328,7 @@ void DisplayServerX11::process_events() { case FocusOut: { DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); WindowData &wd = windows[window_id]; - if (wd.ime_active && event.xfocus.detail == NotifyInferior) { + if (ime_window_event || (event.xfocus.detail == NotifyInferior)) { break; } if (wd.ime_active) { @@ -4227,7 +4398,7 @@ void DisplayServerX11::process_events() { case ButtonPress: case ButtonRelease: { - if (ime_window_event) { + if (ime_window_event || ignore_events) { break; } /* exit in case of a mouse button press */ @@ -4328,7 +4499,7 @@ void DisplayServerX11::process_events() { } break; case MotionNotify: { - if (ime_window_event) { + if (ime_window_event || ignore_events) { break; } // The X11 API requires filtering one-by-one through the motion @@ -4476,6 +4647,9 @@ void DisplayServerX11::process_events() { } break; case KeyPress: case KeyRelease: { + if (ignore_events) { + break; + } #ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED if (event.type == KeyPress) { DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time); @@ -4827,7 +5001,7 @@ DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, W vformat("Your video card drivers seem not to support the required Vulkan version.\n\n" "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n" "You can enable the OpenGL 3 driver by starting the engine from the\n" - "command line with the command:\n'%s --rendering-driver opengl3'\n\n" + "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n" "If you recently updated your video card drivers, try rebooting.", executable_name), "Unable to initialize Vulkan video driver"); @@ -4850,7 +5024,9 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V #ifdef GLES3_ENABLED if (gl_manager) { - visualInfo = gl_manager->get_vi(x11_display); + Error err; + visualInfo = gl_manager->get_vi(x11_display, err); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display."); vi_selected = true; } #endif @@ -4937,18 +5113,24 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V win_rect.position = wpos; } + // Position and size hints are set from these values before they are updated to the actual + // window size, so we need to initialize them here. + wd.position = win_rect.position; + wd.size = win_rect.size; + { wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), win_rect.position.x, win_rect.position.y, win_rect.size.width > 0 ? win_rect.size.width : 1, win_rect.size.height > 0 ? win_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes); + wd.parent = RootWindow(x11_display, visualInfo.screen); XSetWindowAttributes window_attributes_ime = {}; window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime); - - if (dead_tbl && xkb_loaded) { +#ifdef XKB_ENABLED + if (dead_tbl && xkb_loaded_v05p) { wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS); } - +#endif // Enable receiving notification when the window is initialized (MapNotify) // so the focus can be set at the right time. if (!wd.no_focus && !wd.is_popup) { @@ -5213,6 +5395,7 @@ static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMSt DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) { KeyMappingX11::initialize(); +#ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED int dylibloader_verbose = 1; #else @@ -5227,9 +5410,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Can't load XCursor dynamically."); } - - xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0); - +#ifdef XKB_ENABLED + bool xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0); + xkb_loaded_v05p = xkb_loaded; + if (!xkb_context_new || !xkb_compose_table_new_from_locale || !xkb_compose_table_unref || !xkb_context_unref || !xkb_compose_state_feed || !xkb_compose_state_unref || !xkb_compose_state_new || !xkb_compose_state_get_status || !xkb_compose_state_get_utf8) { + xkb_loaded_v05p = false; + print_verbose("Detected XKBcommon library version older than 0.5, dead key composition and Unicode key labels disabled."); + } + xkb_loaded_v08p = xkb_loaded; + if (!xkb_keysym_to_utf32 || !xkb_keysym_to_upper) { + xkb_loaded_v08p = false; + print_verbose("Detected XKBcommon library version older than 0.8, Unicode key labels disabled."); + } +#endif if (initialize_xext(dylibloader_verbose) != 0) { r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Can't load Xext dynamically."); @@ -5254,7 +5447,13 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Can't load Xinput2 dynamically."); } +#else +#ifdef XKB_ENABLED + xkb_loaded = true; +#endif +#endif +#ifdef XKB_ENABLED if (xkb_loaded) { xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (xkb_ctx) { @@ -5271,14 +5470,24 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode dead_tbl = xkb_compose_table_new_from_locale(xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); } } +#endif Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); r_error = OK; + { + if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) { + // There's no API to check version, check if functions are available instead. + ERR_PRINT("Unsupported Xcursor library version."); + r_error = ERR_UNAVAILABLE; + return; + } + } + for (int i = 0; i < CURSOR_MAX; i++) { cursors[i] = None; - img[i] = nullptr; + cursor_img[i] = nullptr; } XInitThreads(); //always use threads @@ -5292,6 +5501,71 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode return; } + { + int version_major = 0; + int version_minor = 0; + int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor)); + if (rc != 1 || version_major < 1) { + ERR_PRINT("Unsupported Xshape library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor)); + if (rc != 1 || version_major < 1) { + ERR_PRINT("Unsupported Xinerama library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XRRQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor)); + if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) { + ERR_PRINT("Unsupported Xrandr library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XRenderQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xrender %d.%d detected.", version_major, version_minor)); + if (rc != 1 || (version_major == 0 && version_minor < 11)) { + ERR_PRINT("Unsupported Xrender library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 2; // Report 2.2 as supported by engine, but should work with 2.1 or 2.0 library as well. + int version_minor = 2; + int rc = XIQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xinput %d.%d detected.", version_major, version_minor)); + if (rc != Success || (version_major < 2)) { + ERR_PRINT("Unsupported Xinput2 library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + char *modifiers = nullptr; Bool xkb_dar = False; XAutoRepeatOn(x11_display); @@ -5406,7 +5680,10 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode #ifdef SPEECHD_ENABLED // Init TTS - tts = memnew(TTS_Linux); + bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech"); + if (tts_enabled) { + tts = memnew(TTS_Linux); + } #endif //!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -5570,8 +5847,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode "question_arrow" }; - img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); - if (!img[i]) { + cursor_img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); + if (!cursor_img[i]) { const char *fallback = nullptr; switch (i) { @@ -5609,7 +5886,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode fallback = "bd_double_arrow"; break; case CURSOR_MOVE: - img[i] = img[CURSOR_DRAG]; + cursor_img[i] = cursor_img[CURSOR_DRAG]; break; case CURSOR_VSPLIT: fallback = "sb_v_double_arrow"; @@ -5622,11 +5899,11 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode break; } if (fallback != nullptr) { - img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size); + cursor_img[i] = XcursorLibraryLoadImage(fallback, cursor_theme, cursor_size); } } - if (img[i]) { - cursors[i] = XcursorImageLoadCursor(x11_display, img[i]); + if (cursor_img[i]) { + cursors[i] = XcursorImageLoadCursor(x11_display, cursor_img[i]); } else { print_verbose("Failed loading custom cursor: " + String(cursor_file[i])); } @@ -5681,6 +5958,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode portal_desktop = memnew(FreeDesktopPortalDesktop); #endif + XSetErrorHandler(&default_window_error_handler); r_error = OK; } @@ -5713,17 +5991,20 @@ DisplayServerX11::~DisplayServerX11() { wd.xic = nullptr; } XDestroyWindow(x11_display, wd.x11_xim_window); - if (xkb_loaded) { +#ifdef XKB_ENABLED + if (xkb_loaded_v05p) { if (wd.xkb_state) { xkb_compose_state_unref(wd.xkb_state); wd.xkb_state = nullptr; } } +#endif XUnmapWindow(x11_display, wd.x11_window); XDestroyWindow(x11_display, wd.x11_window); } - if (xkb_loaded) { +#ifdef XKB_ENABLED + if (xkb_loaded_v05p) { if (dead_tbl) { xkb_compose_table_unref(dead_tbl); } @@ -5731,6 +6012,7 @@ DisplayServerX11::~DisplayServerX11() { xkb_context_unref(xkb_ctx); } } +#endif //destroy drivers #if defined(VULKAN_ENABLED) @@ -5761,8 +6043,8 @@ DisplayServerX11::~DisplayServerX11() { if (cursors[i] != None) { XFreeCursor(x11_display, cursors[i]); } - if (img[i] != nullptr) { - XcursorImageDestroy(img[i]); + if (cursor_img[i] != nullptr) { + XcursorImageDestroy(cursor_img[i]); } } @@ -5776,7 +6058,9 @@ DisplayServerX11::~DisplayServerX11() { } #ifdef SPEECHD_ENABLED - memdelete(tts); + if (tts) { + memdelete(tts); + } #endif #ifdef DBUS_ENABLED diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index ea54b42262..180362923b 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -33,35 +33,38 @@ #ifdef X11_ENABLED -#include "servers/display_server.h" +#include "joypad_linux.h" #include "core/input/input.h" +#include "core/os/mutex.h" +#include "core/os/thread.h" #include "core/templates/local_vector.h" #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" #include "drivers/unix/os_unix.h" -#include "joypad_linux.h" #include "servers/audio_server.h" +#include "servers/display_server.h" #include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" #if defined(SPEECHD_ENABLED) -#include "../tts_linux.h" +#include "tts_linux.h" #endif #if defined(GLES3_ENABLED) -#include "gl_manager_x11.h" +#include "x11/gl_manager_x11.h" #endif #if defined(VULKAN_ENABLED) +#include "x11/vulkan_context_x11.h" + #include "drivers/vulkan/rendering_device_vulkan.h" -#include "vulkan_context_x11.h" #endif #if defined(DBUS_ENABLED) -#include "../freedesktop_portal_desktop.h" -#include "../freedesktop_screensaver.h" +#include "freedesktop_portal_desktop.h" +#include "freedesktop_screensaver.h" #endif #include <X11/Xatom.h> @@ -69,16 +72,36 @@ #include <X11/Xutil.h> #include <X11/keysym.h> -#include "dynwrappers/xlib-so_wrap.h" +#ifdef SOWRAP_ENABLED +#include "x11/dynwrappers/xlib-so_wrap.h" + +#include "x11/dynwrappers/xcursor-so_wrap.h" +#include "x11/dynwrappers/xext-so_wrap.h" +#include "x11/dynwrappers/xinerama-so_wrap.h" +#include "x11/dynwrappers/xinput2-so_wrap.h" +#include "x11/dynwrappers/xrandr-so_wrap.h" +#include "x11/dynwrappers/xrender-so_wrap.h" -#include "dynwrappers/xcursor-so_wrap.h" -#include "dynwrappers/xext-so_wrap.h" -#include "dynwrappers/xinerama-so_wrap.h" -#include "dynwrappers/xinput2-so_wrap.h" -#include "dynwrappers/xrandr-so_wrap.h" -#include "dynwrappers/xrender-so_wrap.h" +#include "xkbcommon-so_wrap.h" +#else +#include <X11/XKBlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> -#include "../xkbcommon-so_wrap.h" +#include <X11/Xcursor/Xcursor.h> +#include <X11/extensions/XInput2.h> +#include <X11/extensions/Xext.h> +#include <X11/extensions/Xinerama.h> +#include <X11/extensions/Xrandr.h> +#include <X11/extensions/Xrender.h> +#include <X11/extensions/shape.h> + +#ifdef XKB_ENABLED +#include <xkbcommon/xkbcommon-compose.h> +#include <xkbcommon/xkbcommon-keysyms.h> +#include <xkbcommon/xkbcommon.h> +#endif +#endif typedef struct _xrr_monitor_info { Atom name; @@ -97,8 +120,7 @@ typedef struct _xrr_monitor_info { #undef CursorShape class DisplayServerX11 : public DisplayServer { - //No need to register, it's platform-specific and nothing is added - //GDCLASS(DisplayServerX11, DisplayServer) + // No need to register with GDCLASS, it's platform-specific and nothing is added. _THREAD_SAFE_CLASS_ @@ -138,11 +160,14 @@ class DisplayServerX11 : public DisplayServer { struct WindowData { Window x11_window; Window x11_xim_window; + Window parent; ::XIC xic; bool ime_active = false; bool ime_in_progress = false; bool ime_suppress_next_keyup = false; +#ifdef XKB_ENABLED xkb_compose_state *xkb_state = nullptr; +#endif Size2i min_size; Size2i max_size; @@ -186,9 +211,12 @@ class DisplayServerX11 : public DisplayServer { Point2i im_selection; String im_text; - bool xkb_loaded = false; +#ifdef XKB_ENABLED + bool xkb_loaded_v05p = false; + bool xkb_loaded_v08p = false; xkb_context *xkb_ctx = nullptr; xkb_compose_table *dead_tbl = nullptr; +#endif HashMap<WindowID, WindowData> windows; @@ -279,7 +307,7 @@ class DisplayServerX11 : public DisplayServer { const char *cursor_theme = nullptr; int cursor_size = 0; - XcursorImage *img[CURSOR_MAX]; + XcursorImage *cursor_img[CURSOR_MAX]; Cursor cursors[CURSOR_MAX]; Cursor null_cursor; CursorShape current_cursor = CURSOR_ARROW; @@ -378,11 +406,14 @@ public: virtual int get_screen_count() const override; virtual int get_primary_screen() const override; + virtual int get_keyboard_focus_screen() const override; virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Color screen_get_pixel(const Point2i &p_position) const override; + virtual Ref<Image> screen_get_image(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; #if defined(DBUS_ENABLED) virtual void screen_set_keep_on(bool p_enable) override; @@ -446,6 +477,7 @@ public: virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override; virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; @@ -491,6 +523,6 @@ public: ~DisplayServerX11(); }; -#endif // X11 enabled +#endif // X11_ENABLED #endif // DISPLAY_SERVER_X11_H diff --git a/platform/linuxbsd/x11/gl_manager_x11.cpp b/platform/linuxbsd/x11/gl_manager_x11.cpp index 03ba95f475..f24bac5e19 100644 --- a/platform/linuxbsd/x11/gl_manager_x11.cpp +++ b/platform/linuxbsd/x11/gl_manager_x11.cpp @@ -30,20 +30,22 @@ #include "gl_manager_x11.h" -#ifdef X11_ENABLED -#if defined(GLES3_ENABLED) +#if defined(X11_ENABLED) && defined(GLES3_ENABLED) + +#include "thirdparty/glad/glad/glx.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> -#include "thirdparty/glad/glad/glx.h" - #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); +// To prevent shadowing warnings +#undef glXCreateContextAttribsARB + struct GLManager_X11_Private { ::GLXContext glx_context; }; @@ -83,8 +85,13 @@ int GLManager_X11::_find_or_create_display(Display *p_x11_display) { d.context = memnew(GLManager_X11_Private); d.context->glx_context = nullptr; - //Error err = _create_context(d); - _create_context(d); + Error err = _create_context(d); + + if (err != OK) { + _displays.remove_at(new_display_id); + return -1; + } + return new_display_id; } @@ -191,8 +198,14 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { return OK; } -XVisualInfo GLManager_X11::get_vi(Display *p_display) { - return _displays[_find_or_create_display(p_display)].x_vi; +XVisualInfo GLManager_X11::get_vi(Display *p_display, Error &r_error) { + int display_id = _find_or_create_display(p_display); + if (display_id < 0) { + r_error = FAILED; + return XVisualInfo(); + } + r_error = OK; + return _displays[display_id].x_vi; } Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { @@ -211,6 +224,10 @@ Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window win.x11_window = p_window; win.gldisplay_id = _find_or_create_display(p_display); + if (win.gldisplay_id == -1) { + return FAILED; + } + // the display could be invalid .. check NYI GLDisplay &gl_display = _displays[win.gldisplay_id]; ::Display *x11_display = gl_display.x11_display; @@ -385,5 +402,4 @@ GLManager_X11::~GLManager_X11() { release_current(); } -#endif -#endif +#endif // X11_ENABLED && GLES3_ENABLED diff --git a/platform/linuxbsd/x11/gl_manager_x11.h b/platform/linuxbsd/x11/gl_manager_x11.h index 713b13376c..59e20fec45 100644 --- a/platform/linuxbsd/x11/gl_manager_x11.h +++ b/platform/linuxbsd/x11/gl_manager_x11.h @@ -31,16 +31,26 @@ #ifndef GL_MANAGER_X11_H #define GL_MANAGER_X11_H -#ifdef X11_ENABLED - -#ifdef GLES3_ENABLED +#if defined(X11_ENABLED) && defined(GLES3_ENABLED) #include "core/os/os.h" #include "core/templates/local_vector.h" -#include "dynwrappers/xext-so_wrap.h" +#include "servers/display_server.h" + +#ifdef SOWRAP_ENABLED #include "dynwrappers/xlib-so_wrap.h" + +#include "dynwrappers/xext-so_wrap.h" #include "dynwrappers/xrender-so_wrap.h" -#include "servers/display_server.h" +#else +#include <X11/XKBlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <X11/extensions/Xext.h> +#include <X11/extensions/Xrender.h> +#include <X11/extensions/shape.h> +#endif struct GLManager_X11_Private; @@ -101,7 +111,7 @@ private: Error _create_context(GLDisplay &gl_display); public: - XVisualInfo get_vi(Display *p_display); + XVisualInfo get_vi(Display *p_display, Error &r_error); Error window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height); void window_destroy(DisplayServer::WindowID p_window_id); void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); @@ -123,7 +133,6 @@ public: ~GLManager_X11(); }; -#endif // GLES3_ENABLED -#endif // X11_ENABLED +#endif // X11_ENABLED && GLES3_ENABLED #endif // GL_MANAGER_X11_H diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp index e5eba6ccad..0f709872cb 100644 --- a/platform/linuxbsd/x11/key_mapping_x11.cpp +++ b/platform/linuxbsd/x11/key_mapping_x11.cpp @@ -85,8 +85,8 @@ void KeyMappingX11::initialize() { xkeysym_map[XK_Begin] = Key::CLEAR; xkeysym_map[XK_Insert] = Key::INSERT; xkeysym_map[XK_Delete] = Key::KEY_DELETE; - //xkeysym_map[XK_KP_Equal] - //xkeysym_map[XK_KP_Separator] + xkeysym_map[XK_KP_Equal] = Key::EQUAL; + xkeysym_map[XK_KP_Separator] = Key::COMMA; xkeysym_map[XK_KP_Decimal] = Key::KP_PERIOD; xkeysym_map[XK_KP_Delete] = Key::KP_PERIOD; xkeysym_map[XK_KP_Multiply] = Key::KP_MULTIPLY; @@ -217,10 +217,10 @@ void KeyMappingX11::initialize() { scancode_map[0x1F] = Key::I; scancode_map[0x20] = Key::O; scancode_map[0x21] = Key::P; - scancode_map[0x22] = Key::BRACELEFT; - scancode_map[0x23] = Key::BRACERIGHT; + scancode_map[0x22] = Key::BRACKETLEFT; + scancode_map[0x23] = Key::BRACKETRIGHT; scancode_map[0x24] = Key::ENTER; - scancode_map[0x25] = Key::CTRL; + scancode_map[0x25] = Key::CTRL; // Left scancode_map[0x26] = Key::A; scancode_map[0x27] = Key::S; scancode_map[0x28] = Key::D; @@ -232,8 +232,8 @@ void KeyMappingX11::initialize() { scancode_map[0x2E] = Key::L; scancode_map[0x2F] = Key::SEMICOLON; scancode_map[0x30] = Key::APOSTROPHE; - scancode_map[0x31] = Key::SECTION; - scancode_map[0x32] = Key::SHIFT; + scancode_map[0x31] = Key::QUOTELEFT; + scancode_map[0x32] = Key::SHIFT; // Left scancode_map[0x33] = Key::BACKSLASH; scancode_map[0x34] = Key::Z; scancode_map[0x35] = Key::X; @@ -245,9 +245,9 @@ void KeyMappingX11::initialize() { scancode_map[0x3B] = Key::COMMA; scancode_map[0x3C] = Key::PERIOD; scancode_map[0x3D] = Key::SLASH; - scancode_map[0x3E] = Key::SHIFT; + scancode_map[0x3E] = Key::SHIFT; // Right scancode_map[0x3F] = Key::KP_MULTIPLY; - scancode_map[0x40] = Key::ALT; + scancode_map[0x40] = Key::ALT; // Left scancode_map[0x41] = Key::SPACE; scancode_map[0x42] = Key::CAPSLOCK; scancode_map[0x43] = Key::F1; @@ -275,14 +275,23 @@ void KeyMappingX11::initialize() { scancode_map[0x59] = Key::KP_3; scancode_map[0x5A] = Key::KP_0; scancode_map[0x5B] = Key::KP_PERIOD; - scancode_map[0x5E] = Key::QUOTELEFT; + //scancode_map[0x5C] + //scancode_map[0x5D] // Zenkaku Hankaku + scancode_map[0x5E] = Key::SECTION; scancode_map[0x5F] = Key::F11; scancode_map[0x60] = Key::F12; + //scancode_map[0x61] // Romaji + //scancode_map[0x62] // Katakana + //scancode_map[0x63] // Hiragana + //scancode_map[0x64] // Henkan + //scancode_map[0x65] // Hiragana Katakana + //scancode_map[0x66] // Muhenkan + scancode_map[0x67] = Key::COMMA; // KP_Separator scancode_map[0x68] = Key::KP_ENTER; - scancode_map[0x69] = Key::CTRL; + scancode_map[0x69] = Key::CTRL; // Right scancode_map[0x6A] = Key::KP_DIVIDE; scancode_map[0x6B] = Key::PRINT; - scancode_map[0x6C] = Key::ALT; + scancode_map[0x6C] = Key::ALT; // Right scancode_map[0x6D] = Key::ENTER; scancode_map[0x6E] = Key::HOME; scancode_map[0x6F] = Key::UP; @@ -294,13 +303,28 @@ void KeyMappingX11::initialize() { scancode_map[0x75] = Key::PAGEDOWN; scancode_map[0x76] = Key::INSERT; scancode_map[0x77] = Key::KEY_DELETE; + //scancode_map[0x78] // Macro scancode_map[0x79] = Key::VOLUMEMUTE; scancode_map[0x7A] = Key::VOLUMEDOWN; scancode_map[0x7B] = Key::VOLUMEUP; + //scancode_map[0x7C] // Power + scancode_map[0x7D] = Key::EQUAL; // KP_Equal + //scancode_map[0x7E] // KP_PlusMinus scancode_map[0x7F] = Key::PAUSE; - scancode_map[0x85] = Key::META; - scancode_map[0x86] = Key::META; + scancode_map[0x80] = Key::LAUNCH0; + scancode_map[0x81] = Key::COMMA; // KP_Comma + //scancode_map[0x82] // Hangul + //scancode_map[0x83] // Hangul_Hanja + scancode_map[0x84] = Key::YEN; + scancode_map[0x85] = Key::META; // Left + scancode_map[0x86] = Key::META; // Right scancode_map[0x87] = Key::MENU; + + scancode_map[0xA6] = Key::BACK; // On Chromebooks + scancode_map[0xA7] = Key::FORWARD; // On Chromebooks + + scancode_map[0xB5] = Key::REFRESH; // On Chromebooks + scancode_map[0xBF] = Key::F13; scancode_map[0xC0] = Key::F14; scancode_map[0xC1] = Key::F15; diff --git a/platform/linuxbsd/x11/key_mapping_x11.h b/platform/linuxbsd/x11/key_mapping_x11.h index 48beefff4c..ae8fd67f27 100644 --- a/platform/linuxbsd/x11/key_mapping_x11.h +++ b/platform/linuxbsd/x11/key_mapping_x11.h @@ -31,16 +31,17 @@ #ifndef KEY_MAPPING_X11_H #define KEY_MAPPING_X11_H +#include "core/os/keyboard.h" +#include "core/templates/hash_map.h" + #include <X11/XF86keysym.h> #include <X11/Xlib.h> + #define XK_MISCELLANY #define XK_LATIN1 #define XK_XKB_KEYS #include <X11/keysymdef.h> -#include "core/os/keyboard.h" -#include "core/templates/hash_map.h" - class KeyMappingX11 { struct HashMapHasherKeys { static _FORCE_INLINE_ uint32_t hash(const Key p_key) { return hash_fmix32(static_cast<uint32_t>(p_key)); } diff --git a/platform/linuxbsd/x11/vulkan_context_x11.h b/platform/linuxbsd/x11/vulkan_context_x11.h index d093bca853..294fdc710e 100644 --- a/platform/linuxbsd/x11/vulkan_context_x11.h +++ b/platform/linuxbsd/x11/vulkan_context_x11.h @@ -34,6 +34,7 @@ #ifdef VULKAN_ENABLED #include "drivers/vulkan/vulkan_context.h" + #include <X11/Xlib.h> class VulkanContextX11 : public VulkanContext { |