diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 46c717f527..34e28c9489 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -68,6 +68,22 @@ if(USE_SDL_WINDOW) target_compile_definitions(llwindow PUBLIC LL_SDL_WINDOW=1) + if(DARWIN) + # macOS-only Objective-C++ helper that fixes up SDL's auto-created Cocoa + # menu bar (strips the Cmd+W shortcut off its default Window > Close item). + target_sources(llwindow + PRIVATE + llsdl_macos.mm + PUBLIC + llsdl_macos.h + ) + + # llwindow reuses llprecompiled's C++ PCH; that PCH has no Objective-C++ + # variant, so skip it for the .mm or CMake fails to resolve a cmake_pch.objcxx + # header it expects this target to provide. + set_source_files_properties(llsdl_macos.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + endif() + target_link_libraries(llwindow ll::SDL3 ) diff --git a/indra/llwindow/llsdl.cpp b/indra/llwindow/llsdl.cpp index cb8a892937..9d58a01efb 100644 --- a/indra/llwindow/llsdl.cpp +++ b/indra/llwindow/llsdl.cpp @@ -46,6 +46,10 @@ bool gSDLMainHandled = false; #include "llwin32headers.h" // CS_BYTEALIGNCLIENT / CS_OWNDC #endif +#if LL_DARWIN +#include "llsdl_macos.h" +#endif + void sdl_logger(void *userdata, int category, SDL_LogPriority priority, const char *message) { switch (priority) @@ -133,6 +137,12 @@ void set_sdl_hints() // Momentum scrolling on macos is desirable for mac touchpads {SDL_HINT_MAC_SCROLL_MOMENTUM, "1"}, + + // Don't let SDL post a synthetic SDL_EVENT_QUIT when the + // last window closes. The viewer owns its own shutdown + // sequence (LLAppViewer), and transient teardown of the + // main window must never be read as a request to quit. + {SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, "0"}, }; for (auto hint: hintList) @@ -200,6 +210,14 @@ void init_sdl(const std::string& app_name) } } } + +#if LL_DARWIN + // SDL has now registered the app and built its default Cocoa menu bar. Drop + // the Cmd+W shortcut off its auto-created Window > Close item so the + // shortcut reaches the viewer's own "Close Window" handler instead of + // tearing down the window (which the SDL backend reads as a quit request). + ll_sdl_macos_strip_default_close_shortcut(); +#endif } void quit_sdl() diff --git a/indra/llwindow/llsdl_macos.h b/indra/llwindow/llsdl_macos.h new file mode 100644 index 0000000000..28e5695966 --- /dev/null +++ b/indra/llwindow/llsdl_macos.h @@ -0,0 +1,37 @@ +/** + * @file llsdl_macos.h + * @brief macOS-specific SDL helpers + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +// Remove the Cmd+W key equivalent from the "Close" item in the default macOS +// menu bar that SDL3's Cocoa backend creates for us. See the implementation in +// llsdl_macos.mm for the full rationale. Safe to call once after the first +// SDL_INIT_VIDEO (when SDL has registered the app and built its menu). +void ll_sdl_macos_strip_default_close_shortcut(); + +// Make the given SDL window key again after a native file dialog closes. +struct SDL_Window; +void ll_sdl_macos_make_window_key_deferred(struct SDL_Window* window); diff --git a/indra/llwindow/llsdl_macos.mm b/indra/llwindow/llsdl_macos.mm new file mode 100644 index 0000000000..1a1e707c1b --- /dev/null +++ b/indra/llwindow/llsdl_macos.mm @@ -0,0 +1,104 @@ +/** + * @file llsdl_macos.mm + * @brief macOS-specific SDL helpers + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifdef LL_DARWIN + +#import + +#include "SDL3/SDL.h" + +#include "llsdl_macos.h" + +void ll_sdl_macos_strip_default_close_shortcut() +{ + @autoreleasepool + { + // SDL3's Cocoa backend auto-creates a default menu bar (it does this + // whenever [NSApp mainMenu] is nil at registration) whose "Window" + // menu contains a "Close" item bound to Cmd+W via -performClose:. That + // native item swallows Cmd+W before it can reach the viewer's own + // in-window "Close Window" accelerator (menu_viewer.xml -> control|W -> + // File.CloseWindow, which just closes the frontmost floater). Worse, + // -performClose: on the sole viewer window triggers + // SDL_EVENT_WINDOW_CLOSE_REQUESTED, which LLWindowSDL turns into a full + // application quit. Clear the key equivalent so Cmd+W falls through to + // LLMenuGL instead of quitting the viewer. The menu item itself stays + // (still clickable); only its keyboard shortcut is removed. + // SDL parks "Close" in the menu it hands to -setWindowsMenu:, but scan + // every top-level submenu rather than assuming that location, so this + // keeps working if SDL ever rearranges its default bar. + NSMenu* main_menu = [NSApp mainMenu]; + if (!main_menu) + { + return; + } + + for (NSMenuItem* top_item in [main_menu itemArray]) + { + NSMenu* submenu = [top_item submenu]; + if (!submenu) + { + continue; + } + + for (NSMenuItem* item in [submenu itemArray]) + { + if ([item action] == @selector(performClose:)) + { + [item setKeyEquivalent:@""]; + [item setKeyEquivalentModifierMask:0]; + } + } + } + } +} + +void ll_sdl_macos_make_window_key_deferred(struct SDL_Window* window) +{ + if (!window) + { + return; + } + + NSWindow* ns_window = (__bridge NSWindow*)SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); + if (!ns_window) + { + return; + } + + // After a native dialog closes, SDL only reactivates the app, leaving no + // key window: keystrokes hit no responder and AppKit beeps. Re-key the + // window so it becomes key again and SDL emits FOCUS_GAINED, restoring input. + // Defer to the next main-queue turn so this runs after SDL's own + // ReactivateAfterDialog instead of being clobbered by it. + dispatch_async(dispatch_get_main_queue(), ^{ + [NSApp activateIgnoringOtherApps:YES]; + [ns_window makeKeyAndOrderFront:nil]; + }); +} + +#endif // LL_DARWIN diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 7a67ea8e3d..bc43cd8bc0 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -69,6 +69,7 @@ LLWindowSDL::WAYLAND_DATA LLWindowSDL::sWaylandData = {}; #include #include #include +#include "llsdl_macos.h" bool LLWindowSDL::sUseMultGL = false; #endif @@ -1754,6 +1755,51 @@ SDL_Window* LLWindowSDL::getMainSDLWindow() return gWindowImplementation ? gWindowImplementation->mWindow : nullptr; } +//static +void LLWindowSDL::enterDialog() +{ + if (gWindowImplementation) + { + gWindowImplementation->beforeDialog(); + } +} + +//static +void LLWindowSDL::exitDialog() +{ + LLWindowSDL* self = gWindowImplementation; + if (!self) + { + return; + } + + self->afterDialog(); + + if (self->mDialogDepth != 0 || !self->mWindow) + { + return; + } + + // afterDialog() restores fullscreen and mouselook; the file dialog also needs + // key-window focus back once the last one closes. SDL's dialog sheet leaves no + // key window, so without this keystrokes hit no responder and AppKit beeps. +#if LL_DARWIN + ll_sdl_macos_make_window_key_deferred(self->mWindow); +#else + SDL_RaiseWindow(self->mWindow); + if (self->mCallbacks) + { + self->mCallbacks->handleFocus(self); + } +#endif +} + +//static +bool LLWindowSDL::dialogOpen() +{ + return gWindowImplementation && gWindowImplementation->mDialogDepth > 0; +} + //static std::vector LLWindowSDL::getDisplaysResolutionList() { diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index aec0b58ddf..43a0daf4b5 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -214,6 +214,18 @@ class LLWindowSDL final : public LLWindow // worker thread has its own GL context current. static SDL_Window* getMainSDLWindow(); + // Bracket an async OS dialog. SDL3's file picker resolves on a later frame, + // so beforeDialog()/afterDialog() can't wrap it on a single stack frame; + // enterDialog()/exitDialog() drive the same mDialogDepth machinery from the + // launch site and the completion callback instead. exitDialog() also restores + // key-window focus, which SDL leaves dropped after its dialog sheet closes. + static void enterDialog(); + static void exitDialog(); + + // True while any OS dialog is up. The frame loop services modeless pickers, + // so it must skip BackgroundYieldTime while one is open or it turns laggy. + static bool dialogOpen(); + #if LL_DARWIN static U64 getVramSize(); static void setUseMultGL(bool use_mult_gl); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 5d3182595c..b635e6e4dc 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1558,9 +1558,17 @@ bool LLAppViewer::doFrame() ms_sleep(non_interactive_ms_sleep_time); } + // A native picker steals key focus but is serviced by this loop, so + // don't background-yield while one is open or it turns laggy. + bool native_dialog_open = false; +#if LL_SDL_WINDOW + native_dialog_open = LLWindowSDL::dialogOpen(); +#endif + // yield cooperatively when not running as foreground window // and when not quiting (causes trouble at mac's cleanup stage) if (!LLApp::isExiting() + && !native_dialog_open && ((gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus())) { diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 1e2a96a9bb..43106651f2 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -693,6 +693,7 @@ void LLNetMap::reshape(S32 width, S32 height, bool called_from_parent) { LLUICtrl::reshape(width, height, called_from_parent); createObjectImage(); + createParcelImage(); } LLVector3 LLNetMap::globalPosToView(const LLVector3d& global_pos) diff --git a/indra/newview/llsdlfiledialog.h b/indra/newview/llsdlfiledialog.h index 6f2a2d517d..cc5fbce80d 100644 --- a/indra/newview/llsdlfiledialog.h +++ b/indra/newview/llsdlfiledialog.h @@ -81,6 +81,11 @@ namespace LLSDLFileDialog template inline void trampoline(void* userdata, const char* const* filelist, int /*filter*/) { + // Close the bracket opened in show(), on the main thread: the callback can + // fire on a worker thread (the Linux Zenity backend), and exitDialog() + // touches mDialogDepth and window focus. Runs inline when already on main. + SDL_RunOnMainThread([](void*) { LLWindowSDL::exitDialog(); }, nullptr, false); + auto* ctx = static_cast*>(userdata); auto* cb = ctx->mCallback; auto* user = ctx->mUserdata; @@ -147,6 +152,10 @@ namespace LLSDLFileDialog SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, location.c_str()); } + // Open the dialog bracket; trampoline() closes it. Drops fullscreen and + // mouselook for the dialog's duration, like the blocking pickers do. + LLWindowSDL::enterDialog(); + SDL_ShowFileDialogWithProperties(type, &trampoline, ctx, props); SDL_DestroyProperties(props); diff --git a/indra/newview/skins/default/xui/en/panel_preferences_general.xml b/indra/newview/skins/default/xui/en/panel_preferences_general.xml index 6d1b952639..ab9a0f5acc 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_general.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_general.xml @@ -208,7 +208,7 @@ width="18"/> @@ -295,6 +295,16 @@ name="show_slids" tool_tip="Show username, like bobsmith123" top_delta="0" /> + - + Away timeout: diff --git a/indra/newview/skins/default/xui/en/panel_tools_texture.xml b/indra/newview/skins/default/xui/en/panel_tools_texture.xml index 86e16c78ae..4b80a951e9 100644 --- a/indra/newview/skins/default/xui/en/panel_tools_texture.xml +++ b/indra/newview/skins/default/xui/en/panel_tools_texture.xml @@ -31,7 +31,7 @@