diff options
Diffstat (limited to 'platform/linuxbsd')
-rw-r--r-- | platform/linuxbsd/README.md | 14 | ||||
-rw-r--r-- | platform/linuxbsd/SCsub | 1 | ||||
-rw-r--r-- | platform/linuxbsd/detect.py | 64 | ||||
-rw-r--r-- | platform/linuxbsd/display_server_x11.cpp | 301 | ||||
-rw-r--r-- | platform/linuxbsd/display_server_x11.h | 19 | ||||
-rw-r--r-- | platform/linuxbsd/freedesktop_portal_desktop.cpp | 135 | ||||
-rw-r--r-- | platform/linuxbsd/freedesktop_portal_desktop.h | 59 | ||||
-rw-r--r-- | platform/linuxbsd/gl_manager_x11.cpp | 28 | ||||
-rw-r--r-- | platform/linuxbsd/gl_manager_x11.h | 3 | ||||
-rw-r--r-- | platform/linuxbsd/os_linuxbsd.cpp | 22 | ||||
-rw-r--r-- | platform/linuxbsd/os_linuxbsd.h | 2 |
11 files changed, 465 insertions, 183 deletions
diff --git a/platform/linuxbsd/README.md b/platform/linuxbsd/README.md index 0d3fb37be5..efa8682062 100644 --- a/platform/linuxbsd/README.md +++ b/platform/linuxbsd/README.md @@ -2,10 +2,20 @@ This folder contains the C++ code for the Linux/*BSD platform port. +See also [`misc/dist/linux`](/misc/dist/linux) folder for additional files +used by this platform. + +## Documentation + +- [Compiling for Linux/*BSD](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_linuxbsd.html) + - Instructions on building this platform port from source. +- [Exporting for Linux/*BSD](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_linux.html) + - Instructions on using the compiled export templates to export a project. + ## Artwork license [`logo.png`](logo.png) is derived from the [Linux logo](https://isc.tamu.edu/~lewing/linux/): > Permission to use and/or modify this image is granted provided you acknowledge me - <lewing@isc.tamu.edu> and [The GIMP](https://isc.tamu.edu/~lewing/gimp/) - if someone asks. +> <lewing@isc.tamu.edu> and [The GIMP](https://isc.tamu.edu/~lewing/gimp/) +> if someone asks. diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index 35c41556ee..91d45627b9 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -9,6 +9,7 @@ common_linuxbsd = [ "crash_handler_linuxbsd.cpp", "os_linuxbsd.cpp", "joypad_linux.cpp", + "freedesktop_portal_desktop.cpp", "freedesktop_screensaver.cpp", ] diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 00e2b9e6eb..36644d5f29 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -2,6 +2,7 @@ import os import platform import sys from methods import get_compiler_version, using_gcc +from platform_methods import detect_arch def is_active(): @@ -30,7 +31,6 @@ def get_opts(): return [ EnumVariable("linker", "Linker program", "default", ("default", "bfd", "gold", "lld", "mold")), BoolVariable("use_llvm", "Use the LLVM compiler", False), - BoolVariable("use_thinlto", "Use ThinLTO (LLVM only, requires linker=lld, implies use_lto=yes)", False), BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", True), BoolVariable("use_coverage", "Test Godot coverage", False), BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False), @@ -39,7 +39,7 @@ def get_opts(): BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False), BoolVariable("pulseaudio", "Detect and use PulseAudio", True), - BoolVariable("dbus", "Detect and use D-Bus to handle screensaver", True), + BoolVariable("dbus", "Detect and use D-Bus to handle screensaver and portal desktop settings", True), BoolVariable("speechd", "Detect and use Speech Dispatcher for Text-to-Speech support", True), BoolVariable("fontconfig", "Detect and use fontconfig for system fonts support", True), BoolVariable("udev", "Use udev for gamepad connection callbacks", True), @@ -52,10 +52,21 @@ def get_opts(): def get_flags(): - return [] + return [ + ("arch", detect_arch()), + ] def configure(env): + # Validate arch. + supported_arches = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64"] + if env["arch"] not in supported_arches: + print( + 'Unsupported CPU architecture "%s" for Linux / *BSD. Supported architectures are: %s.' + % (env["arch"], ", ".join(supported_arches)) + ) + sys.exit() + ## Build type if env["target"] == "release": @@ -80,23 +91,7 @@ def configure(env): env.Prepend(CCFLAGS=["-g3"]) env.Append(LINKFLAGS=["-rdynamic"]) - ## Architecture - - is64 = sys.maxsize > 2**32 - if env["bits"] == "default": - env["bits"] = "64" if is64 else "32" - - machines = { - "riscv64": "rv64", - "ppc64le": "ppc64", - "ppc64": "ppc64", - "ppcle": "ppc", - "ppc": "ppc", - } - - if env["arch"] == "" and platform.machine() in machines: - env["arch"] = machines[platform.machine()] - + # CPU architecture flags. if env["arch"] == "rv64": # G = General-purpose extensions, C = Compression extension (very common). env.Append(CCFLAGS=["-march=rv64gc"]) @@ -133,13 +128,6 @@ def configure(env): else: env.Append(LINKFLAGS=["-fuse-ld=%s" % env["linker"]]) - if env["use_thinlto"]: - if not env["use_llvm"] or env["linker"] != "lld": - print("ThinLTO is only compatible with LLVM and the LLD linker, use `use_llvm=yes linker=lld`.") - sys.exit(255) - else: - env["use_lto"] = True # ThinLTO implies LTO - if env["use_coverage"]: env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"]) env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"]) @@ -182,8 +170,12 @@ def configure(env): env.Append(CCFLAGS=["-fsanitize-recover=memory"]) env.Append(LINKFLAGS=["-fsanitize=memory"]) - if env["use_lto"]: - if env["use_thinlto"]: + # LTO + if env["lto"] != "none": + if env["lto"] == "thin": + if not env["use_llvm"]: + print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") + sys.exit(255) env.Append(CCFLAGS=["-flto=thin"]) env.Append(LINKFLAGS=["-flto=thin"]) elif not env["use_llvm"] and env.GetOption("num_jobs") > 1: @@ -262,8 +254,7 @@ def configure(env): env["builtin_libvorbis"] = False # Needed to link against system libtheora env.ParseConfig("pkg-config theora theoradec --cflags --libs") else: - list_of_x86 = ["x86_64", "x86", "i386", "i586"] - if any(platform.machine() in s for s in list_of_x86): + if env["arch"] in ["x86_64", "x86_32"]: env["x86_libtheora_opt_gcc"] = True if not env["builtin_libvorbis"]: @@ -392,7 +383,9 @@ def configure(env): import subprocess import re - linker_version_str = subprocess.check_output([env.subst(env["LINK"]), "-Wl,--version"]).decode("utf-8") + linker_version_str = subprocess.check_output( + [env.subst(env["LINK"]), "-Wl,--version"] + env.subst(env["LINKFLAGS"]) + ).decode("utf-8") gnu_ld_version = re.search("^GNU ld [^$]*(\d+\.\d+)$", linker_version_str, re.MULTILINE) if not gnu_ld_version: print( @@ -405,11 +398,12 @@ def configure(env): env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.legacy.ld"]) ## Cross-compilation - - if is64 and env["bits"] == "32": + # TODO: Support cross-compilation on architectures other than x86. + host_is_64_bit = sys.maxsize > 2**32 + if host_is_64_bit and env["arch"] == "x86_32": env.Append(CCFLAGS=["-m32"]) env.Append(LINKFLAGS=["-m32", "-L/usr/lib/i386-linux-gnu"]) - elif not is64 and env["bits"] == "64": + elif not host_is_64_bit and env["arch"] == "x86_64": env.Append(CCFLAGS=["-m64"]) env.Append(LINKFLAGS=["-m64", "-L/usr/lib/i686-linux-gnu"]) diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 3409e43ba5..0236e134fb 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -322,8 +322,8 @@ bool DisplayServerX11::tts_is_paused() const { return tts->is_paused(); } -Array DisplayServerX11::tts_get_voices() const { - ERR_FAIL_COND_V(!tts, Array()); +TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const { + ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>()); return tts->get_voices(); } @@ -349,6 +349,28 @@ void DisplayServerX11::tts_stop() { #endif +#ifdef DBUS_ENABLED + +bool DisplayServerX11::is_dark_mode_supported() const { + return portal_desktop->is_supported(); +} + +bool DisplayServerX11::is_dark_mode() const { + switch (portal_desktop->get_appearance_color_scheme()) { + case 1: + // Prefers dark theme. + return true; + case 2: + // Prefers light theme. + return false; + default: + // Preference unknown. + return false; + } +} + +#endif + void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ @@ -993,7 +1015,7 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y); if (left_rect.size.x > 0) { Rect2i intersection = rect.intersection(left_rect); - if (!intersection.has_no_area() && intersection.size.x < rect.size.x) { + if (intersection.has_area() && intersection.size.x < rect.size.x) { rect.position.x = left_rect.size.x; rect.size.x = rect.size.x - intersection.size.x; } @@ -1002,7 +1024,7 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y); if (right_rect.size.x > 0) { Rect2i intersection = rect.intersection(right_rect); - if (!intersection.has_no_area() && right_rect.size.x < rect.size.x) { + if (intersection.has_area() && right_rect.size.x < rect.size.x) { rect.size.x = intersection.position.x - rect.position.x; } } @@ -1010,7 +1032,7 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top); if (top_rect.size.y > 0) { Rect2i intersection = rect.intersection(top_rect); - if (!intersection.has_no_area() && intersection.size.y < rect.size.y) { + if (intersection.has_area() && intersection.size.y < rect.size.y) { rect.position.y = top_rect.size.y; rect.size.y = rect.size.y - intersection.size.y; } @@ -1019,7 +1041,7 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom); if (bottom_rect.size.y > 0) { Rect2i intersection = rect.intersection(bottom_rect); - if (!intersection.has_no_area() && right_rect.size.y < rect.size.y) { + if (intersection.has_area() && right_rect.size.y < rect.size.y) { rect.size.y = intersection.position.y - rect.position.y; } } @@ -1522,11 +1544,15 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent XSetTransientForHint(x11_display, wd_window.x11_window, None); + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd_parent.x11_window, &xwa); + // Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu. // RevertToPointerRoot is used to make sure we don't lose all focus in case // a subwindow and its parent are both destroyed. if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) { - if (!wd_parent.no_focus && !wd_window.is_popup) { + if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) { XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); } } @@ -1819,6 +1845,47 @@ bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_a return retval; } +bool DisplayServerX11::_window_minimize_check(WindowID p_window) const { + const WindowData &wd = windows[p_window]; + + // Using ICCCM -- Inter-Client Communication Conventions Manual + Atom property = XInternAtom(x11_display, "WM_STATE", True); + if (property == None) { + return false; + } + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + + int result = XGetWindowProperty( + x11_display, + wd.x11_window, + property, + 0, + 32, + False, + AnyPropertyType, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success && data) { + long *state = (long *)data; + if (state[0] == WM_IconicState) { + XFree(data); + return true; + } + XFree(data); + } + + return false; +} + bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const { ERR_FAIL_COND_V(!windows.has(p_window), false); const WindowData &wd = windows[p_window]; @@ -1865,6 +1932,18 @@ bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const { return retval; } +void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { + // Check if we applied any window modes that didn't take effect while unmapped + const WindowData &wd = windows[p_window]; + if (wd.fullscreen && !_window_fullscreen_check(p_window)) { + _set_wm_fullscreen(p_window, true); + } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) { + _set_wm_maximized(p_window, true); + } else if (wd.minimized && !_window_minimize_check(p_window)) { + _set_wm_minimized(p_window, true); + } +} + bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { _THREAD_SAFE_METHOD_ return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS"); @@ -1899,6 +1978,37 @@ void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { usleep(10000); } } + wd.maximized = p_enabled; +} + +void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) { + WindowData &wd = windows[p_window]; + // Using ICCCM -- Inter-Client Communication Conventions Manual + XEvent xev; + Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_change; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.xclient.data.l[1] = wm_hidden; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + wd.minimized = p_enabled; } void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { @@ -1980,32 +2090,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { //do nothing } break; case WINDOW_MODE_MINIMIZED: { - //Un-Minimize - // Using ICCCM -- Inter-Client Communication Conventions Manual - XEvent xev; - Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_change; - xev.xclient.format = 32; - xev.xclient.data.l[0] = WM_NormalState; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = _NET_WM_STATE_ADD; - xev.xclient.data.l[1] = wm_hidden; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + _set_wm_minimized(p_window, false); } break; case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { @@ -2034,31 +2119,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { //do nothing } break; case WINDOW_MODE_MINIMIZED: { - // Using ICCCM -- Inter-Client Communication Conventions Manual - XEvent xev; - Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_change; - xev.xclient.format = 32; - xev.xclient.data.l[0] = WM_IconicState; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = _NET_WM_STATE_ADD; - xev.xclient.data.l[1] = wm_hidden; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + _set_wm_minimized(p_window, true); } break; case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { @@ -2093,40 +2154,9 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c return WINDOW_MODE_MAXIMIZED; } - { // Test minimized. - // Using ICCCM -- Inter-Client Communication Conventions Manual - Atom property = XInternAtom(x11_display, "WM_STATE", True); - if (property == None) { - return WINDOW_MODE_WINDOWED; - } - - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - - int result = XGetWindowProperty( - x11_display, - wd.x11_window, - property, - 0, - 32, - False, - AnyPropertyType, - &type, - &format, - &len, - &remaining, - &data); - - if (result == Success && data) { - long *state = (long *)data; - if (state[0] == WM_IconicState) { - XFree(data); - return WINDOW_MODE_MINIMIZED; - } - XFree(data); + { + if (_window_minimize_check(p_window)) { + return WINDOW_MODE_MINIMIZED; } } @@ -2191,7 +2221,7 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } break; case WINDOW_FLAG_TRANSPARENT: { - //todo reimplement + wd.layered_window = p_enabled; } break; case WINDOW_FLAG_NO_FOCUS: { wd.no_focus = p_enabled; @@ -2244,7 +2274,7 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co return wd.on_top; } break; case WINDOW_FLAG_TRANSPARENT: { - //todo reimplement + return wd.layered_window; } break; case WINDOW_FLAG_NO_FOCUS: { return wd.no_focus; @@ -3646,12 +3676,19 @@ void DisplayServerX11::process_events() { const WindowData &wd = windows[window_id]; + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + // Set focus when menu window is started. // RevertToPointerRoot is used to make sure we don't lose all focus in case // a subwindow and its parent are both destroyed. - if (!wd.no_focus && !wd.is_popup) { + if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); } + + // Have we failed to set fullscreen while the window was unmapped? + _validate_mode_on_map(window_id); } break; case Expose: { @@ -3671,8 +3708,7 @@ void DisplayServerX11::process_events() { case VisibilityNotify: { DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); - XVisibilityEvent *visibility = (XVisibilityEvent *)&event; - windows[window_id].minimized = (visibility->state == VisibilityFullyObscured); + windows[window_id].minimized = _window_minimize_check(window_id); } break; case LeaveNotify: { @@ -4390,13 +4426,41 @@ DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, W DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { //Create window - long visualMask = VisualScreenMask; - int numberOfVisuals; - XVisualInfo vInfoTemplate = {}; - vInfoTemplate.screen = DefaultScreen(x11_display); - XVisualInfo *visualInfo = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); + XVisualInfo visualInfo; + bool vi_selected = false; - Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, vInfoTemplate.screen), visualInfo->visual, AllocNone); +#ifdef GLES3_ENABLED + if (gl_manager) { + visualInfo = gl_manager->get_vi(x11_display); + vi_selected = true; + } +#endif + + if (!vi_selected) { + long visualMask = VisualScreenMask; + int numberOfVisuals; + XVisualInfo vInfoTemplate = {}; + vInfoTemplate.screen = DefaultScreen(x11_display); + XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); + ERR_FAIL_COND_V(!vi_list, INVALID_WINDOW_ID); + + visualInfo = vi_list[0]; + if (OS::get_singleton()->is_layered_allowed()) { + for (int i = 0; i < numberOfVisuals; i++) { + XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual); + if (!pict_format) { + continue; + } + visualInfo = vi_list[i]; + if (pict_format->direct.alphaMask > 0) { + break; + } + } + } + XFree(vi_list); + } + + Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone); XSetWindowAttributes windowAttributes = {}; windowAttributes.colormap = colormap; @@ -4406,6 +4470,13 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; + if (OS::get_singleton()->is_layered_allowed()) { + windowAttributes.background_pixmap = None; + windowAttributes.background_pixel = 0; + windowAttributes.border_pixmap = None; + valuemask |= CWBackPixel; + } + WindowID id = window_id_counter++; WindowData &wd = windows[id]; @@ -4429,7 +4500,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V } { - wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); + wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes); // Enable receiving notification when the window is initialized (MapNotify) // so the focus can be set at the right time. @@ -4566,8 +4637,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V XSync(x11_display, False); //XSetErrorHandler(oldHandler); - - XFree(visualInfo); } window_set_mode(p_mode, id); @@ -4831,6 +4900,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } show_window(main_window); + XSync(x11_display, False); + _validate_mode_on_map(main_window); #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { @@ -4977,21 +5048,22 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } cursor_set_shape(CURSOR_BUSY); - XEvent xevent; - while (XPending(x11_display) > 0) { - XNextEvent(x11_display, &xevent); - if (xevent.type == ConfigureNotify) { - _window_changed(&xevent); - } + // Search the X11 event queue for ConfigureNotify events and process all + // that are currently queued early, so we can get the final window size + // for correctly drawing of the bootsplash. + XEvent config_event; + while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) { + _window_changed(&config_event); } - events_thread.start(_poll_events_thread, this); _update_real_mouse_position(windows[MAIN_WINDOW_ID]); #ifdef DBUS_ENABLED screensaver = memnew(FreeDesktopScreenSaver); - screen_set_keep_on(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)); + screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); + + portal_desktop = memnew(FreeDesktopPortalDesktop); #endif r_error = OK; @@ -5077,6 +5149,7 @@ DisplayServerX11::~DisplayServerX11() { #ifdef DBUS_ENABLED memdelete(screensaver); + memdelete(portal_desktop); #endif } diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 9ce6a557db..ea03b2328c 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -60,6 +60,7 @@ #endif #if defined(DBUS_ENABLED) +#include "freedesktop_portal_desktop.h" #include "freedesktop_screensaver.h" #endif @@ -120,6 +121,10 @@ class DisplayServerX11 : public DisplayServer { TTS_Linux *tts = nullptr; #endif +#if defined(DBUS_ENABLED) + FreeDesktopPortalDesktop *portal_desktop = nullptr; +#endif + struct WindowData { Window x11_window; ::XIC xic; @@ -152,7 +157,9 @@ class DisplayServerX11 : public DisplayServer { Vector2i last_position_before_fs; bool focused = true; bool minimized = false; + bool maximized = false; bool is_popup = false; + bool layered_window = false; Rect2i parent_safe_rect; @@ -245,8 +252,6 @@ class DisplayServerX11 : public DisplayServer { CursorShape current_cursor = CURSOR_ARROW; HashMap<CursorShape, Vector<Variant>> cursors_cache; - bool layered_window = false; - String rendering_driver; void set_wm_fullscreen(bool p_enabled); void set_wm_above(bool p_enabled); @@ -268,9 +273,12 @@ class DisplayServerX11 : public DisplayServer { void _update_real_mouse_position(const WindowData &wd); bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; bool _window_fullscreen_check(WindowID p_window) const; + bool _window_minimize_check(WindowID p_window) const; + void _validate_mode_on_map(WindowID p_window); void _update_size_hints(WindowID p_window); void _set_wm_fullscreen(WindowID p_window, bool p_enabled); void _set_wm_maximized(WindowID p_window, bool p_enabled); + void _set_wm_minimized(WindowID p_window, bool p_enabled); void _update_context(WindowData &wd); @@ -308,7 +316,7 @@ public: #ifdef SPEECHD_ENABLED virtual bool tts_is_speaking() const override; virtual bool tts_is_paused() const override; - virtual Array tts_get_voices() const override; + virtual TypedArray<Dictionary> tts_get_voices() const override; virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; virtual void tts_pause() override; @@ -316,6 +324,11 @@ public: virtual void tts_stop() override; #endif +#if defined(DBUS_ENABLED) + virtual bool is_dark_mode_supported() const override; + virtual bool is_dark_mode() const override; +#endif + virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp new file mode 100644 index 0000000000..ed54084694 --- /dev/null +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* freedesktop_portal_desktop.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "freedesktop_portal_desktop.h" + +#ifdef DBUS_ENABLED + +#include "core/error/error_macros.h" +#include "core/os/os.h" +#include "core/string/ustring.h" + +#include "dbus-so_wrap.h" + +#include "core/variant/variant.h" + +#define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop" +#define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop" + +#define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings" + +static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) { + DBusMessageIter iter[3]; + + dbus_message_iter_init(p_reply_message, &iter[0]); + if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) != p_type) { + return false; + } + + dbus_message_iter_get_basic(&iter[2], r_value); + return true; +} + +bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value) { + if (unsupported) { + return false; + } + + DBusError error; + dbus_error_init(&error); + + DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + unsupported = true; + if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT(String() + "Error opening D-Bus connection: " + error.message); + } + return false; + } + + DBusMessage *message = dbus_message_new_method_call( + BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SETTINGS, + "Read"); + dbus_message_append_args( + message, + DBUS_TYPE_STRING, &p_namespace, + DBUS_TYPE_STRING, &p_key, + DBUS_TYPE_INVALID); + + DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error); + dbus_message_unref(message); + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + dbus_connection_unref(bus); + if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT(String() + "Error on D-Bus communication: " + error.message); + } + return false; + } + + bool success = try_parse_variant(reply, p_type, r_value); + + dbus_message_unref(reply); + dbus_connection_unref(bus); + + return success; +} + +uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() { + if (unsupported) { + return 0; + } + + uint32_t value = 0; + read_setting("org.freedesktop.appearance", "color-scheme", DBUS_TYPE_UINT32, &value); + return value; +} + +FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { +#ifdef DEBUG_ENABLED + int dylibloader_verbose = 1; +#else + int dylibloader_verbose = 0; +#endif + unsupported = (initialize_dbus(dylibloader_verbose) != 0); +} + +#endif // DBUS_ENABLED diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h new file mode 100644 index 0000000000..3d976b1ede --- /dev/null +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* freedesktop_portal_desktop.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef FREEDESKTOP_PORTAL_DESKTOP_H +#define FREEDESKTOP_PORTAL_DESKTOP_H + +#ifdef DBUS_ENABLED + +#include <stdint.h> + +class FreeDesktopPortalDesktop { +private: + bool unsupported = false; + + // Read a setting from org.freekdesktop.portal.Settings + bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value); + +public: + FreeDesktopPortalDesktop(); + + bool is_supported() { return !unsupported; } + + // Retrieve the system's preferred color scheme. + // 0: No preference or unknown. + // 1: Prefer dark appearance. + // 2: Prefer light appearance. + uint32_t get_appearance_color_scheme(); +}; + +#endif // DBUS_ENABLED + +#endif // FREEDESKTOP_PORTAL_DESKTOP_H diff --git a/platform/linuxbsd/gl_manager_x11.cpp b/platform/linuxbsd/gl_manager_x11.cpp index d3fb1d6705..04c1df71fb 100644 --- a/platform/linuxbsd/gl_manager_x11.cpp +++ b/platform/linuxbsd/gl_manager_x11.cpp @@ -127,10 +127,6 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { GLXFBConfig fbconfig = nullptr; XVisualInfo *vi = nullptr; - gl_display.x_swa.event_mask = StructureNotifyMask; - gl_display.x_swa.border_pixel = 0; - gl_display.x_valuemask = CWBorderPixel | CWColormap | CWEventMask; - if (OS::get_singleton()->is_layered_allowed()) { GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs_layered, &fbcount); ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); @@ -156,12 +152,6 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { XFree(fbc); ERR_FAIL_COND_V(!fbconfig, ERR_UNCONFIGURED); - - gl_display.x_swa.background_pixmap = None; - gl_display.x_swa.background_pixel = 0; - gl_display.x_swa.border_pixmap = None; - gl_display.x_valuemask |= CWBackPixel; - } else { GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount); ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); @@ -189,8 +179,6 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { } break; } - gl_display.x_swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone); - XSync(x11_display, False); XSetErrorHandler(oldHandler); @@ -205,6 +193,10 @@ 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; +} + Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { // make sure vector is big enough... // we can mirror the external vector, it is simpler @@ -223,8 +215,6 @@ Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window // the display could be invalid .. check NYI GLDisplay &gl_display = _displays[win.gldisplay_id]; - //const XVisualInfo &vi = gl_display.x_vi; - //XSetWindowAttributes &swa = gl_display.x_swa; ::Display *x11_display = gl_display.x11_display; ::Window &x11_window = win.x11_window; @@ -315,6 +305,16 @@ void GLManager_X11::swap_buffers() { return; } + // On X11, when enabled, transparancy is always active, so clear alpha manually. + if (OS::get_singleton()->is_layered_allowed()) { + if (!DisplayServer::get_singleton()->window_get_flag(DisplayServer::WINDOW_FLAG_TRANSPARENT, _current_window->window_id)) { + glColorMask(false, false, false, true); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glColorMask(true, true, true, true); + } + } + // print_line("\tswap_buffers"); // only for debugging without drawing anything diff --git a/platform/linuxbsd/gl_manager_x11.h b/platform/linuxbsd/gl_manager_x11.h index fb2c74a2b6..4f78c45c88 100644 --- a/platform/linuxbsd/gl_manager_x11.h +++ b/platform/linuxbsd/gl_manager_x11.h @@ -68,8 +68,6 @@ private: GLManager_X11_Private *context = nullptr; ::Display *x11_display; XVisualInfo x_vi; - XSetWindowAttributes x_swa; - unsigned long x_valuemask; }; // just for convenience, window and display struct @@ -102,6 +100,7 @@ private: Error _create_context(GLDisplay &gl_display); public: + XVisualInfo get_vi(Display *p_display); 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); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index e306c1054b..61faf3061c 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -65,7 +65,7 @@ void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { for (int i = 0; i < path_elems.size(); i++) { for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) { - String tested_path = path_elems[i].plus_file(message_programs[k]); + String tested_path = path_elems[i].path_join(message_programs[k]); if (FileAccess::exists(tested_path)) { program = tested_path; @@ -368,6 +368,7 @@ Vector<String> OS_LinuxBSD::get_system_fonts() const { FcPatternDestroy(pattern); } FcObjectSetDestroy(object_set); + FcConfigDestroy(config); for (const String &E : font_names) { ret.push_back(E); @@ -417,6 +418,8 @@ String OS_LinuxBSD::get_system_font_path(const String &p_font_name, bool p_bold, FcPatternDestroy(pattern); } FcObjectSetDestroy(object_set); + FcConfigDestroy(config); + return ret; #else ERR_FAIL_V_MSG(String(), "Godot was compiled without fontconfig, system font support is disabled."); @@ -429,10 +432,10 @@ String OS_LinuxBSD::get_config_path() const { return get_environment("XDG_CONFIG_HOME"); } else { WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.config` or `.` per the XDG Base Directory specification."); - return has_environment("HOME") ? get_environment("HOME").plus_file(".config") : "."; + return has_environment("HOME") ? get_environment("HOME").path_join(".config") : "."; } } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".config"); + return get_environment("HOME").path_join(".config"); } else { return "."; } @@ -444,10 +447,10 @@ String OS_LinuxBSD::get_data_path() const { return get_environment("XDG_DATA_HOME"); } else { WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.local/share` or `get_config_path()` per the XDG Base Directory specification."); - return has_environment("HOME") ? get_environment("HOME").plus_file(".local/share") : get_config_path(); + return has_environment("HOME") ? get_environment("HOME").path_join(".local/share") : get_config_path(); } } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".local/share"); + return get_environment("HOME").path_join(".local/share"); } else { return get_config_path(); } @@ -459,10 +462,10 @@ String OS_LinuxBSD::get_cache_path() const { return get_environment("XDG_CACHE_HOME"); } else { WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.cache` or `get_config_path()` per the XDG Base Directory specification."); - return has_environment("HOME") ? get_environment("HOME").plus_file(".cache") : get_config_path(); + return has_environment("HOME") ? get_environment("HOME").path_join(".cache") : get_config_path(); } } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".cache"); + return get_environment("HOME").path_join(".cache"); } else { return get_config_path(); } @@ -516,8 +519,6 @@ String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const } void OS_LinuxBSD::run() { - force_quit = false; - if (!main_loop) { return; } @@ -529,7 +530,7 @@ void OS_LinuxBSD::run() { //int frames=0; //uint64_t frame=0; - while (!force_quit) { + while (true) { DisplayServer::get_singleton()->process_events(); // get rid of pending events #ifdef JOYDEV_ENABLED joypad->process_joypads(); @@ -727,7 +728,6 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { OS_LinuxBSD::OS_LinuxBSD() { main_loop = nullptr; - force_quit = false; #ifdef PULSEAUDIO_ENABLED AudioDriverManager::add_driver(&driver_pulseaudio); diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index cc4e91e885..d5b2321316 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -43,8 +43,6 @@ class OS_LinuxBSD : public OS_Unix { virtual void delete_main_loop() override; - bool force_quit; - #ifdef FONTCONFIG_ENABLED bool font_config_initialized = false; #endif |