diff --git a/indra/cmake/ViewerInstall.cmake b/indra/cmake/ViewerInstall.cmake index 45a6ba6002c..e40b0a763e9 100644 --- a/indra/cmake/ViewerInstall.cmake +++ b/indra/cmake/ViewerInstall.cmake @@ -693,7 +693,15 @@ if(LINUX) DESTINATION etc COMPONENT viewer ) - install(DIRECTORY "${al_newview_dir}/res-sdl" DESTINATION . COMPONENT viewer) + # The window's cursors, and not the ll_icon.BMP older configures copied in + # beside them: the window's icon is the desktop entry's now, below. + install( + DIRECTORY "${al_newview_dir}/res-sdl" + DESTINATION . + COMPONENT viewer + FILES_MATCHING + PATTERN "*.png" + ) # The desktop entry, its AppStream data and its icons, laid out under share/ # as they go under /usr/share. The entry runs the launcher by the package diff --git a/indra/llui/code/alkeychord.h b/indra/llui/code/alkeychord.h index 38d931bdf23..332e134856d 100644 --- a/indra/llui/code/alkeychord.h +++ b/indra/llui/code/alkeychord.h @@ -33,9 +33,10 @@ class LLView; -// A key as the studio's keymaps name it. SDL's windows -- the Mac's and -// Linux's -- say KEY_DIVIDE for the slash key, which is the keypad's -// slash on Windows, so a key bound as '/' is heard from either. +// A key as the studio's keymaps name it. The keypad's slash is KEY_DIVIDE, +// and was the slash key's too on SDL's windows -- the Mac's and Linux's -- +// before they gave it '/', as Windows does; so a key bound as '/' is heard +// from either, and a binding kept as KEY_DIVIDE reads as '/'. inline KEY alKeyAsBound(KEY key) { return key == KEY_DIVIDE ? KEY('/') : key; diff --git a/indra/llui/tests/alkeymap_test.cpp b/indra/llui/tests/alkeymap_test.cpp index e4324c0726c..792dfa02e73 100644 --- a/indra/llui/tests/alkeymap_test.cpp +++ b/indra/llui/tests/alkeymap_test.cpp @@ -136,7 +136,7 @@ namespace tut template<> template<> void alkeymap_object::test<5>() { - set_test_name("the slash key is heard as the SDL windows name it: KEY_DIVIDE comments as '/' does, and binds as '/'"); + set_test_name("the keypad's slash is heard as the slash key: KEY_DIVIDE comments as '/' does, and binds as '/'"); ALKeymap map = ALKeymap::standard(); ensure("Control with the slash key as '/'", map.lookup('/', MASK_CONTROL) == C::ToggleComment); ensure("and as KEY_DIVIDE", map.lookup(KEY_DIVIDE, MASK_CONTROL) == C::ToggleComment); diff --git a/indra/llui/text/altextview.cpp b/indra/llui/text/altextview.cpp index 37c37394c7f..e774392999f 100644 --- a/indra/llui/text/altextview.cpp +++ b/indra/llui/text/altextview.cpp @@ -4395,12 +4395,20 @@ bool ALTextView::getPreeditLocation(S32 query_offset, LLCoordGL* coord, LLRect* } if (bounds) { - S32 row_begin, row_end; - const F32 x0 = lay().xOf(begin.line, begin.column, &row_begin); - F32 x1 = lay().xOf(begin.line, begin.column + mPreeditLength, &row_end); - if (row_end != row_begin) + // The preedit's span on the query's row, which is the row the top + // is of: from the row's start where the preedit began on a row + // before, to its end where it goes on to a row after. + S32 row_begin, row_end, row_at; + F32 x0 = lay().xOf(begin.line, begin.column, &row_begin); + F32 x1 = lay().xOf(begin.line, begin.column + mPreeditLength, &row_end); + lay().xOf(query.line, query.column, &row_at); + if (row_begin != row_at) { - x1 = lay().line(begin.line).rows[row_begin].width; + x0 = 0.f; + } + if (row_end != row_at) + { + x1 = lay().line(begin.line).rows[row_at].width; } LLRect local(static_cast(left + x0), top, static_cast(left + x1), top - row_h); LLRect screen; @@ -5653,6 +5661,20 @@ void ALTextView::draw() LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, text.getWidth(), nullptr, true); } } + // The input method's window at the caret, kept there as the caret, the + // scroll and the view move, as a text field keeps it from where it draws + // its caret: the caret's x, at the top of its row. Not while the caret + // is out of sight, where the window has nowhere to put it. + if (mLanguageInput && gFocusMgr.getAppHasFocus()) + { + LLCoordGL caret; + LLRect row; + LLWindow* window = getWindow(); + if (window && getPreeditLocation(-1, &caret, &row, nullptr)) + { + window->setLanguageTextInput(LLCoordGL(caret.mX, row.mTop)); + } + } drawBars(alpha); if (mModal) { diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 7d075829512..bd643b4c9a3 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -128,7 +128,11 @@ LLKeyboardSDL::LLKeyboardSDL() mTranslateKeyMap[SDLK_MINUS] = '-'; mTranslateKeyMap[SDLK_PERIOD] = '.'; mTranslateKeyMap[SDLK_GRAVE] = '`'; - mTranslateKeyMap[SDLK_SLASH] = KEY_DIVIDE; + // The main-row slash is '/', as Win32 maps VK_OEM_2: LL moved it off the + // keypad's code there because that broke typing into media prims + // (EXT-3650), and as KEY_DIVIDE it also fired the start_gesture binding + // the keypad's slash has. Only the keypad's slash is KEY_DIVIDE, above. + mTranslateKeyMap[SDLK_SLASH] = '/'; mTranslateKeyMap[SDLK_SEMICOLON] = ';'; mTranslateKeyMap[SDLK_LEFTBRACKET] = '['; mTranslateKeyMap[SDLK_BACKSLASH] = '\\'; diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 4ef7ad10a25..24e450784f2 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -196,25 +196,49 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, } #if LL_LINUX -// The BMP cursor/icon tree (res-sdl/) is only shipped in the Linux bundle. -// macOS loads cursors from cursors_mac/*.tif (makeSDLCursorFromMacTIF) and -// Windows from the exe's embedded .cur resources (makeSDLCursorFromWin32), so -// this helper would be unused on those platforms — and the build is -Werror -// on unused static functions. -static SDL_Surface *Load_BMP_Resource(const char *basename) +// One size of the channel's icon, from the hicolor sizes the tree carries +// under share/icons for its desktop entry, named for the application ID +// llappviewersdl.cpp gives SDL. +static SDL_Surface *loadWindowIcon(S32 size) { - const int PATH_BUFFER_SIZE=1000; - char path_buffer[PATH_BUFFER_SIZE]; /* Flawfinder: ignore */ - - // Figure out where our BMP is living on the disk - snprintf(path_buffer, PATH_BUFFER_SIZE-1, "%s%sres-sdl%s%s", - gDirUtilp->getAppRODataDir().c_str(), - gDirUtilp->getDirDelimiter().c_str(), - gDirUtilp->getDirDelimiter().c_str(), - basename); - path_buffer[PATH_BUFFER_SIZE-1] = '\0'; - - return SDL_LoadBMP(path_buffer); + const char *app_id = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING); + if (!app_id || !*app_id) + { + SDL_SetError("no application ID"); + return nullptr; + } + const std::string dims = std::to_string(size) + "x" + std::to_string(size); + const std::string path = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "share", "icons", + "hicolor", dims, "apps", std::string(app_id) + ".png"); + return SDL_LoadPNG(path.c_str()); +} + +// The window's icon, which X11 desktops show from _NET_WM_ICON and KDE's +// Wayland from xdg_toplevel_icon_v1. SDL sends X11 the surface alone, and +// Wayland its alternates only at whole multiples of its size, so the 256 is +// the icon and the 512 its 2x; a smaller size would never be sent. A tree +// without them, a build that was never staged, keeps the desktop's default. +static void setWindowIcon(SDL_Window *window) +{ + SDL_Surface *icon = loadWindowIcon(256); + if (!icon) + { + LL_INFOS() << "No window icon: " << SDL_GetError() << LL_ENDL; + return; + } + if (SDL_Surface *icon_2x = loadWindowIcon(512)) + { + // The icon takes a reference of its own, released with it. + SDL_AddSurfaceAlternateImage(icon, icon_2x); + SDL_DestroySurface(icon_2x); + } + if (!SDL_SetWindowIcon(window, icon)) + { + // A compositor without xdg_toplevel_icon_v1 shows the desktop + // entry's icon, which it finds by the window's app ID. + LL_INFOS() << "Window icon not set: " << SDL_GetError() << LL_ENDL; + } + SDL_DestroySurface(icon); } #endif // LL_LINUX @@ -505,14 +529,9 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b #endif #if LL_LINUX - // Set the application icon. - SDL_Surface* bmpsurface = Load_BMP_Resource("ll_icon.BMP"); - if (bmpsurface) - { - SDL_SetWindowIcon(mWindow, bmpsurface); - SDL_DestroySurface(bmpsurface); - bmpsurface = nullptr; - } + // The channel's icon. Not on macOS, where SDL would put it on the Dock + // over the bundle's own, nor Windows, where the exe's icon serves. + setWindowIcon(mWindow); #endif // SDL3 ties both committed-text events (SDL_EVENT_TEXT_INPUT) and @@ -2016,6 +2035,67 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) break; } + // Touchpad pinch: Wayland's pointer gestures, XInput 2.4's, Cocoa's + // magnify. A Windows precision touchpad delivers a pinch as + // Control+wheel, so it zooms what Control+wheel zooms (a script's + // text, XUI Studio's canvas, a web page) and does to the camera what + // Control+wheel does: in the default third-person view, that raises + // or lowers the camera rather than zooming. Give it to the viewer the + // same way, as wheel clicks with Control held, at the cursor. SDL + // sends a pinch on its own stream with no wheel events beside it, so + // there is nothing to hold back. + case SDL_EVENT_PINCH_BEGIN: + case SDL_EVENT_PINCH_END: + { + mPinchLastScale = 1.f; + mPinchClickAccum = 0.f; + break; + } + case SDL_EVENT_PINCH_UPDATE: + { + // SDL documents the scale as the change since the last update, and + // Cocoa sends that, but SDL 3.4's Wayland and X11 backends pass on + // what their protocols carry: the scale since the gesture began + // (pointer-gestures-unstable-v1, XIGesturePinchEvent). Take the + // ratio to the last one there. Should SDL come to divide it itself, + // this must stop. + const F32 scale = event.pinch.scale; + if (scale <= 0.f || mPinchLastScale <= 0.f) + { + break; + } + const bool from_start = (mServerProtocol != Unknown); + const F32 ratio = from_start ? scale / mPinchLastScale : scale; + if (from_start) + { + mPinchLastScale = scale; + } + + // A click for every quarter octave of spread, the step the camera's + // orbit takes a click (2^(1/4), LLAgentCamera::handleScrollWheel): + // where Control does not change what the wheel does, fingers spread + // twice as far apart bring the camera in to half its distance. + // Spreading zooms in, as a wheel rolled away from you does, which + // is negative clicks. Fractions gather as the wheel's do, so a slow + // pinch still makes its clicks. + const F32 precise = -4.f * std::log2(ratio); + mPinchClickAccum += precise; + const S32 clicks = lltrunc(mPinchClickAccum); + mPinchClickAccum -= (F32)clicks; + if (clicks != 0 || precise != 0.f) + { + // The wheel's handlers ask the keyboard for Control, and + // LLKeyboardSDL::currentMask asks SDL_GetModState, so hold it + // there for the call, as a Windows pinch arrives with it down. + const SDL_Keymod held = SDL_GetModState(); + const SDL_Keymod added = (held & SDL_KMOD_CTRL) ? SDL_KMOD_NONE : SDL_KMOD_LCTRL; + SDL_SetModState(static_cast(held | added)); + mCallbacks->handleScrollWheel(this, LLScrollDelta(clicks, precise)); + SDL_SetModState(static_cast(SDL_GetModState() & ~added)); + } + break; + } + // Pen / stylus native events. SDL3 also emits mouse-emulation events // for these (with event.motion.which == SDL_PEN_MOUSEID) which our // mouse handlers already route through the usual input plumbing. The @@ -2344,40 +2424,69 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) mPreeditor->resetPreedit(); // An empty composition string means the IME cancelled or cleared - // the preedit; the reset above is the entire job. + // the preedit; the reset above did that, and the IME's area goes + // back to the caret the reset left. if (empty) { + placeLanguageTextInputArea(); break; } const std::string_view preedit(event.edit.text); + const S32 preedit_bytes = static_cast(preedit.length()); - // event.edit.start is the IME's caret inside the composition - // counted in CHARACTERS, or -1 when the IME hasn't reported one. - // SDL's own backends settle the unit: fcitx and the Wayland text - // input both run their byte position through SDL_utf8strnlen, - // which counts codepoints, before dispatching the event. The - // preeditor counts bytes, so the caret has to be walked out. - // start == 0 is a legitimate "caret at the beginning" value and - // must NOT be confused with the -1 default (the earlier `> 0` - // check did exactly that and pinned the caret to the end of the - // preedit whenever the IME started the cursor at the first - // character). - S32 caret = static_cast(preedit.length()); + // event.edit.start and .length mark the clause the IME has + // selected — the one being converted, which it highlights — or, + // with no clause (length 0 or -1), start is the IME's caret, and + // -1 when it reported none. start == 0 is a legitimate "at the + // beginning" value and must NOT be confused with -1. + // + // SDL settles the unit in each backend: ibus, fcitx, XIM and the + // Wayland text input count characters (fcitx and text-input-v3 run + // their byte positions through SDL_utf8strnlen), Cocoa counts + // UTF-32 units, which are the same, and Windows UTF-16 units, the + // same short of the astral planes. None passes a caret on as well + // as a clause — ibus and fcitx drop it, XIM sends the clause in + // its place — so the caret goes to the clause's start, under which + // the candidate window belongs. The preeditor counts bytes, so + // both ends are walked out. + S32 clause_begin = preedit_bytes; + S32 clause_end = preedit_bytes; if (event.edit.start >= 0) { - size_t at = 0; - for (S32 remaining = event.edit.start; - remaining > 0 && at < preedit.size(); - --remaining) + const size_t start = static_cast(event.edit.start); + clause_begin = static_cast(utf8str_offset_from_codepoint_index(preedit, start)); + clause_end = clause_begin; + if (event.edit.length > 0) { - at = utf8str_decode_at(preedit, at).next; + clause_end = static_cast(utf8str_offset_from_codepoint_index( + preedit, start + static_cast(event.edit.length))); } - caret = static_cast(at); } - const LLPreeditor::segment_lengths_t lengths { static_cast(preedit.length()) }; - const LLPreeditor::standouts_t standouts { false }; - mPreeditor->updatePreedit(preedit, lengths, standouts, caret); + + // What comes before the clause, the clause standing out as the + // Win32 backend has a target clause stand out, and what comes + // after; a part with nothing in it is left out. + LLPreeditor::segment_lengths_t lengths; + LLPreeditor::standouts_t standouts; + const auto add_segment = [&](S32 length, bool standout) + { + if (length > 0) + { + lengths.push_back(length); + standouts.push_back(standout); + } + }; + add_segment(clause_begin, false); + add_segment(clause_end - clause_begin, true); + add_segment(preedit_bytes - clause_end, false); + mPreeditor->updatePreedit(preedit, lengths, standouts, clause_begin); + + // The composition moved the caret, and with it where the + // candidate window belongs; the IME is told now rather than at + // the preeditor's next draw, as the Win32 backend does after every + // WM_IME_COMPOSITION. + placeLanguageTextInputArea(); break; } @@ -2482,13 +2591,24 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) break; case SDL_EVENT_WINDOW_FOCUS_LOST: mCallbacks->handleFocusLost(this); - // Drop the IME preeditor reference defensively. The viewer's - // focus manager normally clears keyboard focus on app focus - // loss, which triggers allowLanguageTextInput(nullptr) — but - // if any path leaves mPreeditor pointing at a widget that - // thinks it's defocused, a stray TEXT_EDITING event would - // update a non-active widget. - mPreeditor = nullptr; + // The preeditor keeps language input: keyboard focus stays on + // its widget while the window is away (setAppHasFocus leaves it), + // and nothing allows language input again when the window comes + // back, so forgetting it here dropped every composition after an + // Alt-Tab until the widget was focused anew. As on Win32, only + // what is being composed goes. SDL stops text input as the window + // loses focus, and what the IME then does with the composition + // reaches no window, so the preedit would stay in the text. Win32 + // commits it (interruptLanguageTextInput); SDL can only cancel. + if (mPreeditor) + { + S32 preedit_pos = 0, preedit_len = 0; + mPreeditor->getPreeditRange(&preedit_pos, &preedit_len); + if (preedit_len > 0) + { + mPreeditor->resetPreedit(); + } + } // Sync our relative-mode state to SDL3's. SDL3's keyboard // layer auto-disables relative mode on focus loss; if we don't // mirror that, our member stays true and setCursorPosition @@ -2581,8 +2701,21 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) { // Drag-and-drop session starting. SDL3 doesn't populate event.drop.x/y // or deliver file paths until later (DROP_FILE between BEGIN and - // COMPLETE), so we can't START_TRACKING here — just reset the buffer. + // COMPLETE), so we can't START_TRACKING here — just reset the buffers. mPendingDropFiles.clear(); + mPendingDropText.clear(); + break; + } + case SDL_EVENT_DROP_POSITION: + { + // Where the drag is, but not what it carries: every backend reads + // the payload only once it is dropped (Wayland's data offer, X11's + // XdndSelection, Cocoa's pasteboard), so there is nothing to + // START_TRACKING or TRACK with. The viewer's hover feedback, a face + // lit for a URL or a texture, depends on what is dragged, and an + // empty payload would light faces for a file drag too. SDL stamps + // this last position on the DROP_FILE / DROP_TEXT / DROP_COMPLETE + // that follow, which is where the drop is dispatched. break; } case SDL_EVENT_DROP_FILE: @@ -2597,8 +2730,19 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) } case SDL_EVENT_DROP_TEXT: { - // The current handleDragNDrop pipeline is file-only (DNDT_FILE); SDL3 - // also delivers dropped text but we have nowhere to route it. Drop silently. + // Dropped text, a SLURL or a URL, goes to the viewer as DNDT_DEFAULT + // the way Win32 hands over CF_TEXT (lldragdropwin32.cpp): a spatial + // SLURL teleports, a URL dropped on a face becomes its media. SDL + // sends one DROP_TEXT a line; join them back into the one string + // Win32 passes, and dispatch on DROP_COMPLETE. + if (event.drop.data) + { + if (!mPendingDropText.empty()) + { + mPendingDropText += '\n'; + } + mPendingDropText += event.drop.data; + } break; } case SDL_EVENT_DROP_COMPLETE: @@ -2611,15 +2755,20 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) // DNDA_DROPPED — a DROPPED event with no preceding START_TRACKING is // a no-op (see indra/newview/llviewerwindow.cpp:1455+). // - // SDL3's drop API only surfaces file paths between DROP_BEGIN and - // DROP_COMPLETE, so we can't drive START/TRACK live the way the - // Win32 OLE backend does (DragEnter/DragOver/Drop). Instead we - // synthesise the same three-step sequence here so the receiver's - // state machine sees a well-formed transaction: + // SDL3's drop API only surfaces file paths and text between + // DROP_BEGIN and DROP_COMPLETE (see DROP_POSITION), so we can't + // drive START/TRACK live the way the Win32 OLE backend does + // (DragEnter/DragOver/Drop). Instead we synthesise the same + // three-step sequence here so the receiver's state machine sees a + // well-formed transaction: // // 1) DNDA_START_TRACKING with the file list -> mDragItems populated // 2) DNDA_DROPPED -> upload / apply // 3) DNDA_STOP_TRACKING -> mDragItems cleared + // + // Text runs the same three steps as DNDT_DEFAULT: the handler + // weighs the string on START_TRACKING and teleports or sets the + // media on DROPPED. Files win if a drop carries both. // event.drop.x/y are screen-coord units; scale to PIXEL units. const float scale = mCachedPixelDensity; // cached; see refreshPixelMetrics LLCoordWindow winCoord(llfloor(event.drop.x * scale), @@ -2627,25 +2776,34 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) LLCoordGL openGlCoord; convertCoords(winCoord, &openGlCoord); const MASK mask = gKeyboard->currentMask(true); + LLWindowCallbacks::DragNDropType type = LLWindowCallbacks::DNDT_FILE; + std::vector data; if (!mPendingDropFiles.empty()) + { + data.swap(mPendingDropFiles); + } + else if (!mPendingDropText.empty()) + { + type = LLWindowCallbacks::DNDT_DEFAULT; + data.push_back(std::move(mPendingDropText)); + } + if (!data.empty()) { mCallbacks->handleDragNDrop(this, openGlCoord, mask, LLWindowCallbacks::DNDA_START_TRACKING, - LLWindowCallbacks::DNDT_FILE, - mPendingDropFiles); + type, data); mCallbacks->handleDragNDrop(this, openGlCoord, mask, LLWindowCallbacks::DNDA_DROPPED, - LLWindowCallbacks::DNDT_FILE, - mPendingDropFiles); + type, data); } // Always send STOP_TRACKING so any cached state (mDragItems, - // hover highlight) is cleared, whether or not files were dropped - // (text-only drops and cancelled drags both arrive here too). + // hover highlight) is cleared, whether or not anything was dropped + // (cancelled drags arrive here too). mCallbacks->handleDragNDrop(this, openGlCoord, mask, LLWindowCallbacks::DNDA_STOP_TRACKING, - LLWindowCallbacks::DNDT_FILE, - {}); + type, {}); mPendingDropFiles.clear(); + mPendingDropText.clear(); break; } case SDL_EVENT_WINDOW_CLOSE_REQUESTED: @@ -2679,9 +2837,8 @@ SDL_AppResult LLWindowSDL::handleEvents(const SDL_Event& event) #if LL_DARWIN // On macOS the viewer ships TIFF cursor art in /Contents/Resources/cursors_mac/ -// (see viewer_manifest.py). The legacy res-sdl/*.BMP tree is Linux-only, so on the -// SDL build for Mac we load the native TIFFs via SDL3_image. The TIFFs already carry -// proper alpha, so unlike the BMP path we skip the color-key step. +// (see viewer_manifest.py). The res-sdl/ PNG tree is Linux-only, so on the SDL +// build for Mac we load the native TIFFs, which carry their own alpha, via SDL3_image. static SDL_Cursor *makeSDLCursorFromMacTIF(const char *basename, int hotx, int hoty) { std::string fullpath = gDirUtilp->add( @@ -2730,83 +2887,114 @@ static SDL_Cursor *makeSDLCursorFromMacTIF(const char *basename, int hotx, int h #endif // LL_DARWIN #if LL_LINUX -static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty) +// The Linux cursors are 32x32 PNGs in res-sdl/, with their alpha. Under +// Wayland that is the cursor's size in logical pixels, and SDL hands the +// compositor whichever of the surface's alternate images suits the window's +// scale, so each cursor carries nearest-neighbour copies of its art at the +// integer scales up to this one. (SDL 3.4 picks the smallest copy whose area +// is at least the art's times the scale, not times its square, so a 3x output +// gets the 2x copy and the compositor scales it the rest of the way.) +static const int MAX_CURSOR_SCALE = 3; + +// The integer scale the cursors are drawn at under X11. SDL gives Xcursor the +// surface alone, at its own pixel size, and ignores its alternates; the themed +// cursors beside it are drawn at Xcursor's size (XCURSOR_SIZE, the +// Xcursor.size resource, or one from Xft.dpi), which is 24 at a scale of 1, +// where the 32-pixel art belongs. Without XCURSOR_SIZE the display's content +// scale, which SDL reads from Xft.dpi, stands in for the size: the resource +// can't be read without Xlib. +static int x11CursorScale(SDL_Window *window) { - SDL_Surface *bmpsurface = Load_BMP_Resource(filename); - if (!bmpsurface) + F32 scale = 0.f; + if (const char *size = getenv("XCURSOR_SIZE")) { - LL_WARNS() << "Cursor BMP failed to load: " << filename << LL_ENDL; - return nullptr; + scale = (F32)atoi(size) / 24.f; + } + if (scale <= 0.f && window) + { + scale = SDL_GetWindowDisplayScale(window); } + return llclamp(ll_round(scale), 1, MAX_CURSOR_SCALE); +} - LL_DEBUGS() << "Loaded cursor file " << filename << " " - << bmpsurface->w << "x" << bmpsurface->h << LL_ENDL; +// x11_scale is x11CursorScale's under X11, and 0 elsewhere, where the cursor +// takes its scaled copies as alternates instead. +static SDL_Cursor *makeSDLCursorFromPNG(const char *filename, int hotx, int hoty, int x11_scale) +{ + std::string fullpath = gDirUtilp->add( + gDirUtilp->getAppRODataDir(), + "res-sdl", + filename); - // Normalise to RGBA32 (byte order R,G,B,A in memory regardless of - // host endianness) so we have a predictable layout to color-key - // against, and so SDL_CreateColorCursor gets a surface with alpha. - SDL_Surface *rgba = SDL_ConvertSurface(bmpsurface, SDL_PIXELFORMAT_RGBA32); - SDL_DestroySurface(bmpsurface); - if (!rgba) + SDL_Surface *png = SDL_LoadPNG(fullpath.c_str()); + if (!png) { - LL_WARNS() << "Cursor RGBA conversion failed for " << filename + LL_WARNS() << "Cursor PNG failed to load: " << fullpath << ": " << SDL_GetError() << LL_ENDL; return nullptr; } - // Color-key the legacy (200,200,200) "background" pixels to fully - // transparent. The viewer's cursor BMPs were authored against this - // exact gray as the transparency key (same convention the old SDL2 - // path used), so the assets stay drop-in compatible. Unlike the - // legacy path we keep the rest of the pixel data intact — the old - // code quantised everything to 1-bit black/white, which is why - // multi-colour cursors looked degraded vs Win32. - // - // A converted SDL3 RGBA32 surface shouldn't need locking, but the - // pair is cheap and defends against future SDL changes that flag the - // surface as RLE / GPU-backed. - const bool must_lock = SDL_MUSTLOCK(rgba); - if (must_lock && !SDL_LockSurface(rgba)) + LL_DEBUGS() << "Loaded cursor file " << fullpath << " " + << png->w << "x" << png->h << LL_ENDL; + + // ARGB8888 is what SDL makes every cursor from; converting once here + // spares it converting each scaled copy. + SDL_Surface *art = SDL_ConvertSurface(png, SDL_PIXELFORMAT_ARGB8888); + SDL_DestroySurface(png); + if (!art) { - LL_WARNS() << "SDL_LockSurface failed for cursor " << filename + LL_WARNS() << "Cursor ARGB conversion failed for " << fullpath << ": " << SDL_GetError() << LL_ENDL; - SDL_DestroySurface(rgba); return nullptr; } - for (int y = 0; y < rgba->h; ++y) - { - U8 *row = (U8*)rgba->pixels + (size_t)y * rgba->pitch; - for (int x = 0; x < rgba->w; ++x) - { - U8 *px = row + (size_t)x * 4; - if (px[0] == 200 && px[1] == 200 && px[2] == 200) - { - px[3] = 0; - } - } - } - if (must_lock) - { - SDL_UnlockSurface(rgba); - } // Clamp the hot-spot — out-of-range coordinates are undefined behaviour // on most platforms and at minimum produce a cursor that "clicks" // nowhere near the visible tip. - if (hotx < 0 || hotx >= rgba->w || hoty < 0 || hoty >= rgba->h) + if (hotx < 0 || hotx >= art->w || hoty < 0 || hoty >= art->h) { - LL_WARNS() << "Cursor " << filename << " hot-spot (" + LL_WARNS() << "Cursor " << fullpath << " hot-spot (" << hotx << "," << hoty << ") is outside " - << rgba->w << "x" << rgba->h << "; clamping." << LL_ENDL; - hotx = llclamp(hotx, 0, rgba->w - 1); - hoty = llclamp(hoty, 0, rgba->h - 1); + << art->w << "x" << art->h << "; clamping." << LL_ENDL; + hotx = llclamp(hotx, 0, art->w - 1); + hoty = llclamp(hoty, 0, art->h - 1); } - SDL_Cursor *sdlcursor = SDL_CreateColorCursor(rgba, hotx, hoty); - SDL_DestroySurface(rgba); + // Nearest-neighbour, so the art's pixels stay hard-edged. A copy that + // can't be made leaves the cursor at the scales it has. + if (x11_scale > 1) + { + SDL_Surface *scaled = SDL_ScaleSurface(art, art->w * x11_scale, art->h * x11_scale, + SDL_SCALEMODE_NEAREST); + if (scaled) + { + SDL_DestroySurface(art); + art = scaled; + hotx *= x11_scale; + hoty *= x11_scale; + } + } + else if (x11_scale == 0) + { + for (int scale = 2; scale <= MAX_CURSOR_SCALE; ++scale) + { + SDL_Surface *alternate = SDL_ScaleSurface(art, art->w * scale, art->h * scale, + SDL_SCALEMODE_NEAREST); + if (!alternate) + { + break; + } + // The surface takes a reference of its own, released with it. + SDL_AddSurfaceAlternateImage(art, alternate); + SDL_DestroySurface(alternate); + } + } + + SDL_Cursor *sdlcursor = SDL_CreateColorCursor(art, hotx, hoty); + SDL_DestroySurface(art); if (!sdlcursor) { - LL_WARNS() << "SDL_CreateColorCursor failed for " << filename + LL_WARNS() << "SDL_CreateColorCursor failed for " << fullpath << ": " << SDL_GetError() << LL_ENDL; } return sdlcursor; @@ -2817,7 +3005,7 @@ static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty // Convert one of the viewer's embedded Win32 cursor resources (the same // branded .cur/.ani assets the native backend loads in // LLWindowWin32::initCursors) into an SDL color cursor. The hot-spot is taken -// from the resource itself via GetIconInfo, so — unlike the BMP path — there +// from the resource itself via GetIconInfo, so — unlike the PNG path — there // is no hand-maintained hot-spot table to keep in sync. Returns nullptr if the // resource is missing or can't be converted, letting initCursors fall back to // the SDL system arrow. @@ -2844,7 +3032,7 @@ static bool win32ReadDIB32(HBITMAP hbm, int width, int rows, std::vector& ou // Convert one of the viewer's embedded Win32 cursor resources (the same // branded .cur/.ani assets the native backend loads in // LLWindowWin32::initCursors) into an SDL color cursor. The hot-spot is taken -// from the resource itself via GetIconInfo, so — unlike the BMP path — there +// from the resource itself via GetIconInfo, so — unlike the PNG path — there // is no hand-maintained hot-spot table to keep in sync. Returns nullptr if the // resource is missing or can't be converted, letting initCursors fall back to // the SDL system arrow. @@ -3032,7 +3220,6 @@ void LLWindowSDL::initCursors() // Pre-make an SDL cursor for each of the known cursor types. // We hardcode the hotspots - to avoid that we'd have to write // a .cur file loader. - // NOTE: SDL doesn't load RLE-compressed BMP files. mSDLCursors[UI_CURSOR_ARROW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); mSDLCursors[UI_CURSOR_WAIT] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); mSDLCursors[UI_CURSOR_HAND] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER); @@ -3046,7 +3233,7 @@ void LLWindowSDL::initCursors() mSDLCursors[UI_CURSOR_NO] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED); mSDLCursors[UI_CURSOR_WORKING] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_PROGRESS); #if LL_DARWIN - // The macOS bundle ships TIFF cursors under cursors_mac/ (not the res-sdl BMP + // The macOS bundle ships TIFF cursors under cursors_mac/ (not the res-sdl PNG // tree), and uses Mac-specific hot-spots — keep these in sync with // LLWindowMacOSX::initCursors() so the SDL build matches the native client. // ARROWCOPY/ARROWCOPYMULTI/ARROWDRAGMULTI have no TIF counterpart (the native @@ -3118,38 +3305,40 @@ void LLWindowSDL::initCursors() mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD] = makeSDLCursorFromWin32("TOOLPATHFINDINGPATHENDADD"); mSDLCursors[UI_CURSOR_TOOLNO] = makeSDLCursorFromWin32("TOOLNO"); #else - mSDLCursors[UI_CURSOR_TOOLGRAB] = makeSDLCursorFromBMP("lltoolgrab.BMP",2,13); - mSDLCursors[UI_CURSOR_TOOLLAND] = makeSDLCursorFromBMP("lltoolland.BMP",1,6); - mSDLCursors[UI_CURSOR_TOOLFOCUS] = makeSDLCursorFromBMP("lltoolfocus.BMP",8,5); - mSDLCursors[UI_CURSOR_TOOLCREATE] = makeSDLCursorFromBMP("lltoolcreate.BMP",7,7); - mSDLCursors[UI_CURSOR_ARROWDRAG] = makeSDLCursorFromBMP("arrowdrag.BMP",0,0); - mSDLCursors[UI_CURSOR_ARROWCOPY] = makeSDLCursorFromBMP("arrowcop.BMP",0,0); - mSDLCursors[UI_CURSOR_ARROWDRAGMULTI] = makeSDLCursorFromBMP("llarrowdragmulti.BMP",0,0); - mSDLCursors[UI_CURSOR_ARROWCOPYMULTI] = makeSDLCursorFromBMP("arrowcopmulti.BMP",0,0); - mSDLCursors[UI_CURSOR_NOLOCKED] = makeSDLCursorFromBMP("llnolocked.BMP",8,8); - mSDLCursors[UI_CURSOR_ARROWLOCKED] = makeSDLCursorFromBMP("llarrowlocked.BMP",0,0); - mSDLCursors[UI_CURSOR_GRABLOCKED] = makeSDLCursorFromBMP("llgrablocked.BMP",2,13); - mSDLCursors[UI_CURSOR_TOOLTRANSLATE] = makeSDLCursorFromBMP("lltooltranslate.BMP",0,0); - mSDLCursors[UI_CURSOR_TOOLROTATE] = makeSDLCursorFromBMP("lltoolrotate.BMP",0,0); - mSDLCursors[UI_CURSOR_TOOLSCALE] = makeSDLCursorFromBMP("lltoolscale.BMP",0,0); - mSDLCursors[UI_CURSOR_TOOLCAMERA] = makeSDLCursorFromBMP("lltoolcamera.BMP",7,5); - mSDLCursors[UI_CURSOR_TOOLPAN] = makeSDLCursorFromBMP("lltoolpan.BMP",7,5); - mSDLCursors[UI_CURSOR_TOOLZOOMIN] = makeSDLCursorFromBMP("lltoolzoomin.BMP",7,5); - mSDLCursors[UI_CURSOR_TOOLZOOMOUT] = makeSDLCursorFromBMP("lltoolzoomout.BMP", 7, 5); - mSDLCursors[UI_CURSOR_TOOLPICKOBJECT3] = makeSDLCursorFromBMP("toolpickobject3.BMP",0,0); - mSDLCursors[UI_CURSOR_TOOLPLAY] = makeSDLCursorFromBMP("toolplay.BMP",0,0); - mSDLCursors[UI_CURSOR_TOOLPAUSE] = makeSDLCursorFromBMP("toolpause.BMP",0,0); - mSDLCursors[UI_CURSOR_TOOLMEDIAOPEN] = makeSDLCursorFromBMP("toolmediaopen.BMP",0,0); - mSDLCursors[UI_CURSOR_PIPETTE] = makeSDLCursorFromBMP("lltoolpipette.BMP",2,28); - mSDLCursors[UI_CURSOR_TOOLSIT] = makeSDLCursorFromBMP("toolsit.BMP",20,15); - mSDLCursors[UI_CURSOR_TOOLBUY] = makeSDLCursorFromBMP("toolbuy.BMP",20,15); - mSDLCursors[UI_CURSOR_TOOLOPEN] = makeSDLCursorFromBMP("toolopen.BMP",20,15); - mSDLCursors[UI_CURSOR_TOOLPATHFINDING] = makeSDLCursorFromBMP("lltoolpathfinding.BMP", 16, 16); - mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_START] = makeSDLCursorFromBMP("lltoolpathfindingpathstart.BMP", 16, 16); - mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD] = makeSDLCursorFromBMP("lltoolpathfindingpathstartadd.BMP", 16, 16); - mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_END] = makeSDLCursorFromBMP("lltoolpathfindingpathend.BMP", 16, 16); - mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD] = makeSDLCursorFromBMP("lltoolpathfindingpathendadd.BMP", 16, 16); - mSDLCursors[UI_CURSOR_TOOLNO] = makeSDLCursorFromBMP("llno.BMP",8,8); + // Hot-spots are in the 32x32 art's pixels, and scale with it under X11. + const int x11_scale = mServerProtocol == X11 ? x11CursorScale(mWindow) : 0; + mSDLCursors[UI_CURSOR_TOOLGRAB] = makeSDLCursorFromPNG("lltoolgrab.png",2,13,x11_scale); + mSDLCursors[UI_CURSOR_TOOLLAND] = makeSDLCursorFromPNG("lltoolland.png",1,6,x11_scale); + mSDLCursors[UI_CURSOR_TOOLFOCUS] = makeSDLCursorFromPNG("lltoolfocus.png",8,5,x11_scale); + mSDLCursors[UI_CURSOR_TOOLCREATE] = makeSDLCursorFromPNG("lltoolcreate.png",7,7,x11_scale); + mSDLCursors[UI_CURSOR_ARROWDRAG] = makeSDLCursorFromPNG("arrowdrag.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_ARROWCOPY] = makeSDLCursorFromPNG("arrowcop.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_ARROWDRAGMULTI] = makeSDLCursorFromPNG("llarrowdragmulti.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_ARROWCOPYMULTI] = makeSDLCursorFromPNG("arrowcopmulti.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_NOLOCKED] = makeSDLCursorFromPNG("llnolocked.png",8,8,x11_scale); + mSDLCursors[UI_CURSOR_ARROWLOCKED] = makeSDLCursorFromPNG("llarrowlocked.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_GRABLOCKED] = makeSDLCursorFromPNG("llgrablocked.png",2,13,x11_scale); + mSDLCursors[UI_CURSOR_TOOLTRANSLATE] = makeSDLCursorFromPNG("lltooltranslate.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_TOOLROTATE] = makeSDLCursorFromPNG("lltoolrotate.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_TOOLSCALE] = makeSDLCursorFromPNG("lltoolscale.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_TOOLCAMERA] = makeSDLCursorFromPNG("lltoolcamera.png",7,5,x11_scale); + mSDLCursors[UI_CURSOR_TOOLPAN] = makeSDLCursorFromPNG("lltoolpan.png",7,5,x11_scale); + mSDLCursors[UI_CURSOR_TOOLZOOMIN] = makeSDLCursorFromPNG("lltoolzoomin.png",7,5,x11_scale); + mSDLCursors[UI_CURSOR_TOOLZOOMOUT] = makeSDLCursorFromPNG("lltoolzoomout.png", 7, 5, x11_scale); + mSDLCursors[UI_CURSOR_TOOLPICKOBJECT3] = makeSDLCursorFromPNG("toolpickobject3.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_TOOLPLAY] = makeSDLCursorFromPNG("toolplay.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_TOOLPAUSE] = makeSDLCursorFromPNG("toolpause.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_TOOLMEDIAOPEN] = makeSDLCursorFromPNG("toolmediaopen.png",0,0,x11_scale); + mSDLCursors[UI_CURSOR_PIPETTE] = makeSDLCursorFromPNG("lltoolpipette.png",2,28,x11_scale); + mSDLCursors[UI_CURSOR_TOOLSIT] = makeSDLCursorFromPNG("toolsit.png",20,15,x11_scale); + mSDLCursors[UI_CURSOR_TOOLBUY] = makeSDLCursorFromPNG("toolbuy.png",20,15,x11_scale); + mSDLCursors[UI_CURSOR_TOOLOPEN] = makeSDLCursorFromPNG("toolopen.png",20,15,x11_scale); + mSDLCursors[UI_CURSOR_TOOLPATHFINDING] = makeSDLCursorFromPNG("lltoolpathfinding.png", 16, 16, x11_scale); + mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_START] = makeSDLCursorFromPNG("lltoolpathfindingpathstart.png", 16, 16, x11_scale); + mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD] = makeSDLCursorFromPNG("lltoolpathfindingpathstartadd.png", 16, 16, x11_scale); + mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_END] = makeSDLCursorFromPNG("lltoolpathfindingpathend.png", 16, 16, x11_scale); + mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD] = makeSDLCursorFromPNG("lltoolpathfindingpathendadd.png", 16, 16, x11_scale); + mSDLCursors[UI_CURSOR_TOOLNO] = makeSDLCursorFromPNG("llno.png",8,8,x11_scale); #endif // LL_DARWIN } @@ -4083,68 +4272,94 @@ LLFontFallbackMatch LLWindowSDL::findFallbackFontForChar(llwchar wch) return result; } -void LLWindowSDL::setLanguageTextInput(const LLCoordGL& position) +bool LLWindowSDL::placeLanguageTextInputArea() { - if (!mWindow) + if (!mWindow || !mPreeditor) { - return; + return false; + } + + // The preeditor's own caret and bounds — the line of text the caret is + // on, across the preedit, or at the caret itself when nothing is being + // composed — so the platform IME anchors its candidate window to the + // real text rather than a box guessed at the caret. Mirrors how the + // Win32 backend builds CANDIDATEFORM from the same query, in its own + // updateLanguageTextInputArea. + LLCoordGL caret_gl; + LLRect bounds_gl; + if (!mPreeditor->getPreeditLocation(-1, &caret_gl, &bounds_gl, nullptr)) + { + return false; } + // LLRect uses GL convention (Y up, mTop > mBottom). convertCoords + // flips Y so the GL-top-left maps to the window-top-left (the + // corner with the smaller window-Y), and likewise GL-bottom-right + // maps to window-bottom-right. + LLCoordWindow top_left; + LLCoordWindow bottom_right; + LLCoordWindow caret_win; + convertCoords(LLCoordGL(bounds_gl.mLeft, bounds_gl.mTop), &top_left); + convertCoords(LLCoordGL(bounds_gl.mRight, bounds_gl.mBottom), &bottom_right); + convertCoords(caret_gl, &caret_win); + // SDL_SetTextInputArea wants the rect and cursor offset in screen-coord // (logical) units, not pixels — that's the unit the IME backends - // (ibus / fcitx / IMM via SDL) speak. LLCoordWindow on this backend is - // in PIXELS (see the coord-space contract above), so we divide by - // pixel density at the boundary. + // (ibus / fcitx / text-input-v3 / XIM / Cocoa) speak. LLCoordWindow on + // this backend is in PIXELS (see the coord-space contract above), so we + // divide by pixel density at the boundary. const float density = SDL_GetWindowPixelDensity(mWindow); const float div = density > 0.f ? density : 1.f; - // If the active preeditor can give us its actual bounds + caret, feed - // those to SDL so the platform IME anchors its candidate-list popup to - // the real text input area (rather than a magic 500x16 box guessed at - // the caret). Mirrors how the Win32 backend builds CANDIDATEFORM from - // the same LLPreeditor::getPreeditLocation query — see - // llwindowwin32.cpp:4164. - if (mPreeditor) + SDL_Rect rect; + rect.x = llfloor(top_left.mX / div); + rect.y = llfloor(top_left.mY / div); + rect.w = llfloor((bottom_right.mX - top_left.mX) / div); + rect.h = llfloor((bottom_right.mY - top_left.mY) / div); + + // A widget that's still being laid out can return a default- + // constructed LLRect (all zeros), which would anchor the IME popup to + // the window origin. No width is fine, though: it is what every + // preeditor gives for a bare caret, before anything is composed, which + // is just when the IME first needs to know. + if (rect.h <= 0 || rect.w < 0) { - LLCoordGL caret_gl; - LLRect bounds_gl; - if (mPreeditor->getPreeditLocation(-1, &caret_gl, &bounds_gl, nullptr)) - { - // LLRect uses GL convention (Y up, mTop > mBottom). convertCoords - // flips Y so the GL-top-left maps to the window-top-left (the - // corner with the smaller window-Y), and likewise GL-bottom-right - // maps to window-bottom-right. - LLCoordWindow top_left; - LLCoordWindow bottom_right; - convertCoords(LLCoordGL(bounds_gl.mLeft, bounds_gl.mTop), &top_left); - convertCoords(LLCoordGL(bounds_gl.mRight, bounds_gl.mBottom), &bottom_right); - - LLCoordWindow caret_win; - convertCoords(caret_gl, &caret_win); - - SDL_Rect rect; - rect.x = llfloor(top_left.mX / div); - rect.y = llfloor(top_left.mY / div); - rect.w = llfloor((bottom_right.mX - top_left.mX) / div); - rect.h = llfloor((bottom_right.mY - top_left.mY) / div); - - // A widget that's still being laid out can return a default- - // constructed LLRect (all zeros); SDL_SetTextInputArea with a - // zero-sized rect would anchor the IME popup to the window - // origin. Fall through to the caret-based fallback instead. - if (rect.w > 0 && rect.h > 0) - { - const int cursor_x = llfloor((caret_win.mX - top_left.mX) / div); - SDL_SetTextInputArea(mWindow, &rect, cursor_x); - return; - } - } + return false; + } + + // The cursor is the caret's offset from rect.x. ibus, fcitx and XIM put + // the candidate window at rect.x + cursor, under the rect; text-input-v3 + // clamps that to the rect's right edge; Cocoa takes the rect alone. + const int cursor_x = llfloor((caret_win.mX - top_left.mX) / div); + SDL_SetTextInputArea(mWindow, &rect, cursor_x); + return true; +} + +void LLWindowSDL::updateLanguageTextInputArea() +{ + placeLanguageTextInputArea(); +} + +void LLWindowSDL::setLanguageTextInput(const LLCoordGL& position) +{ + if (!mWindow) + { + return; + } + + // The active preeditor's caret and bounds where it can give them. + if (placeLanguageTextInputArea()) + { + return; } // Fallback: no preeditor (or it couldn't compute its bounds). Use a // single-line guess centred at the supplied caret position, in line with // the pre-improvement behaviour — enough for the IME to place its popup // roughly under the cursor. + const float density = SDL_GetWindowPixelDensity(mWindow); + const float div = density > 0.f ? density : 1.f; + LLCoordWindow caret_win; convertCoords(position, &caret_win); @@ -4177,6 +4392,10 @@ void LLWindowSDL::allowLanguageTextInput(LLPreeditor* preeditor, bool b) if (b) { mPreeditor = preeditor; + // Tell the IME where the new owner's caret is now rather than at its + // next draw: the first key composed can open the candidate window + // before then. + placeLanguageTextInputArea(); return; } diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 2b9f9e8a7e5..fb2f62a9e8f 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -195,6 +195,7 @@ class LLWindowSDL final : public LLWindow void bringToFront(const std::string& activation_token); void setLanguageTextInput(const LLCoordGL& pos) override; + void updateLanguageTextInputArea() override; void allowLanguageTextInput(LLPreeditor* preeditor, bool b) override; void spawnWebBrowser(const std::string &escaped_url, bool async) override; @@ -385,6 +386,13 @@ class LLWindowSDL final : public LLWindow F32 mScrollWheelAccumX = 0.f; F32 mScrollWheelAccumY = 0.f; + // A touchpad pinch, given to the viewer as Control+wheel clicks (see + // SDL_EVENT_PINCH_UPDATE): the scale the gesture last reported, where a + // backend reports it from the gesture's start, and the clicks it has not + // yet made whole. Both reset with each gesture. + F32 mPinchLastScale = 1.f; + F32 mPinchClickAccum = 0.f; + // Mirrors LLWindowWin32::mAbsoluteCursorPosition. Set true while the // most recent mouse motion/button event was synthesised from a touch // screen (event.motion.which == SDL_TOUCH_MOUSEID) or a pen / stylus @@ -445,9 +453,11 @@ class LLWindowSDL final : public LLWindow LLMutex mSharedCtxMutex; std::set mSharedContexts; - // Files accumulated between SDL_EVENT_DROP_BEGIN and SDL_EVENT_DROP_COMPLETE. - // Each SDL_EVENT_DROP_FILE only carries one path, so we batch them and - // dispatch a single handleDragNDrop on COMPLETE. + // Files and text accumulated between SDL_EVENT_DROP_BEGIN and + // SDL_EVENT_DROP_COMPLETE. Each SDL_EVENT_DROP_FILE only carries one path + // and each SDL_EVENT_DROP_TEXT one line, so we batch them and dispatch a + // single handleDragNDrop on COMPLETE. + std::string mPendingDropText; std::vector mPendingDropFiles; // Currently-focused preeditor receiving SDL_EVENT_TEXT_EDITING composition @@ -465,6 +475,12 @@ class LLWindowSDL final : public LLWindow // just-the-commits from SDL3. LLPreeditor* mPreeditor = nullptr; + // Hand the platform IME the text input area of mPreeditor, the caret + // and the preedit around it, as the preeditor places them, so the IME's + // candidate window opens under the caret. False when there is no + // preeditor or it cannot place its caret (scrolled out of sight). + bool placeLanguageTextInputArea(); + enum EServerProtocol{ X11, Wayland, Unknown }; EServerProtocol mServerProtocol = Unknown; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 6b56ef29f8f..b6c55226022 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -108,28 +108,14 @@ if(AL_BUILD_VIEWER) find_package(unofficial-alchemy-branding CONFIG REQUIRED) set(BRANDING_SOURCE_DIR "${ALCHEMY_BRANDING_DIR}") + # Linux's window takes the hicolor PNGs installed for the desktop entry + # (ViewerInstall.cmake), and macOS's the bundle's alchemy.icns. if(WINDOWS) file( COPY_FILE "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy.ico" "${CMAKE_CURRENT_SOURCE_DIR}/res/ll_icon.ico" ONLY_IF_DIFFERENT ) - file( - COPY_FILE "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy_256.BMP" - "${CMAKE_CURRENT_SOURCE_DIR}/res/ll_icon.BMP" - ONLY_IF_DIFFERENT - ) - file( - COPY_FILE "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy_256.BMP" - "${CMAKE_CURRENT_SOURCE_DIR}/res-sdl/ll_icon.BMP" - ONLY_IF_DIFFERENT - ) - elseif(LINUX) - file( - COPY_FILE "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy_256.BMP" - "${CMAKE_CURRENT_SOURCE_DIR}/res-sdl/ll_icon.BMP" - ONLY_IF_DIFFERENT - ) endif() # Branded splash icon for the SDL splash screen (LLSplashScreenSDL loads this @@ -1999,8 +1985,6 @@ if(AL_BUILD_VIEWER) res/lltooltranslate.cur res/lltoolzoomin.cur res/lltoolzoomout.cur - res-sdl/ll_icon.BMP - res/ll_icon.BMP res/ll_icon.ico res/resource.h res/toolpickobject.cur diff --git a/indra/newview/res-sdl/arrow.BMP b/indra/newview/res-sdl/arrow.BMP deleted file mode 100644 index a8f6da64b57..00000000000 Binary files a/indra/newview/res-sdl/arrow.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/arrowcop.BMP b/indra/newview/res-sdl/arrowcop.BMP deleted file mode 100644 index 1a26a0df341..00000000000 Binary files a/indra/newview/res-sdl/arrowcop.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/arrowcop.png b/indra/newview/res-sdl/arrowcop.png new file mode 100644 index 00000000000..f9034179773 Binary files /dev/null and b/indra/newview/res-sdl/arrowcop.png differ diff --git a/indra/newview/res-sdl/arrowcopmulti.BMP b/indra/newview/res-sdl/arrowcopmulti.BMP deleted file mode 100644 index 48f153cef66..00000000000 Binary files a/indra/newview/res-sdl/arrowcopmulti.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/arrowcopmulti.png b/indra/newview/res-sdl/arrowcopmulti.png new file mode 100644 index 00000000000..9aa31331ce7 Binary files /dev/null and b/indra/newview/res-sdl/arrowcopmulti.png differ diff --git a/indra/newview/res-sdl/arrowdrag.BMP b/indra/newview/res-sdl/arrowdrag.BMP deleted file mode 100644 index cd868eec200..00000000000 Binary files a/indra/newview/res-sdl/arrowdrag.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/arrowdrag.png b/indra/newview/res-sdl/arrowdrag.png new file mode 100644 index 00000000000..b5d47b0816e Binary files /dev/null and b/indra/newview/res-sdl/arrowdrag.png differ diff --git a/indra/newview/res-sdl/circleandline.BMP b/indra/newview/res-sdl/circleandline.BMP deleted file mode 100644 index 284ae8b7d51..00000000000 Binary files a/indra/newview/res-sdl/circleandline.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/cross.BMP b/indra/newview/res-sdl/cross.BMP deleted file mode 100644 index 0b4672d4d61..00000000000 Binary files a/indra/newview/res-sdl/cross.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/hand.BMP b/indra/newview/res-sdl/hand.BMP deleted file mode 100644 index 2a092fbb7fe..00000000000 Binary files a/indra/newview/res-sdl/hand.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/ibeam.BMP b/indra/newview/res-sdl/ibeam.BMP deleted file mode 100644 index 820904a228d..00000000000 Binary files a/indra/newview/res-sdl/ibeam.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llarrow.BMP b/indra/newview/res-sdl/llarrow.BMP deleted file mode 100644 index a8f6da64b57..00000000000 Binary files a/indra/newview/res-sdl/llarrow.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llarrowdrag.BMP b/indra/newview/res-sdl/llarrowdrag.BMP deleted file mode 100644 index cd868eec200..00000000000 Binary files a/indra/newview/res-sdl/llarrowdrag.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llarrowdragmulti.BMP b/indra/newview/res-sdl/llarrowdragmulti.BMP deleted file mode 100644 index fb528bc92d2..00000000000 Binary files a/indra/newview/res-sdl/llarrowdragmulti.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llarrowdragmulti.png b/indra/newview/res-sdl/llarrowdragmulti.png new file mode 100644 index 00000000000..6bd32fbb0de Binary files /dev/null and b/indra/newview/res-sdl/llarrowdragmulti.png differ diff --git a/indra/newview/res-sdl/llarrowlocked.BMP b/indra/newview/res-sdl/llarrowlocked.BMP deleted file mode 100644 index 0aaa441ab1f..00000000000 Binary files a/indra/newview/res-sdl/llarrowlocked.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llarrowlocked.png b/indra/newview/res-sdl/llarrowlocked.png new file mode 100644 index 00000000000..4ad4250b9da Binary files /dev/null and b/indra/newview/res-sdl/llarrowlocked.png differ diff --git a/indra/newview/res-sdl/llgrablocked.BMP b/indra/newview/res-sdl/llgrablocked.BMP deleted file mode 100644 index 847439670f9..00000000000 Binary files a/indra/newview/res-sdl/llgrablocked.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llgrablocked.png b/indra/newview/res-sdl/llgrablocked.png new file mode 100644 index 00000000000..76269c3ae25 Binary files /dev/null and b/indra/newview/res-sdl/llgrablocked.png differ diff --git a/indra/newview/res-sdl/llno.BMP b/indra/newview/res-sdl/llno.BMP deleted file mode 100644 index 284ae8b7d51..00000000000 Binary files a/indra/newview/res-sdl/llno.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llno.png b/indra/newview/res-sdl/llno.png new file mode 100644 index 00000000000..a0068fdbbf5 Binary files /dev/null and b/indra/newview/res-sdl/llno.png differ diff --git a/indra/newview/res-sdl/llnolocked.BMP b/indra/newview/res-sdl/llnolocked.BMP deleted file mode 100644 index 61f0170cb34..00000000000 Binary files a/indra/newview/res-sdl/llnolocked.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/llnolocked.png b/indra/newview/res-sdl/llnolocked.png new file mode 100644 index 00000000000..6e7bd9633d9 Binary files /dev/null and b/indra/newview/res-sdl/llnolocked.png differ diff --git a/indra/newview/res-sdl/lltoolcamera.BMP b/indra/newview/res-sdl/lltoolcamera.BMP deleted file mode 100644 index c961d7a49c7..00000000000 Binary files a/indra/newview/res-sdl/lltoolcamera.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolcamera.png b/indra/newview/res-sdl/lltoolcamera.png new file mode 100644 index 00000000000..13ae7f3c0aa Binary files /dev/null and b/indra/newview/res-sdl/lltoolcamera.png differ diff --git a/indra/newview/res-sdl/lltoolcreate.BMP b/indra/newview/res-sdl/lltoolcreate.BMP deleted file mode 100644 index 08a4a9322d9..00000000000 Binary files a/indra/newview/res-sdl/lltoolcreate.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolcreate.png b/indra/newview/res-sdl/lltoolcreate.png new file mode 100644 index 00000000000..d90a4139130 Binary files /dev/null and b/indra/newview/res-sdl/lltoolcreate.png differ diff --git a/indra/newview/res-sdl/lltoolfocus.BMP b/indra/newview/res-sdl/lltoolfocus.BMP deleted file mode 100644 index afb90a95e31..00000000000 Binary files a/indra/newview/res-sdl/lltoolfocus.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolfocus.png b/indra/newview/res-sdl/lltoolfocus.png new file mode 100644 index 00000000000..84923a2e218 Binary files /dev/null and b/indra/newview/res-sdl/lltoolfocus.png differ diff --git a/indra/newview/res-sdl/lltoolgrab.BMP b/indra/newview/res-sdl/lltoolgrab.BMP deleted file mode 100644 index f2ac68bf3cc..00000000000 Binary files a/indra/newview/res-sdl/lltoolgrab.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolgrab.png b/indra/newview/res-sdl/lltoolgrab.png new file mode 100644 index 00000000000..2f429bc2a97 Binary files /dev/null and b/indra/newview/res-sdl/lltoolgrab.png differ diff --git a/indra/newview/res-sdl/lltoolland.BMP b/indra/newview/res-sdl/lltoolland.BMP deleted file mode 100644 index 64e63656250..00000000000 Binary files a/indra/newview/res-sdl/lltoolland.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolland.png b/indra/newview/res-sdl/lltoolland.png new file mode 100644 index 00000000000..9ffa2fa3b27 Binary files /dev/null and b/indra/newview/res-sdl/lltoolland.png differ diff --git a/indra/newview/res-sdl/lltoolpan.BMP b/indra/newview/res-sdl/lltoolpan.BMP deleted file mode 100644 index ffbef21ec74..00000000000 Binary files a/indra/newview/res-sdl/lltoolpan.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolpan.png b/indra/newview/res-sdl/lltoolpan.png new file mode 100644 index 00000000000..01d3c0872ce Binary files /dev/null and b/indra/newview/res-sdl/lltoolpan.png differ diff --git a/indra/newview/res-sdl/lltoolpathfinding.BMP b/indra/newview/res-sdl/lltoolpathfinding.BMP deleted file mode 100644 index a567951b7a4..00000000000 Binary files a/indra/newview/res-sdl/lltoolpathfinding.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolpathfinding.png b/indra/newview/res-sdl/lltoolpathfinding.png new file mode 100644 index 00000000000..3c0d29570bc Binary files /dev/null and b/indra/newview/res-sdl/lltoolpathfinding.png differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathend.BMP b/indra/newview/res-sdl/lltoolpathfindingpathend.BMP deleted file mode 100644 index aacea8237ff..00000000000 Binary files a/indra/newview/res-sdl/lltoolpathfindingpathend.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathend.png b/indra/newview/res-sdl/lltoolpathfindingpathend.png new file mode 100644 index 00000000000..69dce589cfc Binary files /dev/null and b/indra/newview/res-sdl/lltoolpathfindingpathend.png differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathendadd.BMP b/indra/newview/res-sdl/lltoolpathfindingpathendadd.BMP deleted file mode 100644 index fa19f3f1058..00000000000 Binary files a/indra/newview/res-sdl/lltoolpathfindingpathendadd.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathendadd.png b/indra/newview/res-sdl/lltoolpathfindingpathendadd.png new file mode 100644 index 00000000000..a96b693f7fb Binary files /dev/null and b/indra/newview/res-sdl/lltoolpathfindingpathendadd.png differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathstart.BMP b/indra/newview/res-sdl/lltoolpathfindingpathstart.BMP deleted file mode 100644 index 912b7f931a7..00000000000 Binary files a/indra/newview/res-sdl/lltoolpathfindingpathstart.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathstart.png b/indra/newview/res-sdl/lltoolpathfindingpathstart.png new file mode 100644 index 00000000000..37f24f1c408 Binary files /dev/null and b/indra/newview/res-sdl/lltoolpathfindingpathstart.png differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathstartadd.BMP b/indra/newview/res-sdl/lltoolpathfindingpathstartadd.BMP deleted file mode 100644 index 4e8999ae0bf..00000000000 Binary files a/indra/newview/res-sdl/lltoolpathfindingpathstartadd.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolpathfindingpathstartadd.png b/indra/newview/res-sdl/lltoolpathfindingpathstartadd.png new file mode 100644 index 00000000000..ca8eceec1d9 Binary files /dev/null and b/indra/newview/res-sdl/lltoolpathfindingpathstartadd.png differ diff --git a/indra/newview/res-sdl/lltoolpipette.BMP b/indra/newview/res-sdl/lltoolpipette.BMP deleted file mode 100644 index 2d27118289e..00000000000 Binary files a/indra/newview/res-sdl/lltoolpipette.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolpipette.png b/indra/newview/res-sdl/lltoolpipette.png new file mode 100644 index 00000000000..c863d28683d Binary files /dev/null and b/indra/newview/res-sdl/lltoolpipette.png differ diff --git a/indra/newview/res-sdl/lltoolrotate.BMP b/indra/newview/res-sdl/lltoolrotate.BMP deleted file mode 100644 index dd84673018e..00000000000 Binary files a/indra/newview/res-sdl/lltoolrotate.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolrotate.png b/indra/newview/res-sdl/lltoolrotate.png new file mode 100644 index 00000000000..db2a9e70462 Binary files /dev/null and b/indra/newview/res-sdl/lltoolrotate.png differ diff --git a/indra/newview/res-sdl/lltoolscale.BMP b/indra/newview/res-sdl/lltoolscale.BMP deleted file mode 100644 index 882515e5e3c..00000000000 Binary files a/indra/newview/res-sdl/lltoolscale.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolscale.png b/indra/newview/res-sdl/lltoolscale.png new file mode 100644 index 00000000000..fc2d6a0c1e9 Binary files /dev/null and b/indra/newview/res-sdl/lltoolscale.png differ diff --git a/indra/newview/res-sdl/lltooltranslate.BMP b/indra/newview/res-sdl/lltooltranslate.BMP deleted file mode 100644 index d084f6a0269..00000000000 Binary files a/indra/newview/res-sdl/lltooltranslate.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltooltranslate.png b/indra/newview/res-sdl/lltooltranslate.png new file mode 100644 index 00000000000..bb9f17285c8 Binary files /dev/null and b/indra/newview/res-sdl/lltooltranslate.png differ diff --git a/indra/newview/res-sdl/lltoolzoomin.BMP b/indra/newview/res-sdl/lltoolzoomin.BMP deleted file mode 100644 index e4e46cc7028..00000000000 Binary files a/indra/newview/res-sdl/lltoolzoomin.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolzoomin.png b/indra/newview/res-sdl/lltoolzoomin.png new file mode 100644 index 00000000000..c18c9c65b64 Binary files /dev/null and b/indra/newview/res-sdl/lltoolzoomin.png differ diff --git a/indra/newview/res-sdl/lltoolzoomout.BMP b/indra/newview/res-sdl/lltoolzoomout.BMP deleted file mode 100644 index 5bdf96f80d1..00000000000 Binary files a/indra/newview/res-sdl/lltoolzoomout.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/lltoolzoomout.png b/indra/newview/res-sdl/lltoolzoomout.png new file mode 100644 index 00000000000..2c5afc954f3 Binary files /dev/null and b/indra/newview/res-sdl/lltoolzoomout.png differ diff --git a/indra/newview/res-sdl/sizeall.BMP b/indra/newview/res-sdl/sizeall.BMP deleted file mode 100644 index 03d9bf46540..00000000000 Binary files a/indra/newview/res-sdl/sizeall.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/sizenesw.BMP b/indra/newview/res-sdl/sizenesw.BMP deleted file mode 100644 index 559579f40e4..00000000000 Binary files a/indra/newview/res-sdl/sizenesw.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/sizens.BMP b/indra/newview/res-sdl/sizens.BMP deleted file mode 100644 index 8373077dffc..00000000000 Binary files a/indra/newview/res-sdl/sizens.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/sizenwse.BMP b/indra/newview/res-sdl/sizenwse.BMP deleted file mode 100644 index 6d069fa7658..00000000000 Binary files a/indra/newview/res-sdl/sizenwse.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/sizewe.BMP b/indra/newview/res-sdl/sizewe.BMP deleted file mode 100644 index 878df453a4a..00000000000 Binary files a/indra/newview/res-sdl/sizewe.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolbuy.BMP b/indra/newview/res-sdl/toolbuy.BMP deleted file mode 100644 index 07e92737218..00000000000 Binary files a/indra/newview/res-sdl/toolbuy.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolbuy.png b/indra/newview/res-sdl/toolbuy.png new file mode 100644 index 00000000000..508b87b2a5b Binary files /dev/null and b/indra/newview/res-sdl/toolbuy.png differ diff --git a/indra/newview/res-sdl/toolmediaopen.BMP b/indra/newview/res-sdl/toolmediaopen.BMP deleted file mode 100644 index ac4b231994c..00000000000 Binary files a/indra/newview/res-sdl/toolmediaopen.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolmediaopen.png b/indra/newview/res-sdl/toolmediaopen.png new file mode 100644 index 00000000000..f7d17223753 Binary files /dev/null and b/indra/newview/res-sdl/toolmediaopen.png differ diff --git a/indra/newview/res-sdl/toolopen.BMP b/indra/newview/res-sdl/toolopen.BMP deleted file mode 100644 index 5b879793049..00000000000 Binary files a/indra/newview/res-sdl/toolopen.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolopen.png b/indra/newview/res-sdl/toolopen.png new file mode 100644 index 00000000000..338c8396b91 Binary files /dev/null and b/indra/newview/res-sdl/toolopen.png differ diff --git a/indra/newview/res-sdl/toolpause.BMP b/indra/newview/res-sdl/toolpause.BMP deleted file mode 100644 index dd2c6857d28..00000000000 Binary files a/indra/newview/res-sdl/toolpause.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolpause.png b/indra/newview/res-sdl/toolpause.png new file mode 100644 index 00000000000..10e1cb13c9b Binary files /dev/null and b/indra/newview/res-sdl/toolpause.png differ diff --git a/indra/newview/res-sdl/toolpickobject.BMP b/indra/newview/res-sdl/toolpickobject.BMP deleted file mode 100644 index 25469fc3a86..00000000000 Binary files a/indra/newview/res-sdl/toolpickobject.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolpickobject2.BMP b/indra/newview/res-sdl/toolpickobject2.BMP deleted file mode 100644 index 09df69e675e..00000000000 Binary files a/indra/newview/res-sdl/toolpickobject2.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolpickobject3.BMP b/indra/newview/res-sdl/toolpickobject3.BMP deleted file mode 100644 index fc286980501..00000000000 Binary files a/indra/newview/res-sdl/toolpickobject3.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolpickobject3.png b/indra/newview/res-sdl/toolpickobject3.png new file mode 100644 index 00000000000..531ffc0df19 Binary files /dev/null and b/indra/newview/res-sdl/toolpickobject3.png differ diff --git a/indra/newview/res-sdl/toolplay.BMP b/indra/newview/res-sdl/toolplay.BMP deleted file mode 100644 index 9c40d7dbeca..00000000000 Binary files a/indra/newview/res-sdl/toolplay.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolplay.png b/indra/newview/res-sdl/toolplay.png new file mode 100644 index 00000000000..a6568f4b949 Binary files /dev/null and b/indra/newview/res-sdl/toolplay.png differ diff --git a/indra/newview/res-sdl/toolsit.BMP b/indra/newview/res-sdl/toolsit.BMP deleted file mode 100644 index 8ce59ae97a9..00000000000 Binary files a/indra/newview/res-sdl/toolsit.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/toolsit.png b/indra/newview/res-sdl/toolsit.png new file mode 100644 index 00000000000..8620527e521 Binary files /dev/null and b/indra/newview/res-sdl/toolsit.png differ diff --git a/indra/newview/res-sdl/wait.BMP b/indra/newview/res-sdl/wait.BMP deleted file mode 100644 index 26dec59afe1..00000000000 Binary files a/indra/newview/res-sdl/wait.BMP and /dev/null differ diff --git a/indra/newview/res-sdl/working.BMP b/indra/newview/res-sdl/working.BMP deleted file mode 100644 index 26dec59afe1..00000000000 Binary files a/indra/newview/res-sdl/working.BMP and /dev/null differ