summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yaml26
-rw-r--r--.github/workflows/check-pr.yaml2
-rw-r--r--.github/workflows/label.yaml2
-rw-r--r--.github/workflows/pre-commit.yaml4
-rw-r--r--.github/workflows/tag-release.yaml2
-rwxr-xr-x[-rw-r--r--].gitignore3
-rw-r--r--autobuild.xml16
-rw-r--r--indra/cmake/00-Common.cmake11
-rw-r--r--indra/cmake/Boost.cmake18
-rw-r--r--indra/cmake/Linking.cmake2
-rw-r--r--indra/cmake/Prebuilt.cmake38
-rw-r--r--indra/cmake/Variables.cmake33
-rw-r--r--indra/llappearance/llavatarappearance.cpp9
-rw-r--r--indra/llappearance/llpolymesh.cpp1
-rw-r--r--indra/llappearance/llwearable.cpp19
-rw-r--r--indra/llaudio/llaudioengine_openal.cpp104
-rw-r--r--indra/llaudio/llaudioengine_openal.h19
-rw-r--r--indra/llcharacter/llcharacter.cpp25
-rw-r--r--indra/llcharacter/llcharacter.h1
-rw-r--r--indra/llcharacter/llvisualparam.h1
-rw-r--r--indra/llcommon/llapr.cpp30
-rw-r--r--indra/llcommon/llcoros.cpp1
-rw-r--r--indra/llcommon/llerror.h1
-rw-r--r--indra/llcommon/llprocess.cpp1996
-rw-r--r--indra/llcommon/llprocess.h402
-rw-r--r--indra/llcommon/llqueuedthread.cpp27
-rw-r--r--indra/llcommon/llqueuedthread.h1
-rw-r--r--indra/llcommon/llsdserialize_xml.cpp77
-rw-r--r--indra/llcommon/llthread.cpp14
-rw-r--r--indra/llcommon/llwatchdog.cpp21
-rw-r--r--indra/llcommon/llwatchdog.h5
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp636
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp52
-rw-r--r--indra/llfilesystem/lldiskcache.cpp4
-rw-r--r--indra/llfilesystem/lllfsthread.cpp1
-rw-r--r--indra/llimagej2coj/llimagej2coj.cpp8
-rw-r--r--indra/llinventory/llinventory.cpp466
-rw-r--r--indra/llkdu/llimagej2ckdu.cpp4
-rw-r--r--indra/llmath/llvolume.cpp354
-rw-r--r--indra/llmath/llvolume.h9
-rw-r--r--indra/llmessage/llcircuit.cpp5
-rw-r--r--indra/llmessage/llpacketbuffer.h10
-rw-r--r--indra/llmessage/llpacketring.cpp315
-rw-r--r--indra/llmessage/llpacketring.h88
-rw-r--r--indra/llmessage/llqueryflags.h4
-rw-r--r--indra/llmessage/message.cpp370
-rw-r--r--indra/llmessage/message.h101
-rw-r--r--indra/llmessage/net.cpp2
-rw-r--r--indra/llphysicsextensionsos/llphysicsextensions.cpp1
-rw-r--r--indra/llplugin/llpluginclassmedia.cpp45
-rw-r--r--indra/llplugin/llpluginclassmedia.h7
-rw-r--r--indra/llplugin/llpluginmessagepipe.cpp3
-rw-r--r--indra/llplugin/llpluginprocessparent.h2
-rw-r--r--indra/llprimitive/llprimitive.cpp8
-rw-r--r--indra/llprimitive/llprimitive.h3
-rw-r--r--indra/llrender/llfontfreetype.cpp93
-rw-r--r--indra/llrender/llfontfreetype.h6
-rw-r--r--indra/llrender/llfontgl.cpp14
-rw-r--r--indra/llrender/llgl.cpp11
-rw-r--r--indra/llrender/llimagegl.cpp150
-rw-r--r--indra/llrender/llimagegl.h2
-rw-r--r--indra/llrender/llrender.cpp6
-rw-r--r--indra/llrender/llshadermgr.cpp90
-rw-r--r--indra/llrender/lluiimage.cpp211
-rw-r--r--indra/llrender/lluiimage.h126
-rw-r--r--indra/llrender/lluiimage.inl80
-rw-r--r--indra/llrender/llvertexbuffer.cpp13
-rw-r--r--indra/llui/CMakeLists.txt2
-rw-r--r--indra/llui/llcombobox.cpp2
-rw-r--r--indra/llui/llflatlistview.cpp473
-rw-r--r--indra/llui/llflatlistview.h78
-rw-r--r--indra/llui/llfloater.cpp2
-rw-r--r--indra/llui/llflyoutbutton.cpp20
-rw-r--r--indra/llui/llflyoutbutton.h2
-rw-r--r--indra/llui/llfolderview.cpp13
-rw-r--r--indra/llui/llfolderviewitem.cpp30
-rw-r--r--indra/llui/llfolderviewitem.h7
-rw-r--r--indra/llui/llgestureautocompletehelper.cpp165
-rw-r--r--indra/llui/llgestureautocompletehelper.h80
-rw-r--r--indra/llui/lllineeditor.cpp7
-rw-r--r--indra/llui/llscrollcontainer.cpp33
-rw-r--r--indra/llui/llscrollcontainer.h5
-rw-r--r--indra/llui/lltextbase.cpp1
-rw-r--r--indra/llui/lltextbase.h1
-rw-r--r--indra/llui/lltexteditor.cpp7
-rw-r--r--indra/llui/lltexteditor.h2
-rw-r--r--indra/llui/lltextvalidate.cpp12
-rw-r--r--indra/llui/lltextvalidate.h2
-rw-r--r--indra/llui/lltoolbar.cpp52
-rw-r--r--indra/llui/lltoolbar.h1
-rw-r--r--indra/llui/llurlaction.cpp13
-rw-r--r--indra/llui/llurlaction.h1
-rw-r--r--indra/llui/llxyvector.cpp11
-rw-r--r--indra/llwebrtc/llwebrtc.cpp225
-rw-r--r--indra/llwebrtc/llwebrtc_impl.h13
-rw-r--r--indra/llwindow/CMakeLists.txt122
-rw-r--r--indra/llwindow/llwindowcallbacks.cpp14
-rw-r--r--indra/llwindow/llwindowcallbacks.h6
-rw-r--r--indra/llwindow/llwindowmacosx.cpp4
-rw-r--r--indra/llwindow/llwindowmacosx_iokit.h35
-rw-r--r--indra/llwindow/llwindowwin32.cpp369
-rw-r--r--indra/llwindow/llwindowwin32.h7
-rw-r--r--indra/llxml/llcontrol.cpp2
-rw-r--r--indra/llxml/llcontrol.h2
-rw-r--r--indra/media_plugins/cef/media_plugin_cef.cpp5
-rw-r--r--indra/newview/CMakeLists.txt75
-rw-r--r--indra/newview/Info-SecondLife.plist2
-rw-r--r--indra/newview/app_settings/commands.xml10
-rw-r--r--indra/newview/app_settings/settings.xml81
-rw-r--r--indra/newview/app_settings/shaders/class3/deferred/materialF.glsl10
-rw-r--r--indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl2
-rw-r--r--indra/newview/app_settings/toolbars.xml1
-rw-r--r--indra/newview/gltf/llgltfloader.cpp122
-rw-r--r--indra/newview/gltf/llgltfloader.h2
-rw-r--r--indra/newview/licenses-linux.txt193
-rw-r--r--indra/newview/licenses-mac.txt202
-rw-r--r--indra/newview/licenses-win32.txt202
-rw-r--r--indra/newview/llagent.cpp27
-rw-r--r--indra/newview/llagentwearables.cpp46
-rw-r--r--indra/newview/llagentwearables.h1
-rw-r--r--indra/newview/llaisapi.cpp76
-rw-r--r--indra/newview/llaisapi.h2
-rw-r--r--indra/newview/llappearancemgr.cpp130
-rw-r--r--indra/newview/llappearancemgr.h13
-rw-r--r--indra/newview/llappviewer.cpp522
-rw-r--r--indra/newview/llappviewer.h26
-rw-r--r--indra/newview/llappviewerlinux.cpp189
-rw-r--r--indra/newview/llappviewerlinux.h18
-rw-r--r--indra/newview/llappviewermacosx-objc.h2
-rw-r--r--indra/newview/llappviewermacosx-objc.mm91
-rw-r--r--indra/newview/llappviewermacosx.cpp25
-rw-r--r--indra/newview/llappviewermacosx.h2
-rw-r--r--indra/newview/llappviewerwin32.cpp300
-rw-r--r--indra/newview/llappviewerwin32.h8
-rw-r--r--indra/newview/llavataractions.cpp7
-rw-r--r--indra/newview/llchathistory.cpp47
-rw-r--r--indra/newview/llchatitemscontainerctrl.cpp28
-rw-r--r--indra/newview/llcofwearables.cpp66
-rw-r--r--indra/newview/llcofwearables.h9
-rw-r--r--indra/newview/lldrawable.cpp13
-rw-r--r--indra/newview/lldrawpool.cpp35
-rw-r--r--indra/newview/lldrawpoolalpha.cpp6
-rw-r--r--indra/newview/lldrawpoolavatar.cpp37
-rw-r--r--indra/newview/lleventpoll.cpp49
-rw-r--r--indra/newview/llfilepicker_mac.mm63
-rw-r--r--indra/newview/llfloaterbuycurrency.cpp17
-rw-r--r--indra/newview/llfloatercolorpicker.cpp162
-rw-r--r--indra/newview/llfloatercolorpicker.h8
-rw-r--r--indra/newview/llfloateremojipicker.cpp3
-rw-r--r--indra/newview/llfloatergestureautocompletepicker.cpp154
-rw-r--r--indra/newview/llfloatergestureautocompletepicker.h (renamed from indra/newview/llfloaterhowto.h)41
-rw-r--r--indra/newview/llfloaterhowto.cpp92
-rw-r--r--indra/newview/llfloaterimcontainer.cpp26
-rw-r--r--indra/newview/llfloaterimnearbychat.cpp93
-rw-r--r--indra/newview/llfloaterimsessiontab.h2
-rw-r--r--indra/newview/llfloaterinspect.cpp21
-rw-r--r--indra/newview/llfloaterland.cpp30
-rw-r--r--indra/newview/llfloaterland.h2
-rw-r--r--indra/newview/llfloaterobjectweights.cpp228
-rw-r--r--indra/newview/llfloaterobjectweights.h21
-rw-r--r--indra/newview/llfloaterreporter.cpp25
-rw-r--r--indra/newview/llfloaterreporter.h3
-rw-r--r--indra/newview/llfloatersnapshot.cpp77
-rw-r--r--indra/newview/llfloatersnapshot.h2
-rw-r--r--indra/newview/llfloaterworldmap.cpp6
-rw-r--r--indra/newview/llfloaterworldmap.h2
-rw-r--r--indra/newview/llgltfmateriallist.cpp2
-rw-r--r--indra/newview/llgroupmgr.cpp10
-rw-r--r--indra/newview/llhudnametag.cpp19
-rw-r--r--indra/newview/llimprocessing.cpp3
-rw-r--r--indra/newview/llimview.cpp10
-rw-r--r--indra/newview/llinventorybridge.cpp108
-rw-r--r--indra/newview/llinventorybridge.h11
-rw-r--r--indra/newview/llinventoryfilter.h4
-rw-r--r--indra/newview/llinventorymodel.cpp237
-rw-r--r--indra/newview/llinventorymodel.h3
-rw-r--r--indra/newview/llinventoryobserver.h2
-rw-r--r--indra/newview/llinventorypanel.cpp45
-rw-r--r--indra/newview/lllogininstance.cpp2
-rw-r--r--indra/newview/llmachineid.cpp4
-rw-r--r--indra/newview/llmanipscale.cpp6
-rw-r--r--indra/newview/llmaterialeditor.cpp128
-rw-r--r--indra/newview/llmediactrl.cpp218
-rw-r--r--indra/newview/llmediactrl.h14
-rw-r--r--indra/newview/llmutelist.cpp8
-rw-r--r--indra/newview/llmutelist.h6
-rw-r--r--indra/newview/llnotificationstorage.cpp22
-rw-r--r--indra/newview/lloutfitslist.cpp20
-rw-r--r--indra/newview/llpaneldirgroups.cpp24
-rw-r--r--indra/newview/llpanelface.cpp50
-rw-r--r--indra/newview/llpanelgrouproles.cpp25
-rw-r--r--indra/newview/llpanelgrouproles.h1
-rw-r--r--indra/newview/llpanellandmedia.cpp7
-rw-r--r--indra/newview/llpanellogin.cpp2
-rw-r--r--indra/newview/llpanelmaininventory.cpp12
-rw-r--r--indra/newview/llpanelmarketplaceinboxinventory.cpp30
-rw-r--r--indra/newview/llpanelobjectinventory.cpp4
-rw-r--r--indra/newview/llpanelplaces.cpp22
-rw-r--r--indra/newview/llpanelprofilepicks.cpp25
-rw-r--r--indra/newview/llpanelprofilepicks.h17
-rw-r--r--indra/newview/llpanelsnapshot.cpp13
-rw-r--r--indra/newview/llpanelvoicedevicesettings.cpp2
-rw-r--r--indra/newview/llpreviewtexture.cpp3
-rw-r--r--indra/newview/llselectmgr.cpp1
-rw-r--r--indra/newview/llselectmgr.h1
-rw-r--r--indra/newview/llstartup.cpp162
-rw-r--r--indra/newview/llstatusbar.cpp1
-rw-r--r--indra/newview/lltexturefetch.cpp8
-rw-r--r--indra/newview/lltoolbrush.cpp5
-rw-r--r--indra/newview/lltooldraganddrop.cpp7
-rw-r--r--indra/newview/lltoolmorph.cpp53
-rw-r--r--indra/newview/lltoolmorph.h1
-rw-r--r--indra/newview/lltranslate.cpp10
-rw-r--r--indra/newview/llurlfloaterdispatchhandler.cpp57
-rw-r--r--indra/newview/llvelopack.cpp115
-rw-r--r--indra/newview/llviewerassetstorage.cpp6
-rw-r--r--indra/newview/llviewerfloaterreg.cpp4
-rw-r--r--indra/newview/llviewerinventory.cpp49
-rw-r--r--indra/newview/llviewerinventory.h22
-rw-r--r--indra/newview/llviewerjoint.cpp1
-rw-r--r--indra/newview/llviewerjointmesh.cpp122
-rw-r--r--indra/newview/llviewerjointmesh.h5
-rw-r--r--indra/newview/llviewermedia.cpp51
-rw-r--r--indra/newview/llviewermenu.cpp53
-rw-r--r--indra/newview/llviewermenufile.cpp4
-rw-r--r--indra/newview/llviewermessage.cpp30
-rw-r--r--indra/newview/llviewernetwork.cpp3
-rw-r--r--indra/newview/llviewerobject.cpp26
-rw-r--r--indra/newview/llviewerobject.h2
-rw-r--r--indra/newview/llviewerobjectlist.cpp10
-rw-r--r--indra/newview/llviewerregion.cpp21
-rw-r--r--indra/newview/llviewershadermgr.cpp39
-rw-r--r--indra/newview/llviewershadermgr.h1
-rw-r--r--indra/newview/llviewerstats.cpp2
-rw-r--r--indra/newview/llviewertexteditor.cpp19
-rw-r--r--indra/newview/llviewertexturelist.cpp1
-rw-r--r--indra/newview/llviewerthrottle.cpp23
-rw-r--r--indra/newview/llviewerthrottle.h4
-rw-r--r--indra/newview/llviewerwindow.cpp67
-rw-r--r--indra/newview/llviewerwindow.h5
-rw-r--r--indra/newview/llvoavatar.cpp140
-rw-r--r--indra/newview/llvoavatar.h29
-rw-r--r--indra/newview/llvoavatarself.cpp21
-rw-r--r--indra/newview/llvoavatarself.h1
-rw-r--r--indra/newview/llvocache.cpp7
-rw-r--r--indra/newview/llvoicechannel.cpp6
-rw-r--r--indra/newview/llvoiceclient.cpp11
-rw-r--r--indra/newview/llvoicewebrtc.cpp9
-rw-r--r--indra/newview/llvovolume.cpp13
-rw-r--r--indra/newview/llvovolume.h1
-rw-r--r--indra/newview/llwindowlistener.cpp41
-rw-r--r--indra/newview/llwindowlistener.h1
-rw-r--r--indra/newview/llworld.cpp15
-rw-r--r--indra/newview/pipeline.cpp12
-rw-r--r--indra/newview/skins/default/textures/textures.xml1
-rw-r--r--indra/newview/skins/default/textures/toolbar_icons/howto.pngbin1306 -> 0 bytes
-rw-r--r--indra/newview/skins/default/textures/widgets/ComboButton_Off.pngbin15127 -> 559 bytes
-rw-r--r--indra/newview/skins/default/textures/widgets/ComboButton_Selected.pngbin15216 -> 541 bytes
-rw-r--r--indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Off.pngbin521 -> 435 bytes
-rw-r--r--indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Selected.pngbin576 -> 436 bytes
-rw-r--r--indra/newview/skins/default/xui/da/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/da/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/da/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/de/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/de/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/de/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/de/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/de/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/de/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/de/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/en/floater_about_land.xml31
-rw-r--r--indra/newview/skins/default/xui/en/floater_color_picker.xml86
-rw-r--r--indra/newview/skins/default/xui/en/floater_create_landmark.xml2
-rw-r--r--indra/newview/skins/default/xui/en/floater_edit_ext_day_cycle.xml15
-rw-r--r--indra/newview/skins/default/xui/en/floater_gesture_autocomplete_picker.xml39
-rw-r--r--indra/newview/skins/default/xui/en/floater_how_to.xml16
-rw-r--r--indra/newview/skins/default/xui/en/floater_inventory_settings.xml6
-rw-r--r--indra/newview/skins/default/xui/en/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/en/floater_object_weights.xml172
-rw-r--r--indra/newview/skins/default/xui/en/floater_preferences.xml6
-rw-r--r--indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml2
-rw-r--r--indra/newview/skins/default/xui/en/floater_snapshot.xml1
-rw-r--r--indra/newview/skins/default/xui/en/floater_tools.xml8
-rw-r--r--indra/newview/skins/default/xui/en/floater_translation_settings.xml2
-rw-r--r--indra/newview/skins/default/xui/en/floater_world_map.xml3
-rw-r--r--indra/newview/skins/default/xui/en/menu_conversation_log_view.xml4
-rw-r--r--indra/newview/skins/default/xui/en/menu_login.xml18
-rw-r--r--indra/newview/skins/default/xui/en/menu_object_icon.xml8
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_objectim.xml7
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml47
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml22
-rw-r--r--indra/newview/skins/default/xui/en/panel_cof_wearables.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_group_roles.xml12
-rw-r--r--indra/newview/skins/default/xui/en/panel_inbox_inventory.xml1
-rw-r--r--indra/newview/skins/default/xui/en/panel_landmark_info.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_outfit_gallery.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_outfits_list.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_setup.xml36
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_sound.xml175
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_voice.xml222
-rw-r--r--indra/newview/skins/default/xui/en/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml4
-rw-r--r--indra/newview/skins/default/xui/en/panel_sound_devices.xml36
-rw-r--r--indra/newview/skins/default/xui/en/panel_tools_texture.xml52
-rw-r--r--indra/newview/skins/default/xui/en/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml9
-rw-r--r--indra/newview/skins/default/xui/en/widgets/flyout_button.xml12
-rw-r--r--indra/newview/skins/default/xui/es/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/es/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/es/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/es/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/es/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/es/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/es/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/fr/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/fr/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/fr/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/fr/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/fr/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/fr/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/it/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/it/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/it/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/it/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/it/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/it/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/it/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/ja/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/ja/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/ja/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/ja/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/ja/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/ja/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/pl/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/pl/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/pl/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/pl/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/pl/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/pt/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/pt/floater_settings_debug.xml12
-rw-r--r--indra/newview/skins/default/xui/pt/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/pt/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/pt/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/pt/settings_comments.xml3019
-rw-r--r--indra/newview/skins/default/xui/pt/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/pt/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/ru/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/ru/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/ru/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/ru/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/ru/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/ru/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/tr/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/tr/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/tr/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/tr/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/tr/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/tr/strings.xml6
-rw-r--r--indra/newview/skins/default/xui/zh/floater_how_to.xml2
-rw-r--r--indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml2
-rw-r--r--indra/newview/skins/default/xui/zh/notifications.xml1
-rw-r--r--indra/newview/skins/default/xui/zh/panel_preferences_setup.xml8
-rw-r--r--indra/newview/skins/default/xui/zh/panel_script_ed.xml2
-rw-r--r--indra/newview/skins/default/xui/zh/sidepanel_item_info.xml2
-rw-r--r--indra/newview/skins/default/xui/zh/strings.xml6
-rwxr-xr-xindra/newview/viewer_manifest.py2
372 files changed, 14708 insertions, 3843 deletions
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 4b9cf6f0c3..84f58fe5e6 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -111,7 +111,7 @@ jobs:
autobuild_configure_parameters: ${{ (contains(matrix.runner, 'windows') || contains(matrix.runner, 'macos')) && (github.event.inputs.installer_type || 'velopack') == 'velopack' && '-- -DUSE_VELOPACK:BOOL=ON' || '' }}
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
@@ -130,18 +130,18 @@ jobs:
df -h
- name: Setup python
- uses: actions/setup-python@v6
+ uses: actions/setup-python@v7
with:
python-version: "3.11"
- name: Checkout build variables
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
with:
repository: secondlife/build-variables
ref: master
path: .build-variables
- name: Checkout master-message-template
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
with:
repository: secondlife/master-message-template
path: .master-message-template
@@ -151,7 +151,7 @@ jobs:
- name: Cache autobuild packages
id: cache-installables
- uses: actions/cache@v5
+ uses: actions/cache@v6
with:
path: .autobuild-installables
key: ${{ runner.os }}-64-${{ matrix.configuration }}-${{ matrix.build_variant }}-${{ hashFiles('autobuild.xml') }}
@@ -330,8 +330,8 @@ jobs:
echo "artifact=$RUNNER_OS$cfg_suffix" >> $GITHUB_OUTPUT
- name: Upload executable
- uses: actions/upload-artifact@v7
if: matrix.build_variant == 'Viewer' && steps.build.outputs.viewer_app
+ uses: actions/upload-artifact@v7
with:
name: "${{ steps.build.outputs.artifact }}-app"
path: |
@@ -340,8 +340,8 @@ jobs:
# The other upload of nontrivial size is the symbol file. Use a distinct
# artifact for that too.
- name: Upload symbol file
- uses: actions/upload-artifact@v7
if: matrix.build_variant == 'Viewer' && steps.build.outputs.symbolfile
+ uses: actions/upload-artifact@v7
with:
name: "${{ steps.build.outputs.artifact }}-symbols"
path: ${{ steps.build.outputs.symbolfile }}
@@ -476,7 +476,7 @@ jobs:
tar -xJf "${{ needs.build.outputs.viewer_channel }}.sym.tar.xz" -C _artifacts
- name: Post Windows symbols
if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID
- uses: BugSplat-Git/symbol-upload@095d163ae9ceb006d286a731dcd35cf6a1b458c8
+ uses: BugSplat-Git/symbol-upload@3857759bd957dadb075509a4e951f04f9baa5ea3
with:
clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}"
clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}"
@@ -504,7 +504,7 @@ jobs:
name: macOS-symbols
- name: Post Mac symbols
if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID
- uses: BugSplat-Git/symbol-upload@095d163ae9ceb006d286a731dcd35cf6a1b458c8
+ uses: BugSplat-Git/symbol-upload@3857759bd957dadb075509a4e951f04f9baa5ea3
with:
clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}"
clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}"
@@ -529,13 +529,13 @@ jobs:
with:
pattern: "*-metadata"
- - uses: actions/download-artifact@v7
+ - uses: actions/download-artifact@v8
with:
- name: "Linux-app"
+ pattern: "*-releases"
- - uses: actions/download-artifact@v7
+ - uses: actions/download-artifact@v8
with:
- pattern: "*-releases"
+ name: "Linux-app"
- name: Rename metadata
run: |
diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml
index 08e907e83f..68ca8244c0 100644
--- a/.github/workflows/check-pr.yaml
+++ b/.github/workflows/check-pr.yaml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check PR description
- uses: actions/github-script@v8
+ uses: actions/github-script@v9
with:
script: |
const description = context.payload.pull_request.body || '';
diff --git a/.github/workflows/label.yaml b/.github/workflows/label.yaml
index 218327ef47..bb3a9031e7 100644
--- a/.github/workflows/label.yaml
+++ b/.github/workflows/label.yaml
@@ -9,7 +9,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@v6
+ - uses: actions/labeler@v7
with:
configuration-path: .github/labeler.yaml
repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml
index 93bcafdea8..5bc3cf6ccc 100644
--- a/.github/workflows/pre-commit.yaml
+++ b/.github/workflows/pre-commit.yaml
@@ -11,8 +11,8 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
- - uses: actions/setup-python@v6
+ - uses: actions/checkout@v7
+ - uses: actions/setup-python@v7
with:
python-version: 3.x
- uses: pre-commit/action@v3.0.1
diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml
index 0f826222a0..f1afb0f1a2 100644
--- a/.github/workflows/tag-release.yaml
+++ b/.github/workflows/tag-release.yaml
@@ -37,7 +37,7 @@ jobs:
echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV}
echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV}
- name: Create Tag
- uses: actions/github-script@v8
+ uses: actions/github-script@v9
with:
# use a real access token instead of GITHUB_TOKEN default.
# required so that the results of this tag creation can trigger the build workflow
diff --git a/.gitignore b/.gitignore
index 483e7083ad..eb8001b238 100644..100755
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,9 @@
*~
# Specific paths and/or names
+/.autobuild-installables/
+/.build-variables/
+/.logs/
.venv
CMakeCache.txt
cmake_install.cmake
diff --git a/autobuild.xml b/autobuild.xml
index 97ee82ac14..9284d39d7b 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -2248,11 +2248,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>e88a7c97a6843d43e0093388f211299ec2892790</string>
+ <string>d1584b3a0011dbb741bc64d32e8bd28845ddd4da</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.0-r1/viewer_fonts-1.0.0.10204976553-common-10204976553.tar.zst</string>
+ <string>https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.0-r2/viewer_fonts-1.0.0.25128287087-common-25128287087.tar.zst</string>
</map>
<key>name</key>
<string>common</string>
@@ -2263,9 +2263,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>license_file</key>
<string>LICENSES/fonts.txt</string>
<key>copyright</key>
- <string>Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT</string>
+ <string>Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT, Copyright (c) 2003 by Bitstream, Inc., Copyright (c) 2006 by Tavmjong Bah.</string>
<key>version</key>
- <string>1.0.0.10204976553</string>
+ <string>1.0.0.25128287087</string>
<key>name</key>
<string>viewer-fonts</string>
<key>description</key>
@@ -2712,11 +2712,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>49046b0b41310d2fb0d72853730b2faad35d3e36</string>
+ <string>140d8fc952a10edb5f2d72ab405336019ef32cadfa64f0cfce76c9de4bc6268cbc87cc8cd89d3417fb78b531d441701afc8d016bafe4bd275df2707f7daf1387</string>
<key>hash_algorithm</key>
- <string>sha1</string>
+ <string>blake2b</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-vhacd/releases/download/v4.1.0-r1/vhacd-4.1.0-r1-common-18475951995.tar.zst</string>
+ <string>https://github.com/AlchemyViewer/3p-vhacd/releases/download/v4.1.0-r2/vhacd-4.1.0-r2-common-18166921729.tar.zst</string>
</map>
<key>name</key>
<string>common</string>
@@ -2729,7 +2729,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>copyright</key>
<string>Copyright (c) 2011, Khaled Mamou</string>
<key>version</key>
- <string>4.1.0-r1</string>
+ <string>4.1.0-r2</string>
<key>name</key>
<string>vhacd</string>
<key>description</key>
diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake
index 1ab94e13df..27d7f9e61f 100644
--- a/indra/cmake/00-Common.cmake
+++ b/indra/cmake/00-Common.cmake
@@ -22,6 +22,17 @@ include(Variables)
include(Linking)
# We go to some trouble to set LL_BUILD to the set of relevant compiler flags.
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{LL_BUILD}")
+
+if (DARWIN)
+ # LL_BUILD carries a literal -mmacosx-version-min=<ver> from
+ # viewer-build-variables. We derive the macOS deployment target in
+ # Variables.cmake (clamping up to the SDK's supported minimum when needed)
+ # and let CMAKE_OSX_DEPLOYMENT_TARGET emit the flag to both compiler and
+ # linker. Strip the verbatim flag here so it can't conflict with the
+ # clamped deployment target.
+ string(REGEX REPLACE "-mmacosx-version-min=[0-9.]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+endif (DARWIN)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} $ENV{LL_BUILD_RELEASE}")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} $ENV{LL_BUILD_RELWITHDEBINFO}")
diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake
index fd3e0eccde..eba39a4b19 100644
--- a/indra/cmake/Boost.cmake
+++ b/indra/cmake/Boost.cmake
@@ -67,12 +67,28 @@ find_library(BOOST_URL_LIBRARY
boost_url-mt${addrsfx}
PATHS "${ARCH_PREBUILT_DIRS_RELEASE}" REQUIRED NO_DEFAULT_PATH)
+find_library(BOOST_PROCESS_LIBRARY
+ NAMES
+ libboost_process
+ libboost_process-mt
+ libboost_process-mt${addrsfx}
+ boost_process
+ boost_process-mt
+ boost_process-mt${addrsfx}
+ PATHS "${ARCH_PREBUILT_DIRS_RELEASE}" REQUIRED NO_DEFAULT_PATH)
+
target_link_libraries(ll::boost INTERFACE
${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_FILESYSTEM_LIBRARY}
${BOOST_PROGRAMOPTIONS_LIBRARY}
- ${BOOST_URL_LIBRARY})
+ ${BOOST_URL_LIBRARY}
+ ${BOOST_PROCESS_LIBRARY})
+
+if (WINDOWS)
+ # Boost.Process v2 needs ntdll (NtSuspendProcess/NtResumeProcess)
+ target_link_libraries(ll::boost INTERFACE ntdll)
+endif (WINDOWS)
if (LINUX)
target_link_libraries(ll::boost INTERFACE rt)
diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake
index 900a64e2dd..5d4e8d8634 100644
--- a/indra/cmake/Linking.cmake
+++ b/indra/cmake/Linking.cmake
@@ -77,6 +77,7 @@ else()
find_library(COREAUDIO_LIBRARY CoreAudio)
find_library(COREGRAPHICS_LIBRARY CoreGraphics)
find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox)
+ find_library(UNIFORMTYPEIDENTIFIERS_LIBRARY UniformTypeIdentifiers)
target_link_libraries( ll::oslibraries INTERFACE
${COCOA_LIBRARY}
@@ -87,6 +88,7 @@ else()
${COREAUDIO_LIBRARY}
${AUDIOTOOLBOX_LIBRARY}
${COREGRAPHICS_LIBRARY}
+ ${UNIFORMTYPEIDENTIFIERS_LIBRARY}
)
endif()
diff --git a/indra/cmake/Prebuilt.cmake b/indra/cmake/Prebuilt.cmake
index d0ab341d94..77543a653f 100644
--- a/indra/cmake/Prebuilt.cmake
+++ b/indra/cmake/Prebuilt.cmake
@@ -38,14 +38,36 @@ macro (use_prebuilt_binary _binary)
${_binary} ")
endif(DEBUG_PREBUILT)
message(STATUS "Installing ${_binary}...")
- execute_process(COMMAND "${AUTOBUILD_EXECUTABLE}"
- install
- --skip-source-environment
- --install-dir=${AUTOBUILD_INSTALL_DIR}
- ${_binary}
- WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
- RESULT_VARIABLE ${_binary}_installed
- )
+
+ # Retry transient download failures (e.g. remote disconnects) instead
+ # of failing the whole configure on the first attempt.
+ set(_max_retries 4)
+ set(_retry_delay 8)
+ set(${_binary}_installed 1)
+
+ foreach(_attempt RANGE 1 ${_max_retries})
+ if(DEBUG_PREBUILT)
+ message(STATUS "autobuild install ${_binary}: attempt ${_attempt}/${_max_retries}")
+ endif(DEBUG_PREBUILT)
+ execute_process(COMMAND "${AUTOBUILD_EXECUTABLE}"
+ install
+ --skip-source-environment
+ --install-dir=${AUTOBUILD_INSTALL_DIR}
+ ${_binary}
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE ${_binary}_installed
+ )
+
+ if(${${_binary}_installed} EQUAL 0)
+ break()
+ endif()
+
+ if(_attempt LESS _max_retries)
+ message(WARNING "autobuild install ${_binary} failed (rc=${${_binary}_installed}); retrying in ${_retry_delay}s")
+ execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep ${_retry_delay})
+ endif()
+ endforeach()
+
file(WRITE ${PREBUILD_TRACKING_DIR}/${_binary}_installed "${${_binary}_installed}")
endif(${PREBUILD_TRACKING_DIR}/sentinel_installed IS_NEWER_THAN ${PREBUILD_TRACKING_DIR}/${_binary}_installed OR NOT ${${_binary}_installed} EQUAL 0)
diff --git a/indra/cmake/Variables.cmake b/indra/cmake/Variables.cmake
index 81a8f87cd3..405f2cd742 100644
--- a/indra/cmake/Variables.cmake
+++ b/indra/cmake/Variables.cmake
@@ -170,7 +170,38 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(DARWIN 1)
string(REGEX MATCH "-mmacosx-version-min=([^ ]+)" scratch "$ENV{LL_BUILD}")
- set(CMAKE_OSX_DEPLOYMENT_TARGET "${CMAKE_MATCH_1}" CACHE STRING "macOS Deploy Target" FORCE)
+ set(LL_REQUESTED_DEPLOYMENT_TARGET "${CMAKE_MATCH_1}")
+
+ # Determine the lowest deployment target the active macOS SDK still supports.
+ # We aim for 11.0 in our public builds, but newer Xcode/SDK releases
+ # periodically raise this floor (e.g. Xcode 26 -> 13.3, Xcode 27 -> 14), and
+ # linking against a deployment target below the SDK's minimum fails. Read the
+ # supported minimum from the SDK and clamp our requested target up to it when
+ # necessary, so the build tracks whatever the SDK allows automatically.
+ set(LL_SDK_MINIMUM_DEPLOYMENT_TARGET "")
+ execute_process(
+ COMMAND xcrun --sdk macosx --show-sdk-path
+ OUTPUT_VARIABLE LL_MACOS_SDK_PATH
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET)
+ if (LL_MACOS_SDK_PATH AND EXISTS "${LL_MACOS_SDK_PATH}/SDKSettings.plist")
+ execute_process(
+ COMMAND /usr/libexec/PlistBuddy -c
+ "Print :SupportedTargets:macosx:MinimumDeploymentTarget"
+ "${LL_MACOS_SDK_PATH}/SDKSettings.plist"
+ OUTPUT_VARIABLE LL_SDK_MINIMUM_DEPLOYMENT_TARGET
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET)
+ endif ()
+
+ set(LL_EFFECTIVE_DEPLOYMENT_TARGET "${LL_REQUESTED_DEPLOYMENT_TARGET}")
+ if (LL_SDK_MINIMUM_DEPLOYMENT_TARGET AND
+ LL_REQUESTED_DEPLOYMENT_TARGET VERSION_LESS LL_SDK_MINIMUM_DEPLOYMENT_TARGET)
+ message(STATUS "Requested macOS deploy target ${LL_REQUESTED_DEPLOYMENT_TARGET} is below the SDK minimum ${LL_SDK_MINIMUM_DEPLOYMENT_TARGET}; clamping to ${LL_SDK_MINIMUM_DEPLOYMENT_TARGET}")
+ set(LL_EFFECTIVE_DEPLOYMENT_TARGET "${LL_SDK_MINIMUM_DEPLOYMENT_TARGET}")
+ endif ()
+
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "${LL_EFFECTIVE_DEPLOYMENT_TARGET}" CACHE STRING "macOS Deploy Target" FORCE)
message(STATUS "CMAKE_OSX_DEPLOYMENT_TARGET = '${CMAKE_OSX_DEPLOYMENT_TARGET}'")
# Use dwarf symbols for most libraries for compilation speed
diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp
index dab18c240d..c8fd2cba70 100644
--- a/indra/llappearance/llavatarappearance.cpp
+++ b/indra/llappearance/llavatarappearance.cpp
@@ -1647,10 +1647,10 @@ glm::mat4 LLAvatarBoneInfo::getJointMatrix()
glm::mat4 mat(1.0f);
// 1. Scaling
mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2]));
- // 2. Rotation (Euler angles rad)
- mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0));
- mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0));
- mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1));
+ // 2. Rotation (avatar_skeleton.xml stores Euler angles in degrees)
+ mat = glm::rotate(mat, glm::radians(mRot[0]), glm::vec3(1, 0, 0));
+ mat = glm::rotate(mat, glm::radians(mRot[1]), glm::vec3(0, 1, 0));
+ mat = glm::rotate(mat, glm::radians(mRot[2]), glm::vec3(0, 0, 1));
// 3. Position
mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2]));
return mat;
@@ -1698,6 +1698,7 @@ void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(
data.mRestMatrix = parent_mat * data.mJointMatrix;
data.mIsJoint = bone_info->mIsJoint;
data.mGroup = bone_info->mGroup;
+ data.setSupport(bone_info->mSupport);
for (LLAvatarBoneInfo* child_info : bone_info->mChildren)
{
LLJointData& child_data = data.mChildren.emplace_back();
diff --git a/indra/llappearance/llpolymesh.cpp b/indra/llappearance/llpolymesh.cpp
index 45f51950ec..b93a232419 100644
--- a/indra/llappearance/llpolymesh.cpp
+++ b/indra/llappearance/llpolymesh.cpp
@@ -862,6 +862,7 @@ LLPolyMesh *LLPolyMesh::getMesh(const std::string &name, LLPolyMesh* reference_m
//-----------------------------------------------------------------------------
void LLPolyMesh::freeAllMeshes()
{
+ LL_PROFILE_ZONE_SCOPED;
// delete each item in the global lists
for_each(sGlobalSharedMeshList.begin(), sGlobalSharedMeshList.end(), DeletePairedPointer());
sGlobalSharedMeshList.clear();
diff --git a/indra/llappearance/llwearable.cpp b/indra/llappearance/llwearable.cpp
index 4acb0ef3d4..5758f79791 100644
--- a/indra/llappearance/llwearable.cpp
+++ b/indra/llappearance/llwearable.cpp
@@ -727,17 +727,22 @@ void LLWearable::writeToAvatar(LLAvatarAppearance* avatarp)
if (!avatarp) return;
// Pull params
- for( const LLVisualParam* param = avatarp->getFirstVisualParam(); param; param = avatarp->getNextVisualParam() )
+ // Iterate over wearable's params first since they should be fewer than avatar params.
+ // Wearables are cloned from avatar params by mType, so they should be always be a
+ // subset of the avatar's params and a situation where mType param doesn't exist in
+ // a wearable yet exist in the avatar should not happen.
+ for (const visual_param_index_map_t::value_type& vp_pair : mVisualParamIndexMap)
{
+ LLVisualParam* wearable_param = vp_pair.second;
+
// cross-wearable parameters are not authoritative, as they are driven by a different wearable. So don't copy the values to the
// avatar object if cross wearable. Cross wearable params get their values from the avatar, they shouldn't write the other way.
- if( (((LLViewerVisualParam*)param)->getWearableType() == mType) && (!((LLViewerVisualParam*)param)->getCrossWearable()) )
- {
- S32 param_id = param->getID();
- F32 weight = getVisualParamWeight(param_id);
+ if (((LLViewerVisualParam*)wearable_param)->getCrossWearable())
+ continue;
- avatarp->setVisualParamWeight( param_id, weight);
- }
+ F32 weight = wearable_param->getWeight();
+ // Silently fails if param was not found
+ avatarp->setVisualParamWeight(vp_pair.first, mType, weight);
}
}
diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp
index 755547bfaa..4126c8ef80 100644
--- a/indra/llaudio/llaudioengine_openal.cpp
+++ b/indra/llaudio/llaudioengine_openal.cpp
@@ -42,7 +42,14 @@ LLAudioEngine_OpenAL::LLAudioEngine_OpenAL()
mWindBufSamples(0),
mWindBufBytes(0),
mWindSource(AL_NONE),
- mNumEmptyWindALBuffers(MAX_NUM_WIND_BUFFERS)
+ mNumEmptyWindALBuffers(MAX_NUM_WIND_BUFFERS),
+ mALDevice(NULL),
+ mHasReopenExt(false),
+ mReopenDeviceSOFT(NULL),
+ mHasSystemEventsExt(false),
+ mEventControlSOFT(NULL),
+ mEventCallbackSOFT(NULL),
+ mDefaultDeviceChanged(false)
{
}
@@ -87,6 +94,38 @@ bool LLAudioEngine_OpenAL::init(void* userdata, const std::string &app_title)
ALC_DEFAULT_DEVICE_SPECIFIER))
<< LL_ENDL;
+ mALDevice = device;
+
+ mHasReopenExt = alcIsExtensionPresent(mALDevice, "ALC_SOFT_reopen_device");
+ if (mHasReopenExt)
+ {
+ mReopenDeviceSOFT = (LPALCREOPENDEVICESOFT)alcGetProcAddress(mALDevice, "alcReopenDeviceSOFT");
+ mHasReopenExt = (mReopenDeviceSOFT != NULL);
+ }
+ LL_INFOS() << "ALC_SOFT_reopen_device " << (mHasReopenExt ? "supported" : "NOT supported") << LL_ENDL;
+
+ mHasSystemEventsExt = alcIsExtensionPresent(mALDevice, "ALC_SOFT_system_events");
+ if (mHasSystemEventsExt)
+ {
+ mEventControlSOFT = (LPALCEVENTCONTROLSOFT)alcGetProcAddress(mALDevice, "alcEventControlSOFT");
+ mEventCallbackSOFT = (LPALCEVENTCALLBACKSOFT)alcGetProcAddress(mALDevice, "alcEventCallbackSOFT");
+ mHasSystemEventsExt = (mEventControlSOFT != NULL && mEventCallbackSOFT != NULL);
+ }
+ LL_INFOS() << "ALC_SOFT_system_events " << (mHasSystemEventsExt ? "supported" : "NOT supported") << LL_ENDL;
+
+ if (mHasReopenExt && mHasSystemEventsExt)
+ {
+ mEventCallbackSOFT(&LLAudioEngine_OpenAL::onDeviceEventSOFT, this);
+ ALCenum events[] = { ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT };
+ mEventControlSOFT(1, events, ALC_TRUE);
+ LL_INFOS() << "Registered for ALC default-device-change notifications" << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS() << "Dynamic OS default output device following is not available; "
+ << "a viewer restart will be needed to pick up default device changes" << LL_ENDL;
+ }
+
return true;
}
@@ -130,9 +169,72 @@ void LLAudioEngine_OpenAL::allocateListener()
}
// virtual
+void LLAudioEngine_OpenAL::idle()
+{
+ LLAudioEngine::idle();
+
+ if (mDefaultDeviceChanged)
+ {
+ mDefaultDeviceChanged = false;
+ reopenOnDefaultDevice();
+ }
+}
+
+// static
+void ALC_APIENTRY LLAudioEngine_OpenAL::onDeviceEventSOFT(ALCenum eventType, ALCenum /*deviceType*/,
+ ALCdevice* /*device*/, ALCsizei /*length*/,
+ const ALCchar* /*message*/, void* userParam) noexcept
+{
+ // This may run on OpenAL's internal thread, so just flip the mDefaultDeviceChanged flag
+ // and handle device change in idle().
+ if (eventType == ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT)
+ {
+ LLAudioEngine_OpenAL* self = static_cast<LLAudioEngine_OpenAL*>(userParam);
+ if (self)
+ {
+ self->mDefaultDeviceChanged = true;
+ }
+ }
+}
+
+void LLAudioEngine_OpenAL::reopenOnDefaultDevice()
+{
+ if (!mHasReopenExt || !mReopenDeviceSOFT || !mALDevice)
+ {
+ return;
+ }
+
+ std::string old_name = ll_safe_string(alcGetString(mALDevice, ALC_DEVICE_SPECIFIER));
+ LL_INFOS() << "OS default audio device changed; reopening OpenAL device (was: "
+ << old_name << ")" << LL_ENDL;
+
+ // Clear any prior device error state so failures below report accurately.
+ (void)alcGetError(mALDevice);
+
+ if (mReopenDeviceSOFT(mALDevice, NULL, NULL))
+ {
+ LL_INFOS() << "OpenAL device reopened successfully, now on: "
+ << ll_safe_string(alcGetString(mALDevice, ALC_DEVICE_SPECIFIER)) << LL_ENDL;
+ }
+ else
+ {
+ ALCenum error = alcGetError(mALDevice);
+ LL_WARNS() << "alcReopenDeviceSOFT() failed: alc error " << error << LL_ENDL;
+ }
+}
+
+// virtual
void LLAudioEngine_OpenAL::shutdown()
{
LL_INFOS() << "About to LLAudioEngine::shutdown()" << LL_ENDL;
+
+ if (mHasSystemEventsExt && mEventControlSOFT && mEventCallbackSOFT)
+ {
+ ALCenum events[] = { ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT };
+ mEventControlSOFT(1, events, ALC_FALSE);
+ mEventCallbackSOFT(NULL, NULL);
+ }
+
LLAudioEngine::shutdown();
// If a subsequent error occurs while there is still an error recorded
diff --git a/indra/llaudio/llaudioengine_openal.h b/indra/llaudio/llaudioengine_openal.h
index 574bec416d..da1f376800 100644
--- a/indra/llaudio/llaudioengine_openal.h
+++ b/indra/llaudio/llaudioengine_openal.h
@@ -33,6 +33,7 @@
#include "llaudioengine.h"
#include "lllistener_openal.h"
#include "llwindgen.h"
+#include "llatomic.h"
class LLAudioEngine_OpenAL : public LLAudioEngine
{
@@ -46,6 +47,7 @@ class LLAudioEngine_OpenAL : public LLAudioEngine
virtual void allocateListener();
virtual void shutdown();
+ virtual void idle();
void setInternalGain(F32 gain);
@@ -68,6 +70,23 @@ class LLAudioEngine_OpenAL : public LLAudioEngine
static const int MAX_NUM_WIND_BUFFERS = 80;
static const float WIND_BUFFER_SIZE_SEC; // 1/20th sec
+
+ ALCdevice* mALDevice;
+
+ bool mHasReopenExt;
+ LPALCREOPENDEVICESOFT mReopenDeviceSOFT;
+
+ bool mHasSystemEventsExt;
+ LPALCEVENTCONTROLSOFT mEventControlSOFT;
+ LPALCEVENTCALLBACKSOFT mEventCallbackSOFT;
+
+ // Set by onDeviceEventSOFT(), consumed by idle()
+ LLAtomicBool mDefaultDeviceChanged;
+
+ static void ALC_APIENTRY onDeviceEventSOFT(ALCenum eventType, ALCenum deviceType,
+ ALCdevice* device, ALCsizei length,
+ const ALCchar* message, void* userParam) noexcept;
+ void reopenOnDefaultDevice();
};
class LLAudioChannelOpenAL : public LLAudioChannel
diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp
index 8efcd9dd29..6a66c06b62 100644
--- a/indra/llcharacter/llcharacter.cpp
+++ b/indra/llcharacter/llcharacter.cpp
@@ -307,6 +307,31 @@ bool LLCharacter::setVisualParamWeight(S32 index, F32 weight)
}
//-----------------------------------------------------------------------------
+// setVisualParamWeight()
+//-----------------------------------------------------------------------------
+bool LLCharacter::setVisualParamWeight(S32 index, S32 type, F32 weight)
+{
+ visual_param_index_map_t::iterator index_iter = mVisualParamIndexMap.find(index);
+ if (index_iter != mVisualParamIndexMap.end())
+ {
+ LLVisualParam* param = index_iter->second;
+ if (param->getWearableType() == type)
+ {
+ param->setWeight(weight);
+ return true;
+ }
+ else
+ {
+ // setVisualParamWeight at the moment is only used in writeToAvatar.
+ // The type is supposed to match since wearable is a subset of avatar by type.
+ llassert(false);
+ LL_WARNS() << "Visual param index " << index << " is not of type " << type << LL_ENDL;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
// getVisualParamWeight()
//-----------------------------------------------------------------------------
F32 LLCharacter::getVisualParamWeight(LLVisualParam *which_param)
diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h
index 079bcd132a..689e3f7d7c 100644
--- a/indra/llcharacter/llcharacter.h
+++ b/indra/llcharacter/llcharacter.h
@@ -195,6 +195,7 @@ public:
virtual bool setVisualParamWeight(const LLVisualParam *which_param, F32 weight);
virtual bool setVisualParamWeight(const char* param_name, F32 weight);
virtual bool setVisualParamWeight(S32 index, F32 weight);
+ virtual bool setVisualParamWeight(S32 index, S32 type, F32 weight);
// get visual param weight by param or name
F32 getVisualParamWeight(LLVisualParam *distortion);
diff --git a/indra/llcharacter/llvisualparam.h b/indra/llcharacter/llvisualparam.h
index 4ceb172005..edaecaf96e 100644
--- a/indra/llcharacter/llvisualparam.h
+++ b/indra/llcharacter/llvisualparam.h
@@ -119,6 +119,7 @@ public:
// Pure virtuals
//virtual bool parseData( LLXmlTreeNode *node ) = 0;
virtual void apply( ESex avatar_sex ) = 0;
+ virtual S32 getWearableType() const = 0;
// Default functions
virtual void setWeight(F32 weight);
virtual void setAnimationTarget( F32 target_value);
diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp
index 9ea7a34bac..e1d80c5dea 100644
--- a/indra/llcommon/llapr.cpp
+++ b/indra/llcommon/llapr.cpp
@@ -230,11 +230,35 @@ bool LLVolatileAPRPool::isFull()
bool _ll_apr_warn_status(apr_status_t status, const char* file, int line)
{
if(APR_SUCCESS == status) return false;
-
- char buf[MAX_STRING]; /* Flawfinder: ignore */
+ char buf[MAX_STRING];
apr_strerror(status, buf, sizeof(buf));
- LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << LL_ENDL;
+#ifdef LL_WINDOWS
+ // On Windows, APR error strings may be in the system's ANSI code page (e.g., Cyrillic)
+ // Convert to UTF-8 for proper logging
+ std::string error_msg = buf;
+ int wlen = MultiByteToWideChar(CP_ACP, 0, buf, -1, nullptr, 0);
+ if (wlen > 0)
+ {
+ std::wstring wbuf(wlen, L'\0');
+ MultiByteToWideChar(CP_ACP, 0, buf, -1, &wbuf[0], wlen);
+
+ int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
+ if (utf8len > 0)
+ {
+ std::string utf8buf(utf8len, '\0');
+ WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &utf8buf[0], utf8len, nullptr, nullptr);
+ error_msg = utf8buf.c_str(); // Remove null terminator
+ }
+ LL_WARNS("APR") << "APR: " << file << ":" << line << " " << error_msg << " (0x" << std::hex << status << std::dec << ")" << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << " (0x" << std::hex << status << std::dec << ")" << LL_ENDL;
+ }
+#else
+ LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << " (0x" << std::hex << status << std::dec << ")" << LL_ENDL;
+#endif
return true;
}
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index 9e95d9c85f..ae7c50e9a3 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -252,6 +252,7 @@ void LLCoros::setStackSize(S32 stacksize)
void LLCoros::printActiveCoroutines(const std::string& when)
{
+ LL_PROFILE_ZONE_SCOPED;
LL_INFOS("LLCoros") << "Number of active coroutines " << when
<< ": " << CoroData::instanceCount() << LL_ENDL;
if (CoroData::instanceCount() > 0)
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 343cc47e4c..a06c299c8d 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -309,6 +309,7 @@ namespace LLError
ERROR_OTHER = 0,
ERROR_BAD_ALLOC = 1,
ERROR_MISSING_FILES = 2,
+ ERROR_INIT_FAILED = 3,
} eLastExecEvent;
// tittle, message and error code to include in error marker file
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 90ce71a6a5..a71589a2bf 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -30,30 +30,129 @@
#include "llsdserialize.h"
#include "llsingleton.h"
#include "llstring.h"
-#include "stringize.h"
-#include "llapr.h"
-#include "apr_signal.h"
#include "llevents.h"
#include "llexception.h"
+#include "stringize.h"
-#include <boost/bind.hpp>
+#include <boost/process/v2/process.hpp>
+#include <boost/process/v2/environment.hpp>
+#include <boost/process/v2/start_dir.hpp>
+#include <boost/process/v2/stdio.hpp>
+#include <boost/asio.hpp>
#include <boost/asio/streambuf.hpp>
#include <boost/asio/buffers_iterator.hpp>
+#include <boost/filesystem/path.hpp>
#include <iostream>
#include <stdexcept>
#include <limits>
#include <algorithm>
+#include <chrono>
+#include <cstring>
+#include <errno.h>
+#include <thread>
#include <vector>
#include <typeinfo>
+#include <signal.h>
#include <utility>
+#if !LL_WINDOWS
+ // not necessarily available on random SDL platforms
+ // for waitpid()
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
+
+#if LL_WINDOWS
+#include <windows.h>
+#include "llwin32headers.h"
+#include <mutex>
+
+namespace {
+ // Global job object that will kill all child processes when parent terminates
+ HANDLE g_jobObject = NULL;
+ bool g_jobObjectInitialized = false;
+ std::mutex g_jobObjectMutex;
+
+ void InitializeJobObject()
+ {
+ if (g_jobObjectInitialized)
+ return;
+ std::lock_guard<std::mutex> lock(g_jobObjectMutex);
+ if (g_jobObjectInitialized)
+ return;
+
+ g_jobObjectInitialized = true;
+
+ // Create a job object
+ g_jobObject = ::CreateJobObjectW(NULL, NULL);
+ if (g_jobObject == NULL)
+ {
+ LL_WARNS("LLProcess") << "Failed to create job object: " << ::GetLastError() << LL_ENDL;
+ return;
+ }
+
+ // Configure the job to kill all processes when the last handle closes
+ // (i.e., when the parent process exits)
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
+ jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ // Windows 8+ supports nested jobs (for VS studio)
+ jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+
+ if (!::SetInformationJobObject(g_jobObject, JobObjectExtendedLimitInformation,
+ &jeli, sizeof(jeli)))
+ {
+ LL_WARNS("LLProcess") << "Failed to set job object limits: " << ::GetLastError() << LL_ENDL;
+ ::CloseHandle(g_jobObject);
+ g_jobObject = NULL;
+ return;
+ }
+
+ LL_INFOS("LLProcess") << "Job object created - child processes will terminate with parent" << LL_ENDL;
+ }
+
+ void AssignProcessToJob(HANDLE hProcess, const std::string& desc)
+ {
+ if (!g_jobObjectInitialized)
+ InitializeJobObject();
+
+ if (g_jobObject != NULL)
+ {
+ if (!::AssignProcessToJobObject(g_jobObject, hProcess))
+ {
+ DWORD error = ::GetLastError();
+ // ERROR_ACCESS_DENIED (5) means the process is already in a job
+ // This can happen if the parent viewer is itself in a job
+ if (error == ERROR_ACCESS_DENIED)
+ {
+ LL_WARNS("LLProcess") << "Autokill requested but process " << desc
+ << " is already in a job object (ERROR_ACCESS_DENIED)"
+ << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("LLProcess") << "Failed to assign process " << desc
+ << " to job object: error " << error << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("LLProcess") << "Process " << desc << " assigned to job object" << LL_ENDL;
+ }
+ }
+ }
+}
+#endif
+
+namespace bp = boost::process::v2;
+namespace asio = boost::asio;
+
/*****************************************************************************
* Helpers
*****************************************************************************/
+
static const char* whichfile_[] = { "stdin", "stdout", "stderr" };
-static std::string empty;
-static LLProcess::Status interpret_status(int status);
-static std::string getDesc(const LLProcess::Params& params);
static std::string whichfile(LLProcess::FILESLOT index)
{
@@ -62,799 +161,836 @@ static std::string whichfile(LLProcess::FILESLOT index)
return STRINGIZE("file slot " << index);
}
-/**
- * Ref-counted "mainloop" listener. As long as there are still outstanding
- * LLProcess objects, keep listening on "mainloop" so we can keep polling APR
- * for process status.
- */
-class LLProcessListener
+std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params)
{
- LOG_CLASS(LLProcessListener);
-public:
- LLProcessListener():
- mCount(0)
- {}
-
- void addPoll(const LLProcess&)
- {
- // Unconditionally increment mCount. If it was zero before
- // incrementing, listen on "mainloop".
- if (mCount++ == 0)
- {
- LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL;
- mConnection = LLEventPumps::instance().obtain("mainloop")
- .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1));
- }
- }
-
- void dropPoll(const LLProcess&)
+ if (params.cwd.isProvided())
{
- // Unconditionally decrement mCount. If it's zero after decrementing,
- // stop listening on "mainloop".
- if (--mCount == 0)
- {
- LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL;
- mConnection.disconnect();
- }
+ out << "cd " << LLStringUtil::quote(params.cwd) << ": ";
}
-
-private:
- /// called once per frame by the "mainloop" LLEventPump
- bool tick(const LLSD&)
+ out << LLStringUtil::quote(params.executable);
+ for (const std::string& arg : params.args)
{
- // Tell APR to sense whether each registered LLProcess is still
- // running and call handle_status() appropriately. We should be able
- // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at
- // least in APR 1.4.2, testing suggests that even with APR_NOWAIT,
- // apr_proc_wait() blocks the caller. We can't have that in the
- // viewer. Hence the callback rigmarole. (Once we update APR, it's
- // probably worth testing again.) Also -- although there's an
- // apr_proc_other_child_refresh() call, i.e. get that information for
- // one specific child, it accepts an 'apr_other_child_rec_t*' that's
- // mentioned NOWHERE else in the documentation or header files! I
- // would use the specific call in LLProcess::getStatus() if I knew
- // how. As it is, each call to apr_proc_other_child_refresh_all() will
- // call callbacks for ALL still-running child processes. That's why we
- // centralize such calls, using "mainloop" to ensure it happens once
- // per frame, and refcounting running LLProcess objects to remain
- // registered only while needed.
- LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL;
- apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING);
- return false;
+ out << ' ' << LLStringUtil::quote(arg);
}
-
- /// If this object is destroyed before mCount goes to zero, stop
- /// listening on "mainloop" anyway.
- LLTempBoundListener mConnection;
- unsigned mCount;
-};
-static LLProcessListener sProcessListener;
-
+ return out;
+}
/*****************************************************************************
-* WritePipe and ReadPipe
+* Helper classes for pipe I/O
*****************************************************************************/
-LLProcess::BasePipe::~BasePipe() {}
-const LLProcess::BasePipe::size_type
- // use funky syntax to call max() to avoid blighted max() macros
- LLProcess::BasePipe::npos((std::numeric_limits<LLProcess::BasePipe::size_type>::max)());
-class WritePipeImpl: public LLProcess::WritePipe
+class WritePipeImpl : public LLProcess::WritePipe
{
LOG_CLASS(WritePipeImpl);
public:
- WritePipeImpl(const std::string& desc, apr_file_t* pipe):
+ WritePipeImpl(const std::string& desc,
+ std::shared_ptr<asio::writable_pipe> pipe) :
mDesc(desc),
mPipe(pipe),
- // Essential to initialize our std::ostream with our special streambuf!
- mStream(&mStreambuf)
- {
- mConnection = LLEventPumps::instance().obtain("mainloop")
- .listen(LLEventPump::inventName("WritePipe"),
- boost::bind(&WritePipeImpl::tick, this, _1));
+ mStream(&mStreambuf),
+ mWritePending(false)
+ {}
-#if ! LL_WINDOWS
- // We can't count on every child process reading everything we try to
- // write to it. And if the child terminates with WritePipe data still
- // pending, unless we explicitly suppress it, Posix will hit us with
- // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means
- // APR gets the correct errno, passes it back to us, we log it, etc.
- signal(SIGPIPE, SIG_IGN);
-#endif
+ virtual ~WritePipeImpl() = default;
+
+ virtual std::ostream& get_ostream() override { return mStream; }
+
+ virtual size_type size() const override
+ {
+ return mStreambuf.size();
}
- virtual std::ostream& get_ostream() { return mStream; }
- virtual size_type size() const { return mStreambuf.size(); }
+ // Called from LLProcess::tick() to initiate writing buffered data.
+ // Self-chains: each completed write immediately starts the next one if
+ // more data is waiting, so large transfers don't stall between ticks.
+ void tick() override
+ {
+ startAsyncWrite();
+ }
- bool tick(const LLSD&)
+private:
+ void startAsyncWrite()
{
- typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence;
- // If there's anything to send, try to send it.
- std::size_t total(mStreambuf.size()), consumed(0);
- if (total)
- {
- const_buffer_sequence bufs = mStreambuf.data();
- // In general, our streambuf might contain a number of different
- // physical buffers; iterate over those.
- bool keepwriting = true;
- for (auto bufi(boost::asio::buffer_sequence_begin(bufs)), bufend(boost::asio::buffer_sequence_end(bufs));
- bufi != bufend && keepwriting; ++bufi)
- {
- // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents
- // Although apr_file_write() accepts const void*, we
- // manipulate const char* so we can increment the pointer.
- const char* remainptr = static_cast<const char*>(bufi->data());
- std::size_t remainlen = bufi->size();
- while (remainlen)
- {
- // Tackle the current buffer in discrete chunks. On
- // Windows, we've observed strange failures when trying to
- // write big lengths (~1 MB) in a single operation. Even a
- // 32K chunk seems too large. At some point along the way
- // apr_file_write() returns 11 (Resource temporarily
- // unavailable, i.e. EAGAIN) and says it wrote 0 bytes --
- // even though it did write the chunk! Our next write
- // attempt retries with the same chunk, resulting in the
- // chunk being duplicated at the child end. Using smaller
- // chunks is empirically more reliable.
- std::size_t towrite((std::min)(remainlen, std::size_t(4*1024)));
- apr_size_t written(towrite);
- apr_status_t err = apr_file_write(mPipe, remainptr, &written);
- // EAGAIN is exactly what we want from a nonblocking pipe.
- // Rather than waiting for data, it should return immediately.
- if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err)))
- {
- LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc
- << " got " << err << ":" << LL_ENDL;
- ll_apr_warn_status(err);
- }
+ if (mWritePending || !mPipe || !mPipe->is_open() || mStreambuf.size() == 0)
+ return;
- // 'written' is modified to reflect the number of bytes actually
- // written. Make sure we consume those later. (Don't consume them
- // now, that would invalidate the buffer iterator sequence!)
- consumed += written;
- // don't forget to advance to next chunk of current buffer
- remainptr += written;
- remainlen -= written;
+ mWritePending = true;
- char msgbuf[512];
- LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite
- << " bytes to " << mDesc
- << " (original " << total << "),"
- << " code " << err << ": "
- << apr_strerror(err, msgbuf, sizeof(msgbuf))
- << LL_ENDL;
+ // Snapshot the number of bytes currently buffered so the async_write
+ // operates on a fixed-size, stable view. Any data written to
+ // get_ostream() while this write is in flight lands in the streambuf's
+ // put area and is excluded from the current operation; it will be sent
+ // on the next tick(). Without this snapshot, a concurrent write to
+ // get_ostream() could reallocate/invalidate the buffer sequence.
+ std::size_t writeSize = mStreambuf.size();
- // The parent end of this pipe is nonblocking. If we weren't able
- // to write everything we wanted, don't keep banging on it -- that
- // won't change until the child reads some. Wait for next tick().
- if (written < towrite)
- {
- keepwriting = false; // break outer loop over buffers too
- break;
- }
- } // next chunk of current buffer
- } // next buffer
- // In all, we managed to write 'consumed' bytes. Remove them from the
- // streambuf so we don't keep trying to send them. This could be
- // anywhere from 0 up to mStreambuf.size(); anything we haven't yet
- // sent, we'll try again later.
- mStreambuf.consume(consumed);
- }
+ // Write all buffered data asynchronously. Do NOT self-chain in the
+ // completion handler: sending the next buffer immediately can cause
+ // the child to respond within the same tick(), which posts a read
+ // event before the test listener has a chance to disconnect -- that
+ // was the root cause of test 18 "more than 3 events" and test 9
+ // "many small messages" failures. tick() calls mWritePipe->tick() on
+ // every mainloop frame, so any newly-queued data will be sent then.
+ asio::async_write(*mPipe, asio::buffer(mStreambuf.data(), writeSize),
+ [this](const boost::system::error_code& ec, std::size_t bytes_transferred)
+ {
+ mWritePending = false;
- return false;
+ if (!ec)
+ {
+ mStreambuf.consume(bytes_transferred);
+ LL_DEBUGS("LLProcess") << "Wrote " << bytes_transferred
+ << " bytes to " << mDesc << LL_ENDL;
+ }
+ else if (ec != asio::error::operation_aborted)
+ {
+ LL_WARNS("LLProcess") << "Write error on " << mDesc
+ << ": " << ec.message() << LL_ENDL;
+ }
+ });
}
-private:
std::string mDesc;
- apr_file_t* mPipe;
- LLTempBoundListener mConnection;
- boost::asio::streambuf mStreambuf;
+ std::shared_ptr<asio::writable_pipe> mPipe;
+ asio::streambuf mStreambuf;
std::ostream mStream;
+ bool mWritePending;
};
-class ReadPipeImpl: public LLProcess::ReadPipe
+class ReadPipeImpl : public LLProcess::ReadPipe
{
LOG_CLASS(ReadPipeImpl);
public:
- ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index):
+ ReadPipeImpl(const std::string& desc,
+ std::shared_ptr<asio::readable_pipe> pipe,
+ LLProcess::FILESLOT slot) :
mDesc(desc),
mPipe(pipe),
- mIndex(index),
- // Essential to initialize our std::istream with our special streambuf!
+ mSlot(slot),
mStream(&mStreambuf),
- mPump("ReadPipe", true), // tweak name as needed to avoid collisions
+ mPump("ReadPipe", true),
mLimit(0),
mEOF(false)
{
- mConnection = LLEventPumps::instance().obtain("mainloop")
- .listen(LLEventPump::inventName("ReadPipe"),
- boost::bind(&ReadPipeImpl::tick, this, _1));
+ // Start async read
+ startAsyncRead();
}
- ~ReadPipeImpl()
+ virtual ~ReadPipeImpl()
{
- if (mConnection.connected())
+ if (mPipe && mPipe->is_open())
{
- mConnection.disconnect();
+ boost::system::error_code ec;
+ mPipe->close(ec);
}
}
- // Much of the implementation is simply connecting the abstract virtual
- // methods with implementation data concealed from the base class.
- virtual std::istream& get_istream() { return mStream; }
- virtual std::string getline() { return LLProcess::getline(mStream); }
- virtual LLEventPump& getPump() { return mPump; }
- virtual void setLimit(size_type limit) { mLimit = limit; }
- virtual size_type getLimit() const { return mLimit; }
- virtual size_type size() const { return mStreambuf.size(); }
+ virtual std::istream& get_istream() override { return mStream; }
- virtual std::string read(size_type len)
+ virtual std::string getline() override
{
- // Read specified number of bytes into a buffer.
- size_type readlen((std::min)(size(), len));
- // Formally, &buffer[0] is invalid for a vector of size() 0. Exit
- // early in that situation.
- if (! readlen)
+ return LLProcess::getline(mStream);
+ }
+
+ virtual LLEventPump& getPump() override { return mPump; }
+
+ virtual void setLimit(size_type limit) override { mLimit = limit; }
+
+ virtual size_type getLimit() const override { return mLimit; }
+
+ virtual bool atEOF() const override { return mEOF; }
+
+ virtual size_type size() const override { return mStreambuf.size(); }
+
+ virtual std::string read(size_type len) override
+ {
+ size_type readlen = (std::min)(size(), len);
+ if (!readlen)
return "";
- // Make a buffer big enough.
+
std::vector<char> buffer(readlen);
mStream.read(&buffer[0], readlen);
- // Since we've already clamped 'readlen', we can think of no reason
- // why mStream.read() should read fewer than 'readlen' bytes.
- // Nonetheless, use the actual retrieved length.
return std::string(&buffer[0], mStream.gcount());
}
- virtual std::string peek(size_type offset=0, size_type len=npos) const
+ virtual std::string peek(size_type offset = 0, size_type len = npos) const override
{
- // Constrain caller's offset and len to overlap actual buffer content.
std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset));
- size_type want_end = (len == npos)? npos : (real_offset + len);
- std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end));
- boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
- return std::string(boost::asio::buffers_begin(cbufs) + real_offset,
- boost::asio::buffers_begin(cbufs) + real_end);
+ size_type want_end = (len == npos) ? npos : (real_offset + len);
+ std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end));
+
+ auto cbufs = mStreambuf.data();
+ return std::string(asio::buffers_begin(cbufs) + real_offset,
+ asio::buffers_begin(cbufs) + real_end);
}
- virtual size_type find(const std::string& seek, size_type offset=0) const
+ virtual size_type find(const std::string& seek, size_type offset = 0) const override
{
- // If we're passing a string of length 1, use find(char), which can
- // use an O(n) std::find() rather than the O(n^2) std::search().
if (seek.length() == 1)
- {
return find(seek[0], offset);
- }
- // If offset is beyond the whole buffer, can't even construct a valid
- // iterator range; can't possibly find the string we seek.
if (offset > mStreambuf.size())
- {
return npos;
- }
- boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
- boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type>
- begin(boost::asio::buffers_begin(cbufs)),
- end (boost::asio::buffers_end(cbufs)),
- found(std::search(begin + offset, end, seek.begin(), seek.end()));
- return (found == end)? npos : (found - begin);
+ auto cbufs = mStreambuf.data();
+ auto begin = asio::buffers_begin(cbufs);
+ auto end = asio::buffers_end(cbufs);
+ auto found = std::search(begin + offset, end, seek.begin(), seek.end());
+ return (found == end) ? npos : (found - begin);
}
- virtual size_type find(char seek, size_type offset=0) const
+ virtual size_type find(char seek, size_type offset = 0) const override
{
- // If offset is beyond the whole buffer, can't even construct a valid
- // iterator range; can't possibly find the char we seek.
if (offset > mStreambuf.size())
- {
return npos;
- }
- boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
- boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type>
- begin(boost::asio::buffers_begin(cbufs)),
- end (boost::asio::buffers_end(cbufs)),
- found(std::find(begin + offset, end, seek));
- return (found == end)? npos : (found - begin);
+ auto cbufs = mStreambuf.data();
+ auto begin = asio::buffers_begin(cbufs);
+ auto end = asio::buffers_end(cbufs);
+ auto found = std::find(begin + offset, end, seek);
+ return (found == end) ? npos : (found - begin);
}
- bool tick(const LLSD&)
+private:
+ void startAsyncRead()
{
- // Once we've hit EOF, skip all the rest of this.
- if (mEOF)
- return false;
+ if (!mPipe || !mPipe->is_open() || mEOF)
+ return;
+
+ // Always read data regardless of mLimit to prevent INTEGRATION_TEST_llleap
+ // deadlock: stopping reads fills the OS pipe buffer and blocks the child,
+ // which prevents large (~1 MB) messages from being fully received.
+ // mLimit only controls how many bytes appear in event notifications.
+ auto bufs = mStreambuf.prepare(4096);
- typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence;
- // Try, every time, to read into our streambuf. In fact, we have no
- // idea how much data the child might be trying to send: keep trying
- // until we're convinced we've temporarily exhausted the pipe.
- enum PipeState { RETRY, EXHAUSTED, CLOSED };
- PipeState state = RETRY;
- std::size_t committed(0);
- do
+ mPipe->async_read_some(bufs,
+ [this](const boost::system::error_code& ec, std::size_t bytes_transferred)
{
- // attempt to read an arbitrary size
- mutable_buffer_sequence bufs = mStreambuf.prepare(4096);
- // In general, the mutable_buffer_sequence returned by prepare() might
- // contain a number of different physical buffers; iterate over those.
- std::size_t tocommit(0);
- for (auto bufi(boost::asio::buffer_sequence_begin(bufs)), bufend(boost::asio::buffer_sequence_end(bufs));
- bufi != bufend; ++bufi)
+ if (!ec)
{
- // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents
- std::size_t toread(bufi->size());
- apr_size_t gotten(toread);
- apr_status_t err = apr_file_read(mPipe,
- bufi->data(),
- &gotten);
- // EAGAIN is exactly what we want from a nonblocking pipe.
- // Rather than waiting for data, it should return immediately.
- if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err)))
- {
- // Handle EOF specially: it's part of normal-case processing.
- if (err == APR_EOF)
- {
- LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL;
- }
- else
- {
- LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc
- << " got " << err << ":" << LL_ENDL;
- ll_apr_warn_status(err);
- }
- // Either way, though, we won't need any more tick() calls.
- mConnection.disconnect();
- // Ignore any subsequent calls we might get anyway.
- mEOF = true;
- state = CLOSED; // also break outer retry loop
- break;
- }
+ mStreambuf.commit(bytes_transferred);
+ LL_DEBUGS("LLProcess") << "Read " << bytes_transferred
+ << " bytes from " << mDesc << LL_ENDL;
- // 'gotten' was modified to reflect the number of bytes actually
- // received. Make sure we commit those later. (Don't commit them
- // now, that would invalidate the buffer iterator sequence!)
- tocommit += gotten;
- LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread
- << " bytes from " << mDesc << LL_ENDL;
+ // Restore original 7-field event contract:
+ // data, len, slot, name, desc, eof, exhst
+ // A successful async read corresponds to the original EXHAUSTED
+ // state: we got data, pipe is not yet closed.
+ size_type data_len = (std::min)(mStreambuf.size(), mLimit);
+ LLSD event;
+ event["data"] = peek(0, data_len);
+ event["len"] = LLSD::Integer(mStreambuf.size());
+ event["slot"] = LLSD::Integer(mSlot);
+ event["name"] = whichfile(mSlot);
+ event["desc"] = mDesc;
+ event["eof"] = false;
+ event["exhst"] = true;
- // The parent end of this pipe is nonblocking. If we weren't even
- // able to fill this buffer, don't loop to try to fill the next --
- // that won't change until the child writes more. Wait for next
- // tick().
- if (gotten < toread)
+ // Arm the next read before posting: LLEventStream::post() is synchronous
+ // and listeners may destroy this object.
+ startAsyncRead();
+ mPump.post(event);
+ }
+ else if (ec == asio::error::eof
+#if LL_WINDOWS
+ // On Windows anonymous pipes, the write end closing
+ // delivers ERROR_BROKEN_PIPE (109), not asio::error::eof.
+ || ec.value() == ERROR_BROKEN_PIPE
+#endif
+ )
+ {
+ if (bytes_transferred > 0)
{
- // break outer retry loop too
- state = EXHAUSTED;
- break;
+ mStreambuf.commit(bytes_transferred);
}
- }
-
- // Don't forget to "commit" the data!
- mStreambuf.commit(tocommit);
- committed += tocommit;
- // state is changed from RETRY when we can't fill any one buffer
- // of the mutable_buffer_sequence established by the current
- // prepare() call -- whether due to error or not enough bytes.
- // That is, if state is still RETRY, we've filled every physical
- // buffer in the mutable_buffer_sequence. In that case, for all we
- // know, the child might have still more data pending -- go for it!
- } while (state == RETRY);
+ mEOF = true;
+ LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL;
- // Once we recognize that the pipe is closed, make one more call to
- // listener. The listener might be waiting for a particular substring
- // to arrive, or a particular length of data or something. The event
- // with "eof" == true announces that nothing further will arrive, so
- // use it or lose it.
- if (committed || state == CLOSED)
- {
- // If we actually received new data, publish it on our LLEventPump
- // as advertised. Constrain it by mLimit. But show listener the
- // actual accumulated buffer size, regardless of mLimit.
- size_type datasize((std::min)(mLimit, size_type(mStreambuf.size())));
- mPump.post(LLSDMap
- ("data", peek(0, datasize))
- ("len", LLSD::Integer(mStreambuf.size()))
- ("slot", LLSD::Integer(mIndex))
- ("name", whichfile(mIndex))
- ("desc", mDesc)
- ("eof", state == CLOSED)
- ("exhst", state == EXHAUSTED));
- }
+ // Match the original behavior: pack all 7 fields into the
+ // single eof event so consumers can "use it or lose it" --
+ // this is the last chance to see any remaining buffered data.
+ // EOF corresponds to the original CLOSED state.
+ size_type data_len = (std::min)(mStreambuf.size(), mLimit);
+ LLSD eof_event;
+ eof_event["data"] = peek(0, data_len);
+ eof_event["len"] = LLSD::Integer(mStreambuf.size());
+ eof_event["slot"] = LLSD::Integer(mSlot);
+ eof_event["name"] = whichfile(mSlot);
+ eof_event["desc"] = mDesc;
+ eof_event["eof"] = true;
+ eof_event["exhst"] = false;
- return false;
+ mPump.post(eof_event);
+ }
+ else if (ec != asio::error::operation_aborted)
+ {
+ LL_WARNS("LLProcess") << "Read error on " << mDesc
+ << ": " << ec.message() << LL_ENDL;
+ }
+ });
}
-private:
std::string mDesc;
- apr_file_t* mPipe;
- LLProcess::FILESLOT mIndex;
- LLTempBoundListener mConnection;
- boost::asio::streambuf mStreambuf;
+ std::shared_ptr<asio::readable_pipe> mPipe;
+ LLProcess::FILESLOT mSlot;
+ mutable asio::streambuf mStreambuf;
std::istream mStream;
- LLEventStream mPump;
+ LLEventStream mPump; // pump specific to this pipe
size_type mLimit;
bool mEOF;
};
/*****************************************************************************
-* LLProcess itself
+* LLProcess implementation
*****************************************************************************/
-/// Need an exception to avoid constructing an invalid LLProcess object, but
-/// internal use only
-struct LLProcessError: public LLException
+
+const LLProcess::BasePipe::size_type LLProcess::BasePipe::npos =
+static_cast<LLProcess::BasePipe::size_type>(-1);
+
+LLProcess::LLProcess(const Params& params) :
+ mStatus(),
+ mDesc(params.desc.isProvided() ? params.desc() : basename(params.executable())),
+ mPostend(params.postend.isProvided() ? params.postend() : ""),
+ mAutokill(params.autokill),
+ mAttached(params.attached.isProvided() ? params.attached() : bool(params.autokill)),
+ mKillCalled(false)
{
- LLProcessError(const std::string& msg): LLException(msg) {}
-};
+ launch(params);
+}
+
+LLProcess::~LLProcess()
+{
+ if (mChild && mStatus.mState == RUNNING)
+ {
+ if (mAttached && mAutokill && !mKillCalled)
+ {
+ LL_INFOS("LLProcess") << "Terminating child process " << mDesc << LL_ENDL;
+ boost::system::error_code ec;
+ mChild->terminate(ec);
+#if !LL_WINDOWS
+ // On POSIX, terminate() sends SIGTERM which allows graceful shutdown.
+ // Poll with waitpid(WNOHANG) rather than mChild->running() to avoid
+ // competing with tick()'s own waitpid call.
+ pid_t pid = mChild->id();
+ for (int i = 0; i < 30; ++i)
+ {
+ int child_status = 0;
+ pid_t w = ::waitpid(pid, &child_status, WNOHANG);
+ if (w == pid || (w == -1 && errno == ECHILD))
+ break; // child exited or was already reaped
+ if (w == -1 && errno != EINTR)
+ {
+ LL_WARNS("LLProcess") << "waitpid(" << pid << ") failed while terminating "
+ << mDesc << ": " << strerror(errno) << LL_ENDL;
+ break;
+ }
+ // Sleep before next poll: applies to both EINTR and
+ // still-running (w == 0) cases.
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+
+ // Force kill if still running
+ {
+ int child_status;
+ if (::waitpid(pid, &child_status, WNOHANG) == 0)
+ {
+ LL_WARNS("LLProcess") << "Force killing " << mDesc << LL_ENDL;
+ (void)::kill(pid, SIGKILL);
+ }
+ }
+#else
+ // On Windows, terminate() already does an immediate hard kill via TerminateProcess()
+ // Only wait if terminate succeeded (process was running)
+
+ if (!ec)
+ {
+ DWORD exit_code = 0;
+ bool still_running = (::GetExitCodeProcess(mChild->native_handle(), &exit_code)
+ && exit_code == STILL_ACTIVE);
+ if (still_running)
+ {
+ // We are likely on the main thread. Don't wait long!
+ // Maybe shouldn't wait at all.
+ WaitForSingleObject(mChild->native_handle(), 10);
+ }
+ }
+ else
+ {
+ LL_WARNS("LLProcess") << "Process " << mDesc
+ << " terminate failed with error " << ec.value()
+ << " (" << ec.message() << "), skipping wait" << LL_ENDL;
+ }
+#endif
+ }
+ else if (!mKillCalled)
+ {
+ LL_INFOS("LLProcess") << "Not terminating " << mDesc
+ << " (attached=" << mAttached
+ << ", autokill=" << mAutokill << ")" << LL_ENDL;
+ // Detach the bp::process so its destructor does not send SIGKILL
+ // to the still-running process (boost::process v2 terminates the
+ // child in bp::process::~process() if the handle is still valid).
+ mChild->detach();
+ }
+ }
+
+ if (mMainloopConnection.connected())
+ {
+ mMainloopConnection.disconnect();
+ }
+}
+
+//static
LLProcessPtr LLProcess::create(const LLSDOrParams& params)
{
try
{
- return LLProcessPtr(new LLProcess(params));
+ // Construct and then register the mainloop connection separately.
+ // connectMainloop() calls shared_from_this(), which requires the
+ // shared_ptr control block to be fully initialized — this is only true
+ // after make_shared returns, not during the constructor.
+ LLProcessPtr ptr = std::make_shared<LLProcess>(params);
+ ptr->connectMainloop();
+ return ptr;
}
- catch (const LLProcessError& e)
+ catch (const std::exception& e)
{
- LL_WARNS("LLProcess") << e.what() << LL_ENDL;
+ LL_WARNS("LLProcess") << "Failed to create process: " << e.what() << LL_ENDL;
- // If caller is requesting an event on process termination, send one
- // indicating bad launch. This may prevent someone waiting forever for
- // a termination post that can't arrive because the child never
- // started.
- if (params.postend.isProvided())
+ // Even on failure, fire the postend event if requested, so callers
+ // that listen for it can detect the launch failure.
+ if (params.postend.isProvided() && !params.postend().empty())
{
- LLEventPumps::instance().obtain(params.postend)
- .post(LLSDMap
- // no "id"
- ("desc", getDesc(params))
- ("state", LLProcess::UNSTARTED)
- // no "data"
- ("string", e.what())
- );
+ std::string desc = params.desc.isProvided() ? params.desc() :
+ (params.executable.isProvided() ? LLProcess::basename(params.executable()) : "");
+ LLSD event;
+ event["desc"] = desc;
+ event["state"] = LLProcess::UNSTARTED;
+ event["string"] = e.what();
+ LLEventPumps::instance().obtain(params.postend()).post(event);
}
return LLProcessPtr();
}
}
-/// Call an apr function returning apr_status_t. On failure, log warning and
-/// throw LLProcessError mentioning the function call that produced that
-/// result.
-#define chkapr(func) \
- if (ll_apr_warn_status(func)) \
- throw LLProcessError(#func " failed")
-
-LLProcess::LLProcess(const LLSDOrParams& params):
- mAutokill(params.autokill),
- // Because 'autokill' originally meant both 'autokill' and 'attached', to
- // preserve existing semantics, we promise that mAttached defaults to the
- // same setting as mAutokill.
- mAttached(params.attached.isProvided()? params.attached : params.autokill),
- mPool(NULL)
+void LLProcess::launch(const LLSDOrParams& params)
{
- mPipes.resize(NSLOTS);
-
- if (! params.validateBlock(true))
+ if (!params.validateBlock(true))
{
- LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n"
- << LLSDNotationStreamer(params))));
+ LL_WARNS("LLProcess") << "Failed parameter validation " << LLSDNotationStreamer(params) << LL_ENDL;
+ throw std::runtime_error("not launched: failed parameter validation\n");
}
- mPostend = params.postend;
-
- apr_pool_create(&mPool, gAPRPoolp);
- if (!mPool)
+ // Validate FileParam types before attempting to launch
+ int file_idx = 0;
+ for (const auto& fparam : params.files)
{
- LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool")));
- }
+ if (fparam.type.isProvided())
+ {
+ const std::string& type = fparam.type();
+ // Only "" (inherit) and "pipe" are supported
+ if (!type.empty() && type != "pipe")
+ {
+ std::string slotname;
+ switch (file_idx)
+ {
+ case STDIN: slotname = "stdin"; break;
+ case STDOUT: slotname = "stdout"; break;
+ case STDERR: slotname = "stderr"; break;
+ default: slotname = STRINGIZE("file slot " << file_idx); break;
+ }
- apr_procattr_t *procattr = NULL;
- chkapr(apr_procattr_create(&procattr, mPool));
+ LL_WARNS("LLProcess") << "For " << params.executable()
+ << ": unsupported FileParam for " << slotname
+ << ": type='" << type << "'";
- // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to
- // constrain the set of handles passed to the child process. Before we
- // changed to APR, the Windows implementation of LLProcessLauncher called
- // CreateProcess(bInheritHandles=false), meaning to pass NO open handles
- // to the child process. Now that we support pipes, though, we must allow
- // apr_proc_create() to pass bInheritHandles=true. But without taking
- // special pains, that causes trouble in a number of ways, due to the fact
- // that the viewer is constantly opening and closing files -- most of
- // which CreateProcess() passes to every child process!
-#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET)
- // Our special preprocessor symbol isn't even defined -- wrong APR
- LL_WARNS("LLProcess") << "This version of APR lacks Linden "
- << "apr_procattr_constrain_handle_set() extension" << LL_ENDL;
-#else
- chkapr(apr_procattr_constrain_handle_set(procattr, 1));
-#endif
+ if (fparam.name.isProvided())
+ {
+ LL_CONT << ", name='" << fparam.name() << "'";
+ }
- // For which of stdin, stdout, stderr should we create a pipe to the
- // child? In the viewer, there are only a couple viable
- // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx
- // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's
- // blocking on the child end but nonblocking at the viewer end
- // (APR_CHILD_BLOCK).
- // Other major options could include explicitly creating a single APR pipe
- // and passing it as both stdout and stderr (apr_procattr_child_out_set(),
- // apr_procattr_child_err_set()), or accepting a filename, opening it and
- // passing that apr_file_t (simple <, >, 2> redirect emulation).
- std::vector<apr_int32_t> select;
- for (const FileParam& fparam : params.files)
- {
- // Every iteration, we're going to append an item to 'select'. At the
- // top of the loop, its size() is, in effect, an index. Use that to
- // pick a string description for messages.
- std::string which(whichfile(FILESLOT(select.size())));
- if (fparam.type().empty()) // inherit our file descriptor
- {
- select.push_back(APR_NO_PIPE);
- }
- else if (fparam.type() == "pipe") // anonymous pipe
- {
- if (! fparam.name().empty())
+ LL_CONT << LL_ENDL;
+
+ throw std::runtime_error(
+ STRINGIZE("unsupported FileParam type '" << type
+ << "' for " << slotname));
+ }
+
+ // Warn about internal pipe names (not yet supported)
+ if (type == "pipe" && fparam.name.isProvided() && !fparam.name().empty())
{
- LL_WARNS("LLProcess") << "For " << params.executable()
- << ": internal names for reusing pipes ('"
- << fparam.name() << "' for " << which
- << ") are not yet supported -- creating distinct pipe"
- << LL_ENDL;
+ LL_WARNS("LLProcess") << "Internal pipe name '" << fparam.name()
+ << "' not yet supported; ignoring" << LL_ENDL;
}
- // The viewer can't block for anything: the parent end MUST be
- // nonblocking. As the APR documentation itself points out, it
- // makes very little sense to set nonblocking I/O for the child
- // end of a pipe: only a specially-written child could deal with
- // that.
- select.push_back(APR_CHILD_BLOCK);
- }
- else
- {
- LLTHROW(LLProcessError(STRINGIZE("For " << params.executable()
- << ": unsupported FileParam for " << which
- << ": type='" << fparam.type()
- << "', name='" << fparam.name() << "'")));
}
+ file_idx++;
}
- // By default, pass APR_NO_PIPE for unspecified slots.
- while (select.size() < NSLOTS)
+
+ // Build arguments vector
+ std::vector<std::string> args;
+ for (const auto& arg : params.args)
{
- select.push_back(APR_NO_PIPE);
+ args.push_back(arg);
}
- chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR]));
- // Thumbs down on implicitly invoking the shell to invoke the child. From
- // our point of view, the other major alternative to APR_PROGRAM_PATH
- // would be APR_PROGRAM_ENV: still copy environment, but require full
- // executable pathname. I don't see a downside to searching the PATH,
- // though: if our caller wants (e.g.) a specific Python interpreter, s/he
- // can still pass the full pathname.
- chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH));
- // YES, do extra work if necessary to report child exec() failures back to
- // parent process.
- chkapr(apr_procattr_error_check_set(procattr, 1));
- // Do not start a non-autokill child in detached state. On Posix
- // platforms, this setting attempts to daemonize the new child, closing
- // std handles and the like, and that's a bit more detachment than we
- // want. autokill=false just means not to implicitly kill the child when
- // the parent terminates!
-// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1));
+ // Determine pipe configuration
+ bool use_stdin_pipe = false;
+ bool use_stdout_pipe = false;
+ bool use_stderr_pipe = false;
- if (mAutokill)
+ file_idx = 0;
+ for (const auto& fparam : params.files)
{
-#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET)
- // Our special preprocessor symbol isn't even defined -- wrong APR
- LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL;
-#elif ! APR_HAS_PROCATTR_AUTOKILL_SET
- // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to
- // return APR_ENOTIMPL.
-#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero
- ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1));
-#endif
+ if (fparam.type.isProvided() && fparam.type() == "pipe")
+ {
+ switch (file_idx)
+ {
+ case STDIN: use_stdin_pipe = true; break;
+ case STDOUT: use_stdout_pipe = true; break;
+ case STDERR: use_stderr_pipe = true; break;
+ }
+ }
+ file_idx++;
}
- // In preparation for calling apr_proc_create(), we collect a number of
- // const char* pointers obtained from std::string::c_str(). Turns out
- // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al.
- // guarantee that converting to the wrapped type (std::string in our
- // case), e.g. by calling operator(), returns a reference to *the same
- // instance* of the wrapped type that's stored in our Block subclass.
- // That's important! We know 'params' persists throughout this method
- // call; but without that guarantee, when you see params.cwd().c_str(),
- // grit your teeth and smile and carry on.
-
- if (params.cwd.isProvided())
+ // Create pipes if needed - v2 uses asio pipes directly
+ // NOTE: From parent's perspective: write to stdin, read from stdout/stderr
+ if (use_stdin_pipe)
{
- chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str()));
+ mStdinPipe = std::make_shared<asio::writable_pipe>(mIOContext);
+ LL_DEBUGS("LLProcess") << "Created stdin pipe for " << mDesc << LL_ENDL;
}
-
- // create an argv vector for the child process
- std::vector<const char*> argv;
-
- // Add the executable path. See above remarks about c_str().
- argv.push_back(params.executable().c_str());
-
- // Add arguments. See above remarks about c_str().
- for (const std::string& arg : params.args)
+ if (use_stdout_pipe)
{
- argv.push_back(arg.c_str());
+ mStdoutPipe = std::make_shared<asio::readable_pipe>(mIOContext);
+ LL_DEBUGS("LLProcess") << "Created stdout pipe for " << mDesc << LL_ENDL;
}
-
- // terminate with a null pointer
- argv.push_back(NULL);
-
- // Launch! The NULL would be the environment block, if we were passing
- // one. Hand-expand chkapr() macro so we can fill in the actual command
- // string instead of the variable names.
- if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,
- mPool)))
+ if (use_stderr_pipe)
{
- LLTHROW(LLProcessError(STRINGIZE(params << " failed")));
+ mStderrPipe = std::make_shared<asio::readable_pipe>(mIOContext);
+ LL_DEBUGS("LLProcess") << "Created stderr pipe for " << mDesc << LL_ENDL;
}
- // arrange to call status_callback()
- apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in,
- mPool);
- // and make sure we poll it once per "mainloop" tick
- sProcessListener.addPoll(*this);
- mStatus.mState = RUNNING;
+ // Build the process
+ try
+ {
+#if !LL_WINDOWS
+ // Ignore SIGPIPE so that writing to a child's closed stdin doesn't
+ // terminate the viewer process. The write will fail with EPIPE instead.
+ signal(SIGPIPE, SIG_IGN);
+#endif
- mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')');
- LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL;
+ // Create child process with appropriate redirections.
+ // v2 uses process_stdio for I/O redirection
+ bp::process_stdio stdio;
- // Unless caller explicitly turned off autokill (child should persist),
- // take steps to terminate the child. This is all suspenders-and-belt: in
- // theory our destructor should kill an autokill child, but in practice
- // that doesn't always work (e.g. VWR-21538).
- if (mAutokill)
- {
-/*==========================================================================*|
- // NO: There may be an APR bug, not sure -- but at least on Mac, when
- // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently
- // either our own PID is getting into the list of processes to kill()
- // (unlikely), or somehow one of those PIDs is getting zeroed first,
- // so that kill() sends SIGTERM to the whole process group -- this
- // process included. I'd have to build and link with a debug version
- // of APR to know for sure. It's too bad: this mechanism would be just
- // right for dealing with static autokill LLProcessPtr variables,
- // which aren't destroyed until after APR is no longer available.
+ if (use_stdin_pipe)
+ stdio.in = *mStdinPipe;
+ else
+ stdio.in = nullptr; // inherit
- // Tie the lifespan of this child process to the lifespan of our APR
- // pool: on destruction of the pool, forcibly kill the process. Tell
- // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use
- // SIGKILL.
- apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT);
-|*==========================================================================*/
+ if (use_stdout_pipe)
+ stdio.out = *mStdoutPipe;
+ else
+ stdio.out = nullptr; // inherit
- // On Windows, associate the new child process with our Job Object.
- autokill();
- }
+ if (use_stderr_pipe)
+ stdio.err = *mStderrPipe;
+ else
+ stdio.err = nullptr; // inherit
- // Instantiate the proper pipe I/O machinery
- // want to be able to point to apr_proc_t::in, out, err by index
- typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr;
- static apr_proc_file_ptr members[] =
- { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err };
- for (size_t i = 0; i < NSLOTS; ++i)
- {
- if (select[i] != APR_CHILD_BLOCK)
- continue;
- std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i))));
- apr_file_t* pipe(mProcess.*(members[i]));
- if (i == STDIN)
+ // Build executable path
+ boost::filesystem::path executable_path(params.executable());
+
+ // In Boost.Process v2, error_code cannot be passed as an initializer.
+ // Use the throwing overload and catch the exception instead.
+ try
{
- mPipes[i] = std::make_unique<WritePipeImpl>(desc, pipe);
+ if (params.cwd.isProvided())
+ {
+ mChild = std::make_unique<bp::process>(
+ mIOContext,
+ executable_path,
+ args,
+ bp::process_start_dir(params.cwd()),
+ stdio
+ );
+ }
+ else
+ {
+ mChild = std::make_unique<bp::process>(
+ mIOContext,
+ executable_path,
+ args,
+ stdio
+ );
+ }
}
- else
+ catch (const boost::system::system_error& ex)
{
- mPipes[i] = std::make_unique<ReadPipeImpl>(desc, pipe, FILESLOT(i));
+ throw std::runtime_error(STRINGIZE("failed to launch " << params.executable()
+ << ": " << ex.what()));
}
- // Removed temporaily for Xcode 7 build tests: error was:
- // "error: expression with side effects will be evaluated despite
- // being used as an operand to 'typeid' [-Werror,-Wpotentially-evaluated-expression]""
- //LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name()
- // << "('" << desc << "')" << LL_ENDL;
- }
-}
-// Helper to obtain a description string, given a Params block
-static std::string getDesc(const LLProcess::Params& params)
-{
- // If caller specified a description string, by all means use it.
- if (params.desc.isProvided())
- return params.desc;
+#if LL_WINDOWS
+ // Add the process to the job object so it terminates when parent dies.
+ // This is done for all processes with autokill=true (the default).
+ // Job objects are the Windows-recommended way to ensure child processes
+ // don't become orphaned if the parent crashes or is killed.
+ if (mAutokill && mChild)
+ {
+ AssignProcessToJob(mChild->native_handle(), mDesc);
+ }
+#else
+ // boost::process v2 may install a SIGCHLD handler via boost::asio
+ // without SA_RESTART when mIOContext is passed to bp::process.
+ // Without SA_RESTART, blocking waitpid() calls elsewhere in the
+ // process return EINTR when a child exits. Ensure SA_RESTART is set.
+ {
+ struct sigaction sa_chld;
+ if (sigaction(SIGCHLD, nullptr, &sa_chld) != 0)
+ {
+ LL_WARNS("LLProcess") << "Failed to read SIGCHLD disposition: "
+ << strerror(errno) << LL_ENDL;
+ }
+ else if (!(sa_chld.sa_flags & SA_RESTART))
+ {
+ sa_chld.sa_flags |= SA_RESTART;
+ if (sigaction(SIGCHLD, &sa_chld, nullptr) != 0)
+ {
+ LL_WARNS("LLProcess") << "Failed to set SA_RESTART on SIGCHLD: "
+ << strerror(errno) << LL_ENDL;
+ }
+ }
+ }
+#endif
+
+ mStatus.mState = RUNNING;
- // Caller didn't say. Use the executable name -- but use just the filename
- // part. On Mac, for instance, full pathnames get cumbersome.
- return LLProcess::basename(params.executable);
+ // Create pipe wrappers
+ if (mStdinPipe)
+ {
+ mWritePipe = std::make_unique<WritePipeImpl>(
+ STRINGIZE(mDesc << " stdin"),
+ mStdinPipe
+ );
+ }
+ if (mStdoutPipe)
+ {
+ mStdoutReadPipe = std::make_unique<ReadPipeImpl>(
+ STRINGIZE(mDesc << " stdout"),
+ mStdoutPipe,
+ STDOUT
+ );
+ }
+ if (mStderrPipe)
+ {
+ mStderrReadPipe = std::make_unique<ReadPipeImpl>(
+ STRINGIZE(mDesc << " stderr"),
+ mStderrPipe,
+ STDERR
+ );
+ }
+
+ LL_INFOS("LLProcess") << "Launched " << mDesc
+ << " (PID: " << mChild->id() << ")" << LL_ENDL;
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error(STRINGIZE("failed to create process: " << e.what()));
+ }
}
-//static
-std::string LLProcess::basename(const std::string& path)
+void LLProcess::connectMainloop()
{
- // If there are Linden utility functions to manipulate pathnames, I
- // haven't found them -- and for this usage, Boost.Filesystem seems kind
- // of heavyweight.
- std::string::size_type delim = path.find_last_of("\\/");
- // If path contains no pathname delimiters, return the whole thing.
- if (delim == std::string::npos)
- return path;
-
- // Return just the part beyond the last delimiter.
- return path.substr(delim + 1);
+ // Capture a weak_ptr to prevent use-after-free: a listener responding to
+ // a synchronous event post inside tick() may drop the last LLProcessPtr,
+ // destroying this object while tick() is on the call stack. Locking the
+ // weak_ptr before calling tick() keeps *this alive for the duration of
+ // the callback.
+ // NOTE: shared_from_this() is only valid after the shared_ptr owning this
+ // object has been fully constructed, so this must be called from create()
+ // rather than from the constructor.
+ std::weak_ptr<LLProcess> weak = shared_from_this();
+ mMainloopConnection = LLEventPumps::instance().obtain("mainloop")
+ .listen(LLEventPump::inventName("LLProcess"),
+ [weak](const LLSD&)
+ {
+ // Lock weak_ptr to keep *this alive during tick(), preventing
+ // destruction mid-callback if a listener drops the last LLProcessPtr.
+ auto self = weak.lock();
+ if (self) self->tick();
+ return false;
+ });
}
-LLProcess::~LLProcess()
+void LLProcess::tick()
{
- // In the Linden viewer, there's at least one static LLProcessPtr. Its
- // destructor will be called *after* ll_cleanup_apr(). In such a case,
- // unregistering is pointless (and fatal!) -- and kill(), which also
- // relies on APR, is impossible.
- if (! gAPRPoolp)
- return;
+ // Initiate pending stdin writes before draining the I/O context so that
+ // self-chained async writes can keep advancing within the same tick
+ // instead of waiting for the next mainloop frame.
+ if (mWritePipe)
+ mWritePipe->tick();
- // Only in state RUNNING are we registered for callback. In UNSTARTED we
- // haven't yet registered. And since receiving the callback is the only
- // way we detect child termination, we only change from state RUNNING at
- // the same time we unregister.
- if (mStatus.mState == RUNNING)
+ mIOContext.restart();
+ while (mIOContext.poll_one() > 0)
{
- // We're still registered for a callback: unregister. Do it before
- // we even issue the kill(): even if kill() somehow prompted an
- // instantaneous callback (unlikely), this object is going away! Any
- // information updated in this object by such a callback is no longer
- // available to any consumer anyway.
- apr_proc_other_child_unregister(this);
- // One less LLProcess to poll for
- sProcessListener.dropPoll(*this);
+ // Keep polling until no more handlers are ready
}
- if (mAttached)
+#if LL_WINDOWS
+ // Check process status
+ if (mChild && mStatus.mState == RUNNING && !mChild->running())
{
- kill("destructor");
+ // Process has exited.
+ // We are on the main thread, so get exit code without blocking
+ DWORD exit_code = STILL_ACTIVE;
+ if (::GetExitCodeProcess(mChild->native_handle(), &exit_code))
+ {
+ if (exit_code != STILL_ACTIVE)
+ {
+ // Exit code is available
+ Status exitStatus;
+ exitStatus.mState = EXITED;
+ exitStatus.mData = static_cast<int>(exit_code);
+ handleExit(exitStatus);
+ }
+ else
+ {
+ // Exit code not ready yet, will retry next tick
+ // This shouldn't happen if mChild->running() returned false,
+ // but handle it gracefully
+ LL_DEBUGS("LLProcess") << "Process " << mDesc
+ << " exited but exit code not ready yet" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("LLProcess") << "GetExitCodeProcess failed for " << mDesc
+ << ": error " << ::GetLastError() << LL_ENDL;
+ // Synthesize exit status
+ Status exitStatus;
+ exitStatus.mState = EXITED;
+ exitStatus.mData = -1;
+ handleExit(exitStatus);
+ }
}
+#else
+ // Check process status using WNOHANG to avoid blocking or generating
+ // signals that interfere with other waitpid() callers.
+ if (mChild && mStatus.mState == RUNNING)
+ {
+ int status = 0;
+ pid_t result;
+ // Retry on EINTR: SA_RESTART only applies to blocking calls, not
+ // WNOHANG waitpid(), so manual retry is still needed.
+ do { // manual EINTR retry (SA_RESTART does not apply to WNOHANG)
+ result = ::waitpid(mChild->id(), &status, WNOHANG);
+ } while (result == -1 && errno == EINTR);
- if (mPool)
+ if (result == mChild->id())
+ {
+ // Child has exited; decode exit status now (before handleExit,
+ // since the child has already been reaped by this waitpid call).
+ Status exitStatus;
+ if (WIFEXITED(status))
+ {
+ exitStatus.mState = EXITED;
+ exitStatus.mData = WEXITSTATUS(status);
+ }
+ else if (WIFSIGNALED(status))
+ {
+ exitStatus.mState = KILLED;
+ exitStatus.mData = WTERMSIG(status);
+ }
+ else
+ {
+ exitStatus.mState = EXITED;
+ exitStatus.mData = 0;
+ }
+ handleExit(exitStatus);
+ }
+ else if (result == -1 && errno == ECHILD)
+ {
+ // The zombie was already reaped by someone else (e.g. a SIGCHLD
+ // handler from APR or another library). We can't determine the
+ // real exit code; synthesize EXITED/0 so the process is no longer
+ // considered "running" and waitfor() doesn't spin for 60 seconds.
+ Status exitStatus;
+ exitStatus.mState = EXITED;
+ exitStatus.mData = 0;
+ handleExit(exitStatus);
+ }
+ // result == 0 means still running
+ }
+#endif
+
+ // Keep pumping after process exit until all ReadPipes report EOF, then
+ // disconnect from mainloop to avoid losing trailing EOF notifications.
+ if (mStatus.mState != RUNNING && mMainloopConnection.connected())
{
- apr_pool_destroy(mPool);
- mPool = NULL;
+ bool stdout_eof = (!mStdoutReadPipe || mStdoutReadPipe->atEOF());
+ bool stderr_eof = (!mStderrReadPipe || mStderrReadPipe->atEOF());
+ if (stdout_eof && stderr_eof)
+ {
+ mMainloopConnection.disconnect();
+ }
}
}
-bool LLProcess::kill(const std::string& who)
+void LLProcess::handleExit(Status exitStatus)
{
- if (isRunning())
- {
- LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL;
+ if (mStatus.mState != RUNNING)
+ return; // Already handled
-#if LL_WINDOWS
- int sig = -1;
-#else // Posix
- int sig = SIGTERM;
-#endif
+ mStatus = exitStatus;
- ll_apr_warn_status(apr_proc_kill(&mProcess, sig));
+ // Drain any remaining pipe handlers before notifying callers. LLLeap
+ // consumes child stdout/stderr from these callbacks, and some tests write
+ // enough data to require far more than a handful of async-read completions
+ // after the child has already exited.
+ if (mIOContext.stopped())
+ {
+ // If the io_context has reached the stopped state,
+ // the following loop will never run and trailing stdout/stderr
+ // handlers (and EOF notifications) may be missed.
+ LL_INFOS("LLProcess") << "mIOContext was stopped on exit, restarting" << LL_ENDL;
+ mIOContext.restart();
+ }
+ while (mIOContext.poll_one() > 0)
+ {
+ // Keep polling until no more handlers are immediately ready.
}
- return ! isRunning();
-}
+ LL_INFOS("LLProcess") << getStatusString(mStatus) << LL_ENDL;
-//static
-bool LLProcess::kill(const LLProcessPtr& p, const std::string& who)
-{
- if (! p)
- return true; // process dead! (was never running)
- return p->kill(who);
+ // Post to event pump if configured
+ if (!mPostend.empty())
+ {
+ LLSD event;
+ event["id"] = static_cast<int>(getProcessID());
+ event["desc"] = mDesc;
+ event["state"] = mStatus.mState;
+ event["data"] = mStatus.mData;
+ event["string"] = getStatusString(mStatus);
+
+ LLEventPumps::instance().obtain(mPostend).post(event);
+ }
+
+ // Leave mainloop connected until tick() observes EOF on all ReadPipes.
}
bool LLProcess::isRunning() const
{
- return getStatus().mState == RUNNING;
+ return mStatus.mState == RUNNING;
}
//static
-bool LLProcess::isRunning(const LLProcessPtr& p)
+bool LLProcess::isRunning(const LLProcessPtr& ptr)
{
- if (! p)
- return false;
- return p->isRunning();
+ return ptr && ptr->isRunning();
}
LLProcess::Status LLProcess::getStatus() const
@@ -863,19 +999,26 @@ LLProcess::Status LLProcess::getStatus() const
}
//static
-LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p)
+LLProcess::Status LLProcess::getStatus(const LLProcessPtr& ptr)
{
- if (! p)
+ if (!ptr)
{
- // default-constructed Status has mState == UNSTARTED
- return Status();
+ Status status;
+ status.mState = UNSTARTED;
+ return status;
}
- return p->getStatus();
+ return ptr->getStatus();
}
std::string LLProcess::getStatusString() const
{
- return getStatusString(getStatus());
+ return getStatusString(mDesc, mStatus);
+}
+
+//static
+std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& ptr)
+{
+ return getStatusString(desc, getStatus(ptr));
}
std::string LLProcess::getStatusString(const Status& status) const
@@ -884,464 +1027,315 @@ std::string LLProcess::getStatusString(const Status& status) const
}
//static
-std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p)
+std::string LLProcess::getStatusString(const std::string& desc, const Status& status)
{
- if (! p)
+ std::string result = desc + ": ";
+ switch (status.mState)
{
- // default-constructed Status has mState == UNSTARTED
- return getStatusString(desc, Status());
+ case UNSTARTED: return result + "not started";
+ case RUNNING: return result + "running";
+ case EXITED: return result + STRINGIZE("exited with code " << status.mData);
+ case KILLED: return result + STRINGIZE("killed by signal " << status.mData);
+ default: return result + "unknown state";
}
- return desc + " " + p->getStatusString();
}
-//static
-std::string LLProcess::getStatusString(const std::string& desc, const Status& status)
+bool LLProcess::kill(const std::string& who)
{
- if (status.mState == UNSTARTED)
- return desc + " was never launched";
-
- if (status.mState == RUNNING)
- return desc + " running";
+ if (!mChild || mStatus.mState != RUNNING)
+ return true;
- if (status.mState == EXITED)
- return STRINGIZE(desc << " exited with code " << status.mData);
+ LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL;
- if (status.mState == KILLED)
#if LL_WINDOWS
- return STRINGIZE(desc << " killed with exception " << std::hex << status.mData);
+ // Call TerminateProcess directly with exit code (UINT)-1 so that
+ // tick()'s GetExitCodeProcess reads back -1 as a signed int, matching
+ // the original APR-based behavior expected by tests and callers.
+ // Do NOT use mChild->terminate(): boost::process v2 may use a different
+ // exit code (e.g. EXIT_FAILURE=1) or invalidate the handle internally,
+ // which would break the exit-code check in tick().
+ if (!::TerminateProcess(mChild->native_handle(), (UINT)-1))
+ {
+ LL_WARNS("LLProcess") << "Failed to terminate " << mDesc
+ << ": error " << ::GetLastError() << LL_ENDL;
+ return false;
+ }
#else
- return STRINGIZE(desc << " killed by signal " << status.mData
- << " (" << apr_signal_description_get(status.mData) << ")");
+ // Send SIGTERM so the child can clean up gracefully, and so tick()'s
+ // waitpid() reports WIFSIGNALED/WTERMSIG == SIGTERM rather than SIGKILL.
+ // Do NOT call mChild->terminate(): in boost::process v2, that function
+ // sends SIGKILL and may set the internal pid to -1, which would cause
+ // tick()'s waitpid(mChild->id(), ...) to wait for any child (-1) and
+ // never match the result against the stored pid.
+ pid_t pid = mChild->id();
+ if (::kill(pid, SIGTERM) != 0 && errno != ESRCH)
+ {
+ LL_WARNS("LLProcess") << "Failed to send SIGTERM to " << mDesc
+ << " (pid " << pid << "): " << strerror(errno) << LL_ENDL;
+ return false;
+ }
#endif
- return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")");
+ // Mark as killed so the destructor doesn't repeat the termination attempt,
+ // but a better idea might be to modify mState.
+ mKillCalled = true;
+ // Don't set status here - let handleExit() do it when the process actually terminates
+ // so that it will be able to post EOF event.
+ // At this point in time mStatus.mState is still RUNNING, so this is basically
+ // retuning false.
+ return !isRunning();
}
-// Classic-C-style APR callback
-void LLProcess::status_callback(int reason, void* data, int status)
+//static
+bool LLProcess::kill(const LLProcessPtr& ptr, const std::string& who)
{
- // Our only role is to bounce this static method call back into object
- // space.
- static_cast<LLProcess*>(data)->handle_status(reason, status);
+ return !ptr || ptr->kill(who);
}
-#define tabent(symbol) { symbol, #symbol }
-static struct ReasonCode
-{
- int code;
- const char* name;
-} reasons[] =
+LLProcess::id LLProcess::getProcessID() const
{
- tabent(APR_OC_REASON_DEATH),
- tabent(APR_OC_REASON_UNWRITABLE),
- tabent(APR_OC_REASON_RESTART),
- tabent(APR_OC_REASON_UNREGISTER),
- tabent(APR_OC_REASON_LOST),
- tabent(APR_OC_REASON_RUNNING)
-};
-#undef tabent
+ if (!mChild)
+ return 0;
-// Object-oriented callback
-void LLProcess::handle_status(int reason, int status)
+#if LL_WINDOWS
+ return static_cast<int>(mChild->id());
+#else
+ return mChild->id();
+#endif
+}
+
+LLProcess::handle LLProcess::getProcessHandle() const
{
- {
- // This odd appearance of LL_DEBUGS is just to bracket a lookup that will
- // only be performed if in fact we're going to produce the log message.
- LL_DEBUGS("LLProcess") << empty;
- std::string reason_str;
- for (const ReasonCode& rcp : reasons)
- {
- if (reason == rcp.code)
- {
- reason_str = rcp.name;
- break;
- }
- }
- if (reason_str.empty())
- {
- reason_str = STRINGIZE("unknown reason " << reason);
- }
- LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL;
- }
+ if (!mChild)
+ return 0;
- if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST))
+#if LL_WINDOWS
+ // Duplicate the process handle so the caller owns an independent copy.
+ // boost::process v2's process destructor always closes the handle
+ // (even after process::detach()), so the raw native_handle() becomes invalid
+ // once ~process() runs. DuplicateHandle gives the caller a handle whose
+ // lifetime is not tied to boost's internal process cleanup.
+ HANDLE source = mChild->native_handle();
+ if (!source || source == INVALID_HANDLE_VALUE)
+ return 0;
+ HANDLE dup = nullptr;
+ if (!::DuplicateHandle(
+ ::GetCurrentProcess(), source,
+ ::GetCurrentProcess(), &dup,
+ PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
+ FALSE, 0))
{
- // We're only interested in the call when the child terminates.
- return;
+ LL_WARNS("LLProcess") << "DuplicateHandle failed for " << mDesc
+ << ": error " << ::GetLastError() << LL_ENDL;
+ return 0;
}
+ return dup;
+#else
+ return mChild->id();
+#endif
+}
- // Somewhat oddly, APR requires that you explicitly unregister even when
- // it already knows the child has terminated. We must pass the same 'data'
- // pointer as for the register() call, which was our 'this'.
- apr_proc_other_child_unregister(this);
- // don't keep polling for a terminated process
- sProcessListener.dropPoll(*this);
- // We overload mStatus.mState to indicate whether the child is registered
- // for APR callback: only RUNNING means registered. Track that we've
- // unregistered. We know the child has terminated; might be EXITED or
- // KILLED; refine below.
- mStatus.mState = EXITED;
+//static
+LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc)
+{
+#if LL_WINDOWS
+ if (h == 0 || h == INVALID_HANDLE_VALUE)
+ return 0;
- // Make last-gasp calls for each of the ReadPipes we have on hand. Since
- // they're listening on "mainloop", we can be sure they'll eventually
- // collect all pending data from the child. But we want to be able to
- // guarantee to our consumer that by the time we post on the "postend"
- // LLEventPump, our ReadPipes are already buffering all the data there
- // will ever be from the child. That lets the "postend" listener decide
- // what to do with that final data.
- for (size_t i = 0; i < mPipes.size(); ++i)
+ DWORD exit_code;
+ if (GetExitCodeProcess(h, &exit_code))
{
- std::string error;
- ReadPipeImpl* ppipe = getPipePtr<ReadPipeImpl>(error, FILESLOT(i));
- if (ppipe)
- {
- static LLSD trivial;
- ppipe->tick(trivial);
- }
+ if (exit_code == STILL_ACTIVE)
+ return h; // process still running
+ // Process has exited: close the duplicated handle (see getProcessHandle()).
}
+ // Either process exited or GetExitCodeProcess failed; either way we're done.
+ CloseHandle(h);
+ return 0;
+#else
+ if (h == 0)
+ return 0;
-// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT);
- // It's just wrong to call apr_proc_wait() here. The only way APR knows to
- // call us with APR_OC_REASON_DEATH is that it's already reaped this child
- // process, so calling wait() will only produce "huh?" from the OS. We
- // must rely on the status param passed in, which unfortunately comes
- // straight from the OS wait() call, which means we have to decode it by
- // hand.
- mStatus = interpret_status(status);
- LL_INFOS("LLProcess") << getStatusString() << LL_ENDL;
+ // Use waitpid with WNOHANG to check if process is still running
+ // This is more reliable than kill(pid, 0) and properly reaps zombies
+ int status;
+ pid_t result;
- // If caller requested notification on child termination, send it.
- if (! mPostend.empty())
+ // Retry on EINTR (interrupted system call)
+ do
{
- LLEventPumps::instance().obtain(mPostend)
- .post(LLSDMap
- ("id", getProcessID())
- ("desc", mDesc)
- ("state", mStatus.mState)
- ("data", mStatus.mData)
- ("string", getStatusString())
- );
- }
-}
-
-LLProcess::id LLProcess::getProcessID() const
-{
- return mProcess.pid;
-}
+ result = waitpid(h, &status, WNOHANG);
+ } while (result == -1 && errno == EINTR);
-LLProcess::handle LLProcess::getProcessHandle() const
-{
-#if LL_WINDOWS
- return mProcess.hproc;
-#else
- return mProcess.pid;
-#endif
-}
-
-std::string LLProcess::getPipeName(FILESLOT) const
-{
- // LLProcess::FileParam::type "npipe" is not yet implemented
- return "";
-}
-
-template<class PIPETYPE>
-PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot)
-{
- if (slot >= NSLOTS)
+ if (result == 0)
{
- error = STRINGIZE(mDesc << " has no slot " << slot);
- return NULL;
+ // Process still running
+ return h;
}
- if (!mPipes[slot])
+ else if (result == h)
{
- error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe");
- return NULL;
+ // Process has terminated (and we've reaped it)
+ return 0;
}
- // Make sure we dynamic_cast in pointer domain so we can test, rather than
- // accepting runtime's exception.
- PIPETYPE* ppipe = dynamic_cast<PIPETYPE*>(mPipes[slot].get());
- if (! ppipe)
+ else if (result == -1)
{
- error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name());
- return NULL;
+ // Error occurred
+ if (errno == ECHILD)
+ {
+ // Process doesn't exist or was already reaped
+ return 0;
+ }
+ // For other errors, assume process is gone
+ LL_WARNS("LLProcess") << "waitpid(" << h << ") failed: "
+ << strerror(errno) << LL_ENDL;
+ return 0;
}
- error.clear();
- return ppipe;
+ // Shouldn't get here, but if we do, assume process is gone
+ return 0;
+#endif
}
-template <class PIPETYPE>
-PIPETYPE& LLProcess::getPipe(FILESLOT slot)
+std::string LLProcess::getPipeName(FILESLOT slot) const
{
- std::string error;
- PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
- if (! wp)
- {
- LLTHROW(NoPipe(error));
- }
- return *wp;
+ // Named pipes not yet implemented in this PoC
+ return "";
}
-template <class PIPETYPE>
-boost::optional<PIPETYPE&> LLProcess::getOptPipe(FILESLOT slot)
+LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot)
{
- std::string error;
- PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
- if (! wp)
+ if (slot >= NSLOTS)
+ throw NoPipe(STRINGIZE(mDesc << ": no slot " << slot));
+
+ if (slot == STDIN)
{
- LL_DEBUGS("LLProcess") << error << LL_ENDL;
- return boost::optional<PIPETYPE&>();
+ if (!mWritePipe)
+ throw NoPipe(STRINGIZE(mDesc << ": stdin is not a monitored pipe"));
+ return *mWritePipe;
}
- return *wp;
-}
-
-LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot)
-{
- return getPipe<WritePipe>(slot);
-}
-boost::optional<LLProcess::WritePipe&> LLProcess::getOptWritePipe(FILESLOT slot)
-{
- return getOptPipe<WritePipe>(slot);
+ // STDOUT or STDERR slots: neither has a WritePipe.
+ // Distinguish "not piped at all" from "piped but wrong direction".
+ const char* slotname = (slot == STDOUT) ? "stdout" : "stderr";
+ bool is_piped = (slot == STDOUT) ? bool(mStdoutReadPipe) : bool(mStderrReadPipe);
+ if (!is_piped)
+ throw NoPipe(STRINGIZE(mDesc << ": " << slotname << " is not a monitored pipe"));
+ else
+ throw NoPipe(STRINGIZE(mDesc << ": " << slotname << " is a ReadPipe, not a WritePipe"));
}
LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot)
{
- return getPipe<ReadPipe>(slot);
-}
+ if (slot >= NSLOTS)
+ throw NoPipe(STRINGIZE(mDesc << ": no slot " << slot));
-boost::optional<LLProcess::ReadPipe&> LLProcess::getOptReadPipe(FILESLOT slot)
-{
- return getOptPipe<ReadPipe>(slot);
-}
+ if (slot == STDIN)
+ throw NoPipe(STRINGIZE(mDesc << ": ReadPipe is invalid for stdin"));
-//static
-std::string LLProcess::getline(std::istream& in)
-{
- std::string line;
- std::getline(in, line);
- // Blur the distinction between "\r\n" and plain "\n". std::getline() will
- // have eaten the "\n", but we could still end up with a trailing "\r".
- std::string::size_type lastpos = line.find_last_not_of("\r");
- if (lastpos != std::string::npos)
+ // At this point, slot must be STDOUT or STDERR
+ // Check if a pipe was configured for this slot
+ if (slot == STDOUT)
{
- // Found at least one character that's not a trailing '\r'. SKIP OVER
- // IT and erase the rest of the line.
- line.erase(lastpos+1);
+ if (!mStdoutReadPipe)
+ throw NoPipe(STRINGIZE(mDesc << ": stdout is not a monitored pipe"));
+ return *mStdoutReadPipe;
}
- return line;
-}
-
-std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params)
-{
- if (params.cwd.isProvided())
+ else if (slot == STDERR)
{
- out << "cd " << LLStringUtil::quote(params.cwd) << ": ";
+ if (!mStderrReadPipe)
+ throw NoPipe(STRINGIZE(mDesc << ": stderr is not a monitored pipe"));
+ return *mStderrReadPipe;
}
- out << LLStringUtil::quote(params.executable);
- for (const std::string& arg : params.args)
+ else
{
- out << ' ' << LLStringUtil::quote(arg);
+ // This should never happen given the checks above, but handle it anyway
+ throw NoPipe(STRINGIZE(mDesc << ": no slot " << slot));
}
- return out;
-}
-
-/*****************************************************************************
-* Windows specific
-*****************************************************************************/
-#if LL_WINDOWS
-
-static std::string WindowsErrorString(const std::string& operation);
-
-void LLProcess::autokill()
-{
- // hopefully now handled by apr_procattr_autokill_set()
}
-LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc)
+LLProcess::WritePipe* LLProcess::getOptWritePipe(FILESLOT slot)
{
- // This direct Windows implementation is because we have no access to the
- // apr_proc_t struct: we expect it's been destroyed.
- if (! h)
- return 0;
+ if (slot >= NSLOTS)
+ {
+ LL_WARNS("LLProcess") << mDesc << ": no slot " << slot << LL_ENDL;
+ return nullptr;
+ }
- DWORD waitresult = WaitForSingleObject(h, 0);
- if(waitresult == WAIT_OBJECT_0)
+ if (slot == STDIN)
{
- // the process has completed.
- if (! desc.empty())
+ if (!mWritePipe)
{
- DWORD status = 0;
- if (! GetExitCodeProcess(h, &status))
- {
- LL_WARNS("LLProcess") << desc << " terminated, but "
- << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL;
- }
- {
- LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status))
- << LL_ENDL;
- }
+ LL_WARNS("LLProcess") << mDesc << ": stdin is not a monitored pipe" << LL_ENDL;
+ return nullptr;
}
- CloseHandle(h);
- return 0;
+ return mWritePipe.get();
}
- return h;
+ // STDOUT or STDERR slots: neither has a WritePipe.
+ const char* slotname = (slot == STDOUT) ? "stdout" : "stderr";
+ bool is_piped = (slot == STDOUT) ? bool(mStdoutReadPipe) : bool(mStderrReadPipe);
+ if (!is_piped)
+ LL_WARNS("LLProcess") << mDesc << ": " << slotname << " is not a monitored pipe" << LL_ENDL;
+ else
+ LL_WARNS("LLProcess") << mDesc << ": " << slotname << " is a ReadPipe, not a WritePipe" << LL_ENDL;
+ return nullptr;
}
-static LLProcess::Status interpret_status(int status)
+LLProcess::ReadPipe* LLProcess::getOptReadPipe(FILESLOT slot)
{
- LLProcess::Status result;
-
- // This bit of code is cribbed from apr/threadproc/win32/proc.c, a
- // function (unfortunately static) called why_from_exit_code():
- /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how
- * this class of failures was determined
- */
- if ((status & 0xFFFF0000) == 0xC0000000)
- {
- result.mState = LLProcess::KILLED;
- }
- else
+ if (slot >= NSLOTS)
{
- result.mState = LLProcess::EXITED;
+ LL_WARNS("LLProcess") << mDesc << ": no slot " << slot << LL_ENDL;
+ return nullptr;
}
- result.mData = status;
-
- return result;
-}
-
-/// GetLastError()/FormatMessage() boilerplate
-static std::string WindowsErrorString(const std::string& operation)
-{
- auto result = GetLastError();
- return STRINGIZE(operation << " failed (" << result << "): "
- << windows_message<std::string>(result));
-}
-
-/*****************************************************************************
-* Posix specific
-*****************************************************************************/
-#else // Mac and linux
-
-#include <signal.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <sys/wait.h>
-
-void LLProcess::autokill()
-{
- // What we ought to do here is to:
- // 1. create a unique process group and run all autokill children in that
- // group (see https://jira.secondlife.com/browse/SWAT-563);
- // 2. figure out a way to intercept control when the viewer exits --
- // gracefully or not;
- // 3. when the viewer exits, kill off the aforementioned process group.
-
- // It's point 2 that's troublesome. Although I've seen some signal-
- // handling logic in the Posix viewer code, I haven't yet found any bit of
- // code that's run no matter how the viewer exits (a try/finally for the
- // whole process, as it were).
-}
-// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
-static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL)
-{
- LLProcess::Status dummy;
- if (! pstatus)
+ if (slot == STDIN)
{
- // If caller doesn't want to see Status, give us a target anyway so we
- // don't have to have a bunch of conditionals.
- pstatus = &dummy;
+ LL_WARNS("LLProcess") << mDesc << ": ReadPipe is invalid for stdin" << LL_ENDL;
+ return nullptr;
}
- int status = 0;
- pid_t wait_result = ::waitpid(pid, &status, WNOHANG);
- if (wait_result == pid)
+ if (slot == STDOUT)
{
- *pstatus = interpret_status(status);
- return true;
+ if (!mStdoutReadPipe)
+ {
+ LL_WARNS("LLProcess") << mDesc << ": stdout is not a monitored pipe" << LL_ENDL;
+ return nullptr;
+ }
+ return mStdoutReadPipe.get();
}
- if (wait_result == 0)
+ else if (slot == STDERR)
{
- pstatus->mState = LLProcess::RUNNING;
- pstatus->mData = 0;
- return false;
+ if (!mStderrReadPipe)
+ {
+ LL_WARNS("LLProcess") << mDesc << ": stderr is not a monitored pipe" << LL_ENDL;
+ return nullptr;
+ }
+ return mStderrReadPipe.get();
}
-
- // Clear caller's Status block; caller must interpret UNSTARTED to mean
- // "if this PID was ever valid, it no longer is."
- *pstatus = LLProcess::Status();
-
- // We've dealt with the success cases: we were able to reap the child
- // (wait_result == pid) or it's still running (wait_result == 0). It may
- // be that the child terminated but didn't hang around long enough for us
- // to reap. In that case we still have no Status to report, but we can at
- // least state that it's not running.
- if (wait_result == -1 && errno == ECHILD)
+ else
{
- // No such process -- this may mean we're ignoring SIGCHILD.
- return true;
+ LL_WARNS("LLProcess") << mDesc << ": no slot " << slot << LL_ENDL;
+ return nullptr;
}
-
- // Uh, should never happen?!
- LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned "
- << wait_result << "; not meaningful?" << LL_ENDL;
- // If caller is looping until this pid terminates, and if we can't find
- // out, better to break the loop than to claim it's still running.
- return true;
}
-LLProcess::id LLProcess::isRunning(id pid, const std::string& desc)
+//static
+std::string LLProcess::basename(const std::string& path)
{
- // This direct Posix implementation is because we have no access to the
- // apr_proc_t struct: we expect it's been destroyed.
- if (! pid)
- return 0;
-
- // Check whether the process has exited, and reap it if it has.
- LLProcess::Status status;
- if(reap_pid(pid, &status))
- {
- // the process has exited.
- if (! desc.empty())
- {
- std::string statstr(desc + " apparently terminated: no status available");
- // We don't just pass UNSTARTED to getStatusString() because, in
- // the context of reap_pid(), that state has special meaning.
- if (status.mState != UNSTARTED)
- {
- statstr = getStatusString(desc, status);
- }
- LL_INFOS("LLProcess") << statstr << LL_ENDL;
- }
- return 0;
- }
-
- return pid;
+ std::string::size_type delim = path.find_last_of("\\/");
+ if (delim == std::string::npos)
+ return path;
+ return path.substr(delim + 1);
}
-static LLProcess::Status interpret_status(int status)
+//static
+std::string LLProcess::getline(std::istream& in)
{
- LLProcess::Status result;
-
- if (WIFEXITED(status))
- {
- result.mState = LLProcess::EXITED;
- result.mData = WEXITSTATUS(status);
- }
- else if (WIFSIGNALED(status))
- {
- result.mState = LLProcess::KILLED;
- result.mData = WTERMSIG(status);
- }
- else // uh, shouldn't happen?
- {
- result.mState = LLProcess::EXITED;
- result.mData = status; // someone else will have to decode
- }
-
- return result;
+ std::string line;
+ std::getline(in, line);
+ // Trim trailing \r for cross-platform compatibility
+ if (!line.empty() && line.back() == '\r')
+ line.pop_back();
+ return line;
}
-
-#endif // Posix
diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h
index 0c71cfc415..26f9a27837 100644
--- a/indra/llcommon/llprocess.h
+++ b/indra/llcommon/llprocess.h
@@ -30,8 +30,16 @@
#include "llinitparam.h"
#include "llsdparam.h"
#include "llexception.h"
-#include "apr_thread_proc.h"
-#include <boost/optional.hpp>
+#include <memory>
+#include <boost/process/v2/process.hpp>
+#include <boost/process/v2/environment.hpp>
+#include <boost/process/v2/start_dir.hpp>
+#include <boost/process/v2/stdio.hpp>
+#include <boost/signals2.hpp>
+#include <boost/asio.hpp>
+#include <boost/asio/streambuf.hpp>
+#include <boost/asio/readable_pipe.hpp>
+#include <boost/asio/writable_pipe.hpp>
#include <iosfwd> // std::ostream
#if LL_WINDOWS
@@ -65,93 +73,31 @@ typedef std::shared_ptr<LLProcess> LLProcessPtr;
* indra/llcommon/tests/llprocess_test.cpp for an example of waiting for
* child-process termination in a standalone test context.
*/
-class LL_COMMON_API LLProcess
+
+class LL_COMMON_API LLProcess : public std::enable_shared_from_this<LLProcess>
{
LOG_CLASS(LLProcess);
public:
/**
* Specify what to pass for each of child stdin, stdout, stderr.
- * @see LLProcess::Params::files.
*/
- struct FileParam: public LLInitParam::Block<FileParam>
+ struct FileParam : public LLInitParam::Block<FileParam>
{
/**
* type of file handle to pass to child process
- *
- * - "" (default): let the child inherit the same file handle used by
- * this process. For instance, if passed as stdout, child stdout
- * will be interleaved with stdout from this process. In this case,
- * @a name is moot and should be left "".
- *
- * - "file": open an OS filesystem file with the specified @a name.
- * <i>Not yet implemented.</i>
- *
- * - "pipe" or "tpipe" or "npipe": depends on @a name
- *
- * - @a name.empty(): construct an OS pipe used only for this slot
- * of the forthcoming child process.
- *
- * - ! @a name.empty(): in a global registry, find or create (using
- * the specified @a name) an OS pipe. The point of the (purely
- * internal) @a name is that passing the same @a name in more than
- * one slot for a given LLProcess -- or for slots in different
- * LLProcess instances -- means the same pipe. For example, you
- * might pass the same @a name value as both stdout and stderr to
- * make the child process produce both on the same actual pipe. Or
- * you might pass the same @a name as the stdout for one LLProcess
- * and the stdin for another to connect the two child processes.
- * Use LLProcess::getPipeName() to generate a unique name
- * guaranteed not to already exist in the registry. <i>Not yet
- * implemented.</i>
- *
- * The difference between "pipe", "tpipe" and "npipe" is as follows.
- *
- * - "pipe": direct LLProcess to monitor the parent end of the pipe,
- * pumping nonblocking I/O every frame. The expectation (at least
- * for stdout or stderr) is that the caller will listen for
- * incoming data and consume it as it arrives. It's important not
- * to neglect such a pipe, because it's buffered in memory. If you
- * suspect the child may produce a great volume of output between
- * frames, consider directing the child to write to a filesystem
- * file instead, then read the file later.
- *
- * - "tpipe": do not engage LLProcess machinery to monitor the
- * parent end of the pipe. A "tpipe" is used only to connect
- * different child processes. As such, it makes little sense to
- * pass an empty @a name. <i>Not yet implemented.</i>
- *
- * - "npipe": like "tpipe", but use an OS named pipe with a
- * generated name. Note that @a name is the @em internal name of
- * the pipe in our global registry -- it doesn't necessarily have
- * anything to do with the pipe's name in the OS filesystem. Use
- * LLProcess::getPipeName() to obtain the named pipe's OS
- * filesystem name, e.g. to pass it as the @a name to another
- * LLProcess instance using @a type "file". This supports usage
- * like bash's &lt;(subcommand...) or &gt;(subcommand...)
- * constructs. <i>Not yet implemented.</i>
- *
- * In all cases the open mode (read, write) is determined by the child
- * slot you're filling. Child stdin means select the "read" end of a
- * pipe, or open a filesystem file for reading; child stdout or stderr
- * means select the "write" end of a pipe, or open a filesystem file
- * for writing.
- *
- * Confusion such as passing the same pipe as the stdin of two
- * processes (rather than stdout for one and stdin for the other) is
- * explicitly permitted: it's up to the caller to construct meaningful
- * LLProcess pipe graphs.
+ * - "" (default): inherit from parent
+ * - "pipe": create a pipe for I/O
+ * - "file": open a filesystem file (future enhancement)
*/
Optional<std::string> type;
Optional<std::string> name;
- FileParam(const std::string& tp="", const std::string& nm=""):
+ FileParam(const std::string& tp = "", const std::string& nm = "") :
type("type"),
name("name")
{
- // If caller wants to specify values, use explicit assignment to
- // set them rather than initialization.
- if (! tp.empty()) type = tp;
- if (! nm.empty()) name = nm;
+ if (!tp.empty()) type = tp;
+ if (!nm.empty()) name = nm;
}
};
@@ -167,7 +113,8 @@ public:
files("files"),
postend("postend"),
desc("desc")
- {}
+ {
+ }
/// pathname of executable
Mandatory<std::string> executable;
@@ -249,18 +196,11 @@ public:
};
typedef LLSDParamAdapter<Params> LLSDOrParams;
- /**
- * Factory accepting either plain LLSD::Map or Params block.
- * MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid!
- */
static LLProcessPtr create(const LLSDOrParams& params);
virtual ~LLProcess();
/// Is child process still running?
bool isRunning() const;
- // static isRunning(LLProcessPtr), getStatus(LLProcessPtr),
- // getStatusString(LLProcessPtr), kill(LLProcessPtr) handle the case in
- // which the passed LLProcessPtr might be NULL (default-constructed).
static bool isRunning(const LLProcessPtr&);
/**
@@ -279,296 +219,132 @@ public:
*/
struct Status
{
- Status():
- mState(UNSTARTED),
- mData(0)
- {}
-
- state mState; ///< @see state
- /**
- * - for mState == EXITED: mData is exit() code
- * - for mState == KILLED: mData is signal number (Posix)
- * - otherwise: mData is undefined
- */
- int mData;
+ Status() : mState(UNSTARTED), mData(0) {}
+ state mState;
+ int mData; // exit code or signal number
};
- /// Status query
Status getStatus() const;
static Status getStatus(const LLProcessPtr&);
- /// English Status string query, for logging etc.
std::string getStatusString() const;
static std::string getStatusString(const std::string& desc, const LLProcessPtr&);
- /// English Status string query for previously-captured Status
std::string getStatusString(const Status& status) const;
- /// static English Status string query
static std::string getStatusString(const std::string& desc, const Status& status);
- // Attempt to kill the process -- returns true if the process is no longer running when it returns.
- // Note that even if this returns false, the process may exit some time after it's called.
- bool kill(const std::string& who="");
- static bool kill(const LLProcessPtr& p, const std::string& who="");
+ bool kill(const std::string& who = "");
+ static bool kill(const LLProcessPtr& p, const std::string& who = "");
#if LL_WINDOWS
- typedef int id; ///< as returned by getProcessID()
- typedef HANDLE handle; ///< as returned by getProcessHandle()
+ typedef int id;
+ typedef HANDLE handle;
#else
typedef pid_t id;
typedef pid_t handle;
#endif
- /**
- * Get an int-like id value. This is primarily intended for a human reader
- * to differentiate processes.
- */
+
id getProcessID() const;
- /**
- * Get a "handle" of a kind that you might pass to platform-specific API
- * functions to engage features not directly supported by LLProcess.
- */
handle getProcessHandle() const;
+ static handle isRunning(handle, const std::string& desc = "");
- /**
- * Test if a process (@c handle obtained from getProcessHandle()) is still
- * running. Return same nonzero @c handle value if still running, else
- * zero, so you can test it like a bool. But if you want to update a
- * stored variable as a side effect, you can write code like this:
- * @code
- * hchild = LLProcess::isRunning(hchild);
- * @endcode
- * @note This method is intended as a unit-test hook, not as the first of
- * a whole set of operations supported on freestanding @c handle values.
- * New functionality should be added as nonstatic members operating on
- * the same data as getProcessHandle().
- *
- * In particular, if child termination is detected by this static isRunning()
- * rather than by nonstatic isRunning(), the LLProcess object won't be
- * aware of the child's changed status and may encounter OS errors trying
- * to obtain it. This static isRunning() is only intended for after the
- * launching LLProcess object has been destroyed.
- */
- static handle isRunning(handle, const std::string& desc="");
+ enum FILESLOT { STDIN = 0, STDOUT = 1, STDERR = 2, NSLOTS = 3 };
- /// Provide symbolic access to child's file slots
- enum FILESLOT { STDIN=0, STDOUT=1, STDERR=2, NSLOTS=3 };
+ /// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to
+ /// create a pipe at the corresponding FILESLOT.
+ struct NoPipe : public LLException
+ {
+ NoPipe(const std::string& what) : LLException(what) {}
+ };
- /**
- * For a pipe constructed with @a type "npipe", obtain the generated OS
- * filesystem name for the specified pipe. Otherwise returns the empty
- * string. @see LLProcess::FileParam::type
- */
std::string getPipeName(FILESLOT) const;
- /// base of ReadPipe, WritePipe
+ /// Base class for pipes
class LL_COMMON_API BasePipe
{
public:
- virtual ~BasePipe() = 0;
-
+ virtual ~BasePipe() = default;
typedef std::size_t size_type;
static const size_type npos;
-
- /**
- * Get accumulated buffer length.
- *
- * For WritePipe, is there still pending data to send to child?
- *
- * For ReadPipe, we often need to refrain from actually reading the
- * std::istream returned by get_istream() until we've accumulated
- * enough data to make it worthwhile. For instance, if we're expecting
- * a number from the child, but the child happens to flush "12" before
- * emitting "3\n", get_istream() >> myint could return 12 rather than
- * 123!
- */
virtual size_type size() const = 0;
};
- /// As returned by getWritePipe() or getOptWritePipe()
- class WritePipe: public BasePipe
+ /// Write pipe for stdin
+ class WritePipe : public BasePipe
{
public:
- /**
- * Get ostream& on which to write to child's stdin.
- *
- * @usage
- * @code
- * myProcess->getWritePipe().get_ostream() << "Hello, child!" << std::endl;
- * @endcode
- */
virtual std::ostream& get_ostream() = 0;
+ // Called each mainloop tick to initiate any pending async writes.
+ virtual void tick() {}
};
- /// As returned by getReadPipe() or getOptReadPipe()
- class ReadPipe: public BasePipe
+ /// Read pipe for stdout/stderr
+ class ReadPipe : public BasePipe
{
public:
- /**
- * Get istream& on which to read from child's stdout or stderr.
- *
- * @usage
- * @code
- * std::string stuff;
- * myProcess->getReadPipe().get_istream() >> stuff;
- * @endcode
- *
- * You should be sure in advance that the ReadPipe in question can
- * fill the request. @see getPump()
- */
virtual std::istream& get_istream() = 0;
-
- /**
- * Like std::getline(get_istream(), line), but trims off trailing '\r'
- * to make calling code less platform-sensitive.
- */
virtual std::string getline() = 0;
-
- /**
- * Like get_istream().read(buffer, n), but returns std::string rather
- * than requiring caller to construct a buffer, etc.
- */
virtual std::string read(size_type len) = 0;
+ virtual std::string peek(size_type offset = 0, size_type len = npos) const = 0;
- /**
- * Peek at accumulated buffer data without consuming it. Optional
- * parameters give you substr() functionality.
- *
- * @note You can discard buffer data using get_istream().ignore(n).
- */
- virtual std::string peek(size_type offset=0, size_type len=npos) const = 0;
-
- /**
- * Detect presence of a substring (or char) in accumulated buffer data
- * without retrieving it. Optional offset allows you to search from
- * specified position.
- */
template <typename SEEK>
- bool contains(SEEK seek, size_type offset=0) const
- { return find(seek, offset) != npos; }
-
- /**
- * Search for a substring in accumulated buffer data without
- * retrieving it. Returns size_type position at which found, or npos
- * meaning not found. Optional offset allows you to search from
- * specified position.
- */
- virtual size_type find(const std::string& seek, size_type offset=0) const = 0;
+ bool contains(SEEK seek, size_type offset = 0) const
+ {
+ return find(seek, offset) != npos;
+ }
- /**
- * Search for a char in accumulated buffer data without retrieving it.
- * Returns size_type position at which found, or npos meaning not
- * found. Optional offset allows you to search from specified
- * position.
- */
- virtual size_type find(char seek, size_type offset=0) const = 0;
+ virtual size_type find(const std::string& seek, size_type offset = 0) const = 0;
+ virtual size_type find(char seek, size_type offset = 0) const = 0;
- /**
- * Get LLEventPump& on which to listen for incoming data. The posted
- * LLSD::Map event will contain:
- *
- * - "data" part of pending data; see setLimit()
- * - "len" entire length of pending data, regardless of setLimit()
- * - "slot" this ReadPipe's FILESLOT, e.g. LLProcess::STDOUT
- * - "name" e.g. "stdout"
- * - "desc" e.g. "SLPlugin (pid) stdout"
- * - "eof" @c true means there no more data will arrive on this pipe,
- * therefore no more events on this pump
- *
- * If the child sends "abc", and this ReadPipe posts "data"="abc", but
- * you don't consume it by reading the std::istream returned by
- * get_istream(), and the child next sends "def", ReadPipe will post
- * "data"="abcdef".
- */
virtual LLEventPump& getPump() = 0;
-
- /**
- * Set maximum length of buffer data that will be posted in the LLSD
- * announcing arrival of new data from the child. If you call
- * setLimit(5), and the child sends "abcdef", the LLSD event will
- * contain "data"="abcde". However, you may still read the entire
- * "abcdef" from get_istream(): this limit affects only the size of
- * the data posted with the LLSD event. If you don't call this method,
- * @em no data will be posted: the default is 0 bytes.
- */
virtual void setLimit(size_type limit) = 0;
-
- /**
- * Query the current setLimit() limit.
- */
virtual size_type getLimit() const = 0;
+ // True once the pipe has observed child-process EOF.
+ virtual bool atEOF() const = 0;
};
- /// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to
- /// create a pipe at the corresponding FILESLOT.
- struct NoPipe: public LLException
- {
- NoPipe(const std::string& what): LLException(what) {}
- };
-
- /**
- * Get a reference to the (only) WritePipe for this LLProcess. @a slot, if
- * specified, must be STDIN. Throws NoPipe if you did not request a "pipe"
- * for child stdin. Use this method when you know how you created the
- * LLProcess in hand.
- */
- WritePipe& getWritePipe(FILESLOT slot=STDIN);
+ WritePipe& getWritePipe(FILESLOT slot = STDIN);
+ ReadPipe& getReadPipe(FILESLOT index);
+ WritePipe* getOptWritePipe(FILESLOT slot = STDIN);
+ ReadPipe* getOptReadPipe(FILESLOT index);
- /**
- * Get a boost::optional<WritePipe&> to the (only) WritePipe for this
- * LLProcess. @a slot, if specified, must be STDIN. The return value is
- * empty if you did not request a "pipe" for child stdin. Use this method
- * for inspecting an LLProcess you did not create.
- */
- boost::optional<WritePipe&> getOptWritePipe(FILESLOT slot=STDIN);
+ static std::string basename(const std::string& path);
+ static std::string getline(std::istream& in);
- /**
- * Get a reference to one of the ReadPipes for this LLProcess. @a slot, if
- * specified, must be STDOUT or STDERR. Throws NoPipe if you did not
- * request a "pipe" for child stdout or stderr. Use this method when you
- * know how you created the LLProcess in hand.
- */
- ReadPipe& getReadPipe(FILESLOT slot);
+ // Constructor is public for the sake of make_shared
+ // but create() should be used instead for proper initialization.
+ LLProcess(const Params& params);
- /**
- * Get a boost::optional<ReadPipe&> to one of the ReadPipes for this
- * LLProcess. @a slot, if specified, must be STDOUT or STDERR. The return
- * value is empty if you did not request a "pipe" for child stdout or
- * stderr. Use this method for inspecting an LLProcess you did not create.
- */
- boost::optional<ReadPipe&> getOptReadPipe(FILESLOT slot);
+private:
+ void launch(const LLSDOrParams& params);
+ void connectMainloop();
+ void tick();
+ void handleExit(Status exitStatus);
- /// little utilities that really should already be somewhere else in the
- /// code base
- static std::string basename(const std::string& path);
- static std::string getline(std::istream&);
+ // Boost.Process v2 components
+ boost::asio::io_context mIOContext;
+ std::unique_ptr<boost::process::v2::process> mChild;
- // Non-copyable
- LLProcess(const LLProcess&) = delete;
- LLProcess& operator=(const LLProcess&) = delete;
+ // Pipes - using Boost.Asio pipes directly (v2 no longer has async_pipe)
+ // From parent's perspective: write to stdin (writable_pipe), read from stdout/stderr (readable_pipe)
+ // std::shared_ptr so WritePipeImpl/ReadPipeImpl keep the pipe alive as
+ // long as async operations are in flight
+ std::shared_ptr<boost::asio::writable_pipe> mStdinPipe;
+ std::shared_ptr<boost::asio::readable_pipe> mStdoutPipe;
+ std::shared_ptr<boost::asio::readable_pipe> mStderrPipe;
-private:
- /// constructor is private: use create() instead
- LLProcess(const LLSDOrParams& params);
- void autokill();
- // Classic-C-style APR callback
- static void status_callback(int reason, void* data, int status);
- // Object-oriented callback
- void handle_status(int reason, int status);
- // implementation for get[Opt][Read|Write]Pipe()
- template <class PIPETYPE>
- PIPETYPE& getPipe(FILESLOT slot);
- template <class PIPETYPE>
- boost::optional<PIPETYPE&> getOptPipe(FILESLOT slot);
- template <class PIPETYPE>
- PIPETYPE* getPipePtr(std::string& error, FILESLOT slot);
+ // Our pipe wrapper implementations
+ std::unique_ptr<WritePipe> mWritePipe;
+ std::unique_ptr<ReadPipe> mStdoutReadPipe;
+ std::unique_ptr<ReadPipe> mStderrReadPipe;
+ Status mStatus;
std::string mDesc;
std::string mPostend;
- apr_proc_t mProcess;
- bool mAutokill, mAttached;
- Status mStatus;
- // explicitly want this ptr_vector to be able to store NULLs
- typedef std::vector<std::unique_ptr<BasePipe>> PipeVector;
- PipeVector mPipes;
- apr_pool_t* mPool;
+ bool mAutokill;
+ bool mAttached;
+ bool mKillCalled;
+
+ // For integrating with LLEventPump mainloop
+ boost::signals2::scoped_connection mMainloopConnection;
};
/// for logging
diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp
index efeeb1340e..8c740956b3 100644
--- a/indra/llcommon/llqueuedthread.cpp
+++ b/indra/llcommon/llqueuedthread.cpp
@@ -222,6 +222,33 @@ void LLQueuedThread::waitOnPending()
return;
}
+void LLQueuedThread::waitOnPending(F32 max_time_sec)
+{
+ LLTimer wait_timer;
+ wait_timer.reset();
+ while (1)
+ {
+ update(0);
+
+ if (mIdleThread)
+ {
+ break;
+ }
+
+ if (wait_timer.getElapsedTimeF64() >= max_time_sec)
+ {
+ LL_WARNS() << "waitOnPending() timed out after " << max_time_sec
+ << " seconds in thread: " << mName << LL_ENDL;
+ break;
+ }
+
+ if (mThreaded)
+ {
+ yield();
+ }
+ }
+}
+
// MAIN thread
void LLQueuedThread::printQueueStats()
{
diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h
index de50b8ae95..95f0e58fa8 100644
--- a/indra/llcommon/llqueuedthread.h
+++ b/indra/llcommon/llqueuedthread.h
@@ -141,6 +141,7 @@ public:
size_t updateQueue(F32 max_time_ms);
void waitOnPending();
+ void waitOnPending(F32 max_time_sec);
void printQueueStats();
virtual size_t getPending();
diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp
index f399c51608..af79fabe81 100644
--- a/indra/llcommon/llsdserialize_xml.cpp
+++ b/indra/llcommon/llsdserialize_xml.cpp
@@ -63,15 +63,80 @@ S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr,
EFormatterOptions options) const
{
std::streamsize old_precision = ostr.precision(25);
+ std::ios_base::iostate old_exceptions = ostr.exceptions();
+ // Merged exception mask: preserve the caller's bits and add failbit|badbit
+ // for I/O error detection, so we never drop bits the caller already enabled.
+ std::ios_base::iostate new_exceptions =
+ old_exceptions | std::ios_base::badbit | std::ios_base::failbit;
+ // Bits we are newly adding (not already in the caller's mask).
+ std::ios_base::iostate added_bits = new_exceptions & ~old_exceptions;
- std::string post;
- if (options & LLSDFormatter::OPTIONS_PRETTY)
+ // If the stream already has error-state bits that we would newly add to the
+ // exception mask, enabling those bits would throw immediately; bail out early.
+ if (added_bits && (ostr.rdstate() & added_bits))
{
- post = "\n";
+ LL_WARNS() << "LLSDXMLFormatter::format: Stream already in error state" << LL_ENDL;
+ ostr.precision(old_precision);
+ return -1;
+ }
+
+ S32 rv = 0;
+
+ try
+ {
+ // Enable the merged exception mask to detect I/O errors during formatting.
+ if (added_bits)
+ {
+ ostr.exceptions(new_exceptions);
+ }
+
+ std::string post;
+ if (options & LLSDFormatter::OPTIONS_PRETTY)
+ {
+ post = "\n";
+ }
+ ostr << "<llsd>" << post;
+ rv = format_impl(data, ostr, options, 1);
+ ostr << "</llsd>\n";
+ }
+ catch (const std::ios_base::failure& e)
+ {
+ LL_WARNS() << "LLSDXMLFormatter::format: Stream I/O exception: " << e.what()
+ << " - Stream state: good=" << ostr.good()
+ << " eof=" << ostr.eof()
+ << " fail=" << ostr.fail()
+ << " bad=" << ostr.bad() << LL_ENDL;
+ rv = -1;
+ }
+ catch (const std::bad_alloc&)
+ {
+ // we might be saving something massive, don't error or crash
+ LL_WARNS() << "LLSDXMLFormatter::format: Memory allocation failed during formatting" << LL_ENDL;
+ rv = -1;
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS() << "LLSDXMLFormatter::format: Standard exception: " << e.what() << LL_ENDL;
+ rv = -1;
+ }
+ catch (...)
+ {
+ LL_WARNS() << "LLSDXMLFormatter::format: Unknown exception during formatting" << LL_ENDL;
+ rv = -1;
+ }
+
+ // Restore original exception mask. First set to goodbit (never throws) so
+ // the subsequent restore call won't immediately throw if the stream is in
+ // error state for bits in old_exceptions.
+ try
+ {
+ ostr.exceptions(std::ios_base::goodbit);
+ ostr.exceptions(old_exceptions);
+ }
+ catch (...)
+ {
+ LL_WARNS() << "LLSDXMLFormatter::format: failed to restore exceptions" << LL_ENDL;
}
- ostr << "<llsd>" << post;
- S32 rv = format_impl(data, ostr, options, 1);
- ostr << "</llsd>\n";
ostr.precision(old_precision);
return rv;
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index bae80a3fb9..277edcb6a2 100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -310,11 +310,13 @@ void LLThread::shutdown()
// The thread isn't already stopped
// First, set the flag that indicates that we're ready to die
setQuitting();
+ if (!isStopped())
+ {
+ // Give the thread a chance to update status.
+ yield();
+ }
//LL_INFOS() << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << LL_ENDL;
- // Now wait a bit for the thread to exit
- // It's unclear whether I should even bother doing this - this destructor
- // should never get called unless we're already stopped, really...
S32 counter = 0;
const S32 MAX_WAIT = 600;
while (counter < MAX_WAIT)
@@ -323,9 +325,9 @@ void LLThread::shutdown()
{
break;
}
- // Sleep for a tenth of a second
- ms_sleep(100);
- yield();
+ // Sleep for 10ms, 6 seconds total, then give up and kill the thread
+ // Warning: This can be called from the main thread
+ ms_sleep(10);
counter++;
}
}
diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp
index 66b565c763..ad7c2db62e 100644
--- a/indra/llcommon/llwatchdog.cpp
+++ b/indra/llcommon/llwatchdog.cpp
@@ -51,6 +51,7 @@ public:
{
mStopping = true;
mSleepMsecs = 1;
+ setQuitting();
}
void run() override
@@ -116,6 +117,16 @@ bool LLWatchdogTimeout::isAlive() const
return (mTimer.getStarted() && !mTimer.hasExpired());
}
+bool LLWatchdogTimeout::hasExpired() const
+{
+ return mTimer.hasExpired();
+}
+
+bool LLWatchdogTimeout::started() const
+{
+ return mTimer.getStarted();
+}
+
void LLWatchdogTimeout::reset()
{
mTimer.setTimerExpirySec(mTimeout);
@@ -220,11 +231,21 @@ void LLWatchdog::init(
mCrashOnFreeze = crash_on_freeze;
}
+void LLWatchdog::shutdown()
+{
+ if (mTimer)
+ {
+ mTimer->stop();
+ }
+}
+
void LLWatchdog::cleanup()
{
+ LL_PROFILE_ZONE_SCOPED;
if (mTimer)
{
mTimer->stop();
+ mTimer->shutdown();
delete mTimer;
mTimer = nullptr;
}
diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h
index f138fbccb0..a3a51113b0 100644
--- a/indra/llcommon/llwatchdog.h
+++ b/indra/llcommon/llwatchdog.h
@@ -47,6 +47,8 @@ public:
// This may mean that resources used by
// isAlive and other method may need synchronization.
virtual bool isAlive() const = 0;
+ virtual bool hasExpired() const = 0;
+ virtual bool started() const = 0;
virtual void reset() = 0;
virtual void start();
virtual void stop();
@@ -66,6 +68,8 @@ public:
virtual ~LLWatchdogTimeout();
bool isAlive() const override;
+ bool hasExpired() const override;
+ bool started() const override;
void reset() override;
void start() override { start(""); }
void stop() override;
@@ -105,6 +109,7 @@ public:
bool crash_on_freeze);
void run();
void cleanup();
+ void shutdown();
private:
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 5e653d0ba0..dfc34373da 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -17,7 +17,9 @@
#include <vector>
#include <list>
// std headers
+#include <chrono>
#include <fstream>
+#include <thread>
// external library headers
#include "llapr.h"
#include "apr_thread_proc.h"
@@ -108,6 +110,19 @@ static std::string readfile(const std::filesystem::path& pathname, const std::st
return output;
}
+#if LL_WINDOWS
+static std::string readfile_if_present(const std::string& pathname)
+{
+ std::ifstream inf(pathname.c_str());
+ if (!inf.is_open())
+ {
+ return "";
+ }
+ return std::string((std::istreambuf_iterator<char>(inf)),
+ std::istreambuf_iterator<char>());
+}
+#endif
+
/// Looping on LLProcess::isRunning() must now be accompanied by pumping
/// "mainloop" -- otherwise the status won't update and you get an infinite
/// loop.
@@ -118,6 +133,9 @@ void yield(int seconds=1)
LLEventPumps::instance().obtain("mainloop").post(LLSD());
}
+constexpr int EOF_EVENT_RETRY_COUNT = 20;
+constexpr auto EOF_EVENT_RETRY_DELAY = std::chrono::milliseconds(50);
+
void waitfor(LLProcess& proc, int timeout=60)
{
int i = 0;
@@ -246,6 +264,215 @@ struct PythonProcessLauncher
NamedExtTempFile mScript;
};
+#if LL_WINDOWS
+namespace
+{
+ static constexpr const char* AUTOKILL_HELPER_SCRIPT_ENV = "LLPROCESS_AUTOKILL_HELPER_SCRIPT";
+ static constexpr const char* AUTOKILL_HELPER_PIDFILE_ENV = "LLPROCESS_AUTOKILL_HELPER_PIDFILE";
+ static constexpr const char* AUTOKILL_HELPER_RELEASE_ENV = "LLPROCESS_AUTOKILL_HELPER_RELEASE";
+ static constexpr const char* AUTOKILL_HELPER_COUNT_ENV = "LLPROCESS_AUTOKILL_HELPER_COUNT";
+ static constexpr int AUTOKILL_HELPER_RELEASE_TIMEOUT_SECONDS = 60;
+ static constexpr int AUTOKILL_HELPER_PID_TIMEOUT_SECONDS = 15;
+ static constexpr DWORD AUTOKILL_HELPER_POLL_INTERVAL_MS = 100;
+ static constexpr DWORD AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS = 5000;
+ static constexpr int AUTOKILL_HELPER_INVALID_ENV_EXIT = 2;
+ static constexpr int AUTOKILL_HELPER_INVALID_COUNT_EXIT = 3;
+ static constexpr int AUTOKILL_HELPER_LAUNCH_FAILURE_EXIT = 4;
+
+ struct ScopedEnvironmentVariable
+ {
+ ScopedEnvironmentVariable(const char* name, const std::string& value):
+ mName(name),
+ mHadValue(false)
+ {
+ DWORD size = GetEnvironmentVariableA(name, nullptr, 0);
+ if (size > 0)
+ {
+ std::vector<char> buffer(size);
+ DWORD copied = GetEnvironmentVariableA(name, buffer.data(), size);
+ if (copied > 0)
+ {
+ mHadValue = true;
+ mOldValue.assign(buffer.data(), copied);
+ }
+ }
+ SetEnvironmentVariableA(name, value.c_str());
+ }
+
+ ~ScopedEnvironmentVariable()
+ {
+ SetEnvironmentVariableA(mName.c_str(), mHadValue ? mOldValue.c_str() : nullptr);
+ }
+
+ std::string mName;
+ std::string mOldValue;
+ bool mHadValue;
+ };
+
+ std::string get_current_executable_path()
+ {
+ std::vector<char> buffer(MAX_PATH);
+ for (;;)
+ {
+ DWORD length = GetModuleFileNameA(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
+ tut::ensure("GetModuleFileNameA() failed", length > 0);
+ if (length < buffer.size() &&
+ (length < buffer.size() - 1 || buffer[length] == '\0'))
+ {
+ return std::string(buffer.data(), length);
+ }
+ buffer.resize(buffer.size() * 2);
+ }
+ }
+
+ void run_autokill_helper_from_environment()
+ {
+ const std::string script = LLStringUtil::getenv(AUTOKILL_HELPER_SCRIPT_ENV);
+ if (script.empty())
+ {
+ return;
+ }
+
+ const std::string pidfile = LLStringUtil::getenv(AUTOKILL_HELPER_PIDFILE_ENV);
+ const std::string releasefile = LLStringUtil::getenv(AUTOKILL_HELPER_RELEASE_ENV);
+ const std::string countstr = LLStringUtil::getenv(AUTOKILL_HELPER_COUNT_ENV);
+ const std::string python = LLStringUtil::getenv("PYTHON");
+ int child_count = 1;
+ if (!countstr.empty())
+ {
+ try
+ {
+ child_count = std::stoi(countstr);
+ }
+ catch (const std::exception& err)
+ {
+ LL_WARNS("LLProcess") << "Invalid autokill helper child count '"
+ << countstr << "': " << err.what() << LL_ENDL;
+ std::exit(AUTOKILL_HELPER_INVALID_COUNT_EXIT);
+ }
+ }
+
+ if (pidfile.empty() || releasefile.empty() || python.empty() || child_count < 1)
+ {
+ std::exit(AUTOKILL_HELPER_INVALID_ENV_EXIT);
+ }
+
+ std::vector<LLProcessPtr> children;
+ children.reserve(child_count);
+ std::ofstream out(pidfile.c_str(), std::ios::trunc);
+ for (int i = 0; i < child_count; ++i)
+ {
+ LLProcess::Params params;
+ params.executable = python;
+ params.args.add(script);
+ params.autokill = true;
+ params.attached = false;
+ LLProcessPtr child = LLProcess::create(params);
+ if (!child)
+ {
+ std::exit(AUTOKILL_HELPER_LAUNCH_FAILURE_EXIT);
+ }
+ children.push_back(child);
+ out << child->getProcessID() << '\n';
+ }
+ out.flush();
+ out.close();
+
+ for (DWORD elapsed_ms = 0;
+ elapsed_ms < AUTOKILL_HELPER_RELEASE_TIMEOUT_SECONDS * 1000;
+ elapsed_ms += AUTOKILL_HELPER_POLL_INTERVAL_MS)
+ {
+ if (readfile_if_present(releasefile) == "exit")
+ {
+ break;
+ }
+ Sleep(AUTOKILL_HELPER_POLL_INTERVAL_MS);
+ }
+
+ // Exit the helper process itself so the job handle closes and Windows
+ // terminates the autokilled children.
+ std::exit(0);
+ }
+
+ std::vector<DWORD> wait_for_helper_pids(
+ const std::string& pidfile,
+ int expected_count,
+ int timeout = AUTOKILL_HELPER_PID_TIMEOUT_SECONDS)
+ {
+ for (int i = 0;
+ i < (timeout * 1000) / static_cast<int>(AUTOKILL_HELPER_POLL_INTERVAL_MS);
+ ++i)
+ {
+ std::ifstream inf(pidfile.c_str());
+ std::vector<DWORD> pids;
+ DWORD pid = 0;
+ while (inf >> pid)
+ {
+ pids.push_back(pid);
+ }
+ if (static_cast<int>(pids.size()) == expected_count)
+ {
+ return pids;
+ }
+ Sleep(AUTOKILL_HELPER_POLL_INTERVAL_MS);
+ LLEventPumps::instance().obtain("mainloop").post(LLSD());
+ }
+ tut::ensure(STRINGIZE("expected " << expected_count
+ << " child pids within " << timeout
+ << " seconds"), false);
+ return {};
+ }
+
+ void verify_autokill_on_helper_exit(const std::string& desc, int child_count)
+ {
+ NamedExtTempFile child_script("py",
+ "import time\n"
+ "time.sleep(30)\n");
+ NamedTempFile pidfile("pid", "");
+ NamedTempFile releasefile("release", "");
+
+ ScopedEnvironmentVariable helper_script(AUTOKILL_HELPER_SCRIPT_ENV, child_script.getPath().string());
+ ScopedEnvironmentVariable helper_pidfile(AUTOKILL_HELPER_PIDFILE_ENV, pidfile.getPath().string());
+ ScopedEnvironmentVariable helper_release(AUTOKILL_HELPER_RELEASE_ENV, releasefile.getPath().string());
+ ScopedEnvironmentVariable helper_count(AUTOKILL_HELPER_COUNT_ENV, std::to_string(child_count));
+
+ LLProcess::Params params;
+ params.executable = get_current_executable_path();
+ params.desc = desc + " helper";
+ LLProcessPtr helper = LLProcess::create(params);
+ tut::ensure("helper launched", bool(helper));
+
+ std::vector<DWORD> pids = wait_for_helper_pids(pidfile.getPath().string(), child_count);
+ std::vector<HANDLE> handles;
+ handles.reserve(pids.size());
+ for (DWORD pid : pids)
+ {
+ // SYNCHRONIZE lets the test wait for the child to terminate, while
+ // PROCESS_QUERY_LIMITED_INFORMATION keeps the requested access minimal.
+ HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
+ tut::ensure(STRINGIZE("opened child process handle for pid " << pid), handle != nullptr);
+ handles.push_back(handle);
+ }
+
+ {
+ std::ofstream out(releasefile.getPath(), std::ios::trunc);
+ out << "exit";
+ }
+
+ waitfor(*helper);
+ tut::ensure_equals("helper exited", helper->getStatus().mState, LLProcess::EXITED);
+
+ for (HANDLE handle : handles)
+ {
+ tut::ensure_equals("autokilled child exited",
+ WaitForSingleObject(handle, AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS),
+ WAIT_OBJECT_0);
+ CloseHandle(handle);
+ }
+ }
+}
+#endif
+
/// convenience function for PythonProcessLauncher::run()
template <typename CONTENT>
static void python(const std::string& desc, const CONTENT& script)
@@ -298,6 +525,13 @@ namespace tut
{
struct llprocess_data
{
+ llprocess_data()
+ {
+#if LL_WINDOWS
+ run_autokill_helper_from_environment();
+#endif
+ }
+
LLAPRPool pool;
};
typedef test_group<llprocess_data> llprocess_group;
@@ -335,7 +569,7 @@ namespace tut
WaitInfo(apr_proc_t* child_):
child(child_),
rv(-1), // we haven't yet called apr_proc_wait()
- rc(0),
+ rc(0), // child's exit code
why(apr_exit_why_e(0))
{}
apr_proc_t* child; // which subprocess
@@ -1220,6 +1454,14 @@ namespace tut
LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT));
EventListener listener(childout.getPump());
waitfor(*py.mPy);
+ // On Windows the pipe-close EOF notification can trail the process exit
+ // status by a short interval, so keep pumping for up to 1 second
+ // (20 * 50 ms) until it arrives.
+ for (int i = 0; i < EOF_EVENT_RETRY_COUNT && listener.mHistory.empty(); ++i)
+ {
+ std::this_thread::sleep_for(EOF_EVENT_RETRY_DELAY);
+ LLEventPumps::instance().obtain("mainloop").post(LLSD());
+ }
// We can't be positive there will only be a single event, if the OS
// (or any other intervening layer) does crazy buffering. What we want
// to ensure is that there was exactly ONE event with "eof" true, and
@@ -1431,4 +1673,396 @@ namespace tut
waitfor(*py.mPy);
ensure("postend never triggered", listener.mTriggered);
}
+
+ template<> template<>
+ void object::test<25>()
+ {
+ set_test_name("large stdin write");
+ PythonProcessLauncher py(get_test_name(),
+ "import sys\n"
+ "expected = int(sys.argv[1])\n"
+ "data = sys.stdin.buffer.read(expected)\n"
+ "if len(data) != expected:\n"
+ " print('short read %s' % len(data))\n"
+ "elif data != (b'x' * expected):\n"
+ " print('payload mismatch')\n"
+ "else:\n"
+ " print('ok')\n");
+ const std::size_t payload_size = 1024 * 1024;
+ const std::string payload(payload_size, 'x');
+ py.mParams.args.add(stringize(payload_size));
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout
+ py.launch();
+
+ LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT));
+ std::ostream& childin(py.mPy->getWritePipe(LLProcess::STDIN).get_ostream());
+ childin.write(payload.data(), payload.size());
+ childin.flush();
+
+ int i, timeout = 20;
+ for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i)
+ {
+ yield();
+ }
+ ensure("large stdin write timed out", i < timeout);
+ ensure("child never replied", childout.contains("\n"));
+ ensure_equals("large stdin ack", childout.getline(), "ok");
+ waitfor(*py.mPy);
+ ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED);
+ ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0);
+ }
+
+ template<> template<>
+ void object::test<26>()
+ {
+ set_test_name("all three pipes active");
+ PythonProcessLauncher py(get_test_name(),
+ "import sys\n"
+ "sys.stdout.write('stdout message\\n')\n"
+ "sys.stderr.write('stderr message\\n')\n"
+ "sys.stdout.flush()\n"
+ "sys.stderr.flush()\n"
+ "input_data = sys.stdin.readline()\n"
+ "sys.stdout.write('received: ' + input_data)\n");
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stderr
+ py.launch();
+
+ LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT);
+ LLProcess::ReadPipe& childerr = py.mPy->getReadPipe(LLProcess::STDERR);
+
+ // Wait for initial output
+ int i, timeout = 60;
+ for (i = 0; i < timeout && (!childout.contains("\n") || !childerr.contains("\n")); ++i)
+ {
+ yield();
+ }
+ ensure("initial output timeout", i < timeout);
+
+ ensure_equals("stdout message", childout.getline(), "stdout message");
+ ensure_equals("stderr message", childerr.getline(), "stderr message");
+
+ py.mPy->getWritePipe().get_ostream() << "test input" << std::endl;
+
+ waitfor(*py.mPy);
+ ensure("script never replied", childout.contains("\n"));
+ ensure_equals("echo response", childout.getline(), "received: test input");
+ }
+
+ template<> template<>
+ void object::test<27>()
+ {
+ set_test_name("process state transitions");
+ PythonProcessLauncher py(get_test_name(),
+ "import time\n"
+ "time.sleep(1)\n");
+
+ py.launch();
+
+ // Immediately after launch
+ LLProcess::Status status = py.mPy->getStatus();
+ ensure_equals("post-launch state", status.mState, LLProcess::RUNNING);
+ ensure("process is running", py.mPy->isRunning());
+
+ // After completion
+ waitfor(*py.mPy);
+ status = py.mPy->getStatus();
+ ensure_equals("post-completion state", status.mState, LLProcess::EXITED);
+ ensure("process is not running", !py.mPy->isRunning());
+ }
+
+ template<> template<>
+ void object::test<28>()
+ {
+ set_test_name("ReadPipe limit with rapid data");
+ PythonProcessLauncher py(get_test_name(),
+ "import sys\n"
+ "for i in range(100):\n"
+ " sys.stdout.write('line %d\\n' % i)\n"
+ " sys.stdout.flush()\n");
+ py.mParams.files.add(LLProcess::FileParam()); // stdin
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout
+ py.launch();
+
+ LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT);
+ childout.setLimit(50); // Small limit
+
+ EventListener listener(childout.getPump());
+ waitfor(*py.mPy);
+
+ // Verify that events respect the limit
+ listener.checkHistory(
+ [](const EventListener::Listory& history)
+ {
+ bool saw_data = false;
+ for (const LLSD& event : history)
+ {
+ const std::string data = event["data"].asString();
+ ensure("event data within limit", data.length() <= 50);
+ saw_data = saw_data || !data.empty();
+ }
+ ensure("saw at least one data event", saw_data);
+ });
+ }
+
+ template<> template<>
+ void object::test<29>()
+ {
+ set_test_name("nonexistent executable");
+ LLProcess::Params params;
+ params.executable = "/path/to/nonexistent/executable";
+ std::string pumpname("postend_invalid");
+ params.postend = pumpname;
+
+ EventListener listener(LLEventPumps::instance().obtain(pumpname));
+
+ LLProcessPtr child = LLProcess::create(params);
+ ensure("should not create invalid process", !child);
+
+ listener.checkHistory(
+ [](const EventListener::Listory& history)
+ {
+ ensure_equals("got failure event", history.size(), 1);
+ LLSD event = history.front();
+ ensure_equals("state is UNSTARTED",
+ event["state"].asInteger(), LLProcess::UNSTARTED);
+ ensure("has error string", !event["string"].asString().empty());
+ });
+ }
+
+ template<> template<>
+ void object::test<30>()
+ {
+ set_test_name("process ID and handle validity");
+ PythonProcessLauncher py(get_test_name(),
+ "import time\n"
+ "time.sleep(1)\n");
+ py.launch();
+
+ LLProcess::id pid = py.mPy->getProcessID();
+ LLProcess::handle handle = py.mPy->getProcessHandle();
+
+ ensure("PID is valid", pid != 0);
+ ensure("handle is valid", handle != 0);
+
+#if LL_WINDOWS
+ // On Windows, verify handle is a valid process handle
+ DWORD exitCode;
+ ensure("GetExitCodeProcess succeeds",
+ GetExitCodeProcess(handle, &exitCode) != 0);
+ ensure_equals("process still running", exitCode, STILL_ACTIVE);
+#else
+ // On POSIX, PID and handle should be the same
+ ensure_equals("PID equals handle", pid, handle);
+ ensure("process still running", py.mPy->isRunning());
+#endif
+
+ waitfor(*py.mPy);
+ }
+
+ template<> template<>
+ void object::test<31>()
+ {
+ set_test_name("ReadPipe search at boundaries");
+ PythonProcessLauncher py(get_test_name(),
+ "import sys\n"
+ "sys.stdout.write('abcdefghijklmnopqrstuvwxyz')\n");
+ py.mParams.files.add(LLProcess::FileParam()); // stdin
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout
+ py.launch();
+
+ LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT);
+ waitfor(*py.mPy);
+
+ // Test find at exact end
+ ensure("find at end succeeds",
+ childout.find("xyz", 23) != LLProcess::ReadPipe::npos);
+ ensure("find past end returns npos",
+ childout.find("a", 30) == LLProcess::ReadPipe::npos);
+
+ // Test empty string search
+ ensure("contains empty string", childout.contains(""));
+
+ // Test single char at boundaries
+ ensure_equals("find 'a' at 0", childout.find('a', 0), 0);
+ ensure_equals("find 'z' at end", childout.find('z'), 25);
+
+ // Test peek at boundaries
+ ensure_equals("peek at exact size", childout.peek(26), "");
+ ensure_equals("peek past end", childout.peek(30, 10), "");
+ }
+
+ template<> template<>
+ void object::test<32>()
+ {
+ set_test_name("rapid process lifecycle");
+ const int ITERATIONS = 10;
+
+ for (int i = 0; i < ITERATIONS; ++i)
+ {
+ PythonProcessLauncher py(STRINGIZE(get_test_name() << " " << i),
+ "import sys\n"
+ "sys.exit(0)\n");
+ py.run();
+ ensure_equals("quick exit status",
+ py.mPy->getStatus().mState, LLProcess::EXITED);
+ ensure_equals("quick exit code",
+ py.mPy->getStatus().mData, 0);
+ // Let the process object destroy
+ }
+
+ // Give time for cleanup
+ yield(0);
+ }
+
+ template<> template<>
+ void object::test<33>()
+ {
+ set_test_name("process with no output");
+ PythonProcessLauncher py(get_test_name(),
+ "import time\n"
+ "time.sleep(1)\n"
+ "# Produce no output\n");
+ py.mParams.files.add(LLProcess::FileParam()); // stdin
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout
+ py.mParams.files.add(LLProcess::FileParam("pipe")); // stderr
+ py.launch();
+
+ LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT);
+ LLProcess::ReadPipe& childerr = py.mPy->getReadPipe(LLProcess::STDERR);
+
+ EventListener outListener(childout.getPump());
+ EventListener errListener(childerr.getPump());
+
+ waitfor(*py.mPy);
+ // On Windows the pipe-close EOF notification can trail the process exit
+ // status by a short interval, so keep pumping for up to 1 second
+ // (20 * 50 ms) until both pipes report it.
+ for (int i = 0;
+ i < EOF_EVENT_RETRY_COUNT &&
+ (outListener.mHistory.empty() || errListener.mHistory.empty());
+ ++i)
+ {
+ std::this_thread::sleep_for(EOF_EVENT_RETRY_DELAY);
+ LLEventPumps::instance().obtain("mainloop").post(LLSD());
+ }
+
+ ensure_equals("stdout size", childout.size(), 0);
+ ensure_equals("stderr size", childerr.size(), 0);
+ ensure_equals("process exited", py.mPy->getStatus().mState, LLProcess::EXITED);
+
+ auto check_eof = [](const EventListener::Listory& history, const std::string& which)
+ {
+ ensure_equals(STRINGIZE(which << " events"), history.size(), 1);
+ const LLSD& event = history.front();
+ ensure(STRINGIZE(which << " eof event"), event["eof"].asBoolean());
+ ensure_equals(STRINGIZE(which << " len"), event["len"].asInteger(), 0);
+ };
+
+ outListener.checkHistory(
+ [&](const EventListener::Listory& history)
+ {
+ check_eof(history, "stdout");
+ });
+
+ errListener.checkHistory(
+ [&](const EventListener::Listory& history)
+ {
+ check_eof(history, "stderr");
+ });
+ }
+
+ template<> template<>
+ void object::test<34>()
+ {
+ set_test_name("tick() completes quickly after kill()");
+ // Regression test: tick() must not block after kill() is called.
+ // The old Windows code called WaitForSingleObject(..., 100) in tick(),
+ // causing a 100 ms stall on the main thread. This test ensures that a
+ // mainloop tick completes well under that threshold even when the child
+ // process has been killed and may still be exiting.
+ PythonProcessLauncher py(get_test_name(),
+ "import time\n"
+ "time.sleep(120)\n");
+ py.launch();
+
+ // Wait for the process to start up
+ yield();
+ ensure("process started", py.mPy->isRunning());
+
+ // Send the kill signal
+ py.mPy->kill();
+
+ // Sleep longer than the tick threshold to ensure the child has had
+ // time to exit at the OS level, so the next tick is likely to enter
+ // the "process just exited" code path that used to block for 100 ms.
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ // Time a single mainloop tick: it must not block
+ auto start = std::chrono::steady_clock::now();
+ LLEventPumps::instance().obtain("mainloop").post(LLSD());
+ auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start).count();
+
+ ensure(STRINGIZE("tick() took " << elapsed_ms << " ms, expected < 75 ms"),
+ elapsed_ms < 75);
+
+ // Let the process fully exit so cleanup is orderly
+ waitfor(*py.mPy);
+ }
+
+ template<> template<>
+ void object::test<35>()
+ {
+ set_test_name("LLProcess destructor completes quickly after kill()");
+ // Regression test: after an explicit kill() call the destructor must
+ // not perform a blocking wait. The old Windows code called
+ // WaitForSingleObject(..., 100) in the destructor, causing a 100 ms
+ // stall on the main thread. With mKillCalled set to true, the
+ // destructor skips the termination/wait block entirely.
+ PythonProcessLauncher py(get_test_name(),
+ "import time\n"
+ "time.sleep(120)\n");
+ py.launch();
+
+ // Wait for the process to start up
+ yield();
+ ensure("process started", py.mPy->isRunning());
+
+ // Kill the process (sets mKillCalled = true)
+ py.mPy->kill();
+
+ // Time how long the destructor takes
+ auto start = std::chrono::steady_clock::now();
+ py.mPy.reset(); // explicit destruction
+ auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start).count();
+
+ ensure(STRINGIZE("destructor took " << elapsed_ms << " ms, expected < 75 ms"),
+ elapsed_ms < 75);
+ }
+
+ template<> template<>
+ void object::test<36>()
+ {
+ set_test_name("autokill ensures child termination on parent exit");
+#if !LL_WINDOWS
+ skip("Windows-specific test");
+#else
+ verify_autokill_on_helper_exit(get_test_name(), 1);
+#endif
+ }
+
+ template<> template<>
+ void object::test<37>()
+ {
+ set_test_name("multiple processes with autokill");
+#if !LL_WINDOWS
+ skip("Windows-specific test");
+#else
+ verify_autokill_on_helper_exit(get_test_name(), 2);
+#endif
+ }
} // namespace tut
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index a7abfda099..71096523a9 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -243,6 +243,58 @@ namespace tut
xml_test("binary", expected);
}
+ template<> template<>
+ void sd_xml_object::test<7>()
+ {
+ // Test that stream state (precision and exceptions) is correctly restored after format()
+ {
+ std::ostringstream ostr;
+
+ // Set custom precision
+ ostr.precision(10);
+
+ // Set some exception bits
+ ostr.exceptions(std::ios_base::badbit);
+ std::ios_base::iostate original_exceptions = ostr.exceptions();
+
+ // Format some LLSD data
+ mSD = 3.141592653589793;
+ S32 result = mFormatter->format(mSD, ostr);
+
+ // Verify formatting succeeded
+ ensure("format should succeed", result >= 0);
+
+ // Verify precision was restored
+ ensure_equals("precision should be restored",
+ ostr.precision(), 10);
+
+ // Verify exceptions were restored
+ ensure_equals("exception bits should be restored",
+ ostr.exceptions(), original_exceptions);
+ }
+
+ // Test with no bits set
+ {
+ std::ostringstream ostr;
+ ostr.precision(5);
+ std::ios_base::iostate original_exceptions = ostr.exceptions(); // 0
+
+ mSD = "test";
+ S32 result = mFormatter->format(mSD, ostr);
+
+ // Verify formatting succeeded
+ ensure("format should succeed", result >= 0);
+
+ // Verify exceptions were removed
+ ensure_equals("exception bits should remain unchanged",
+ ostr.exceptions(), original_exceptions);
+
+ // Verify precision was still restored even on failure
+ ensure_equals("precision should be restored even on stream failure",
+ ostr.precision(), (std::streamsize)5);
+ }
+ }
+
class TestLLSDSerializeData
{
public:
diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp
index 9b674d5528..e7b43e15e2 100644
--- a/indra/llfilesystem/lldiskcache.cpp
+++ b/indra/llfilesystem/lldiskcache.cpp
@@ -64,9 +64,9 @@ LLDiskCache::LLDiskCache(const std::string& cache_dir,
// WARNING: purge() is called by LLPurgeDiskCacheThread. As such it must
// NOT touch any LLDiskCache data without introducing and locking a mutex!
-// Interaction through the filesystem itself should be safe. Let’s say thread
+// Interaction through the filesystem itself should be safe. Let's say thread
// A is accessing the cache file for reading/writing and thread B is trimming
-// the cache. Let’s also assume using llifstream to open a file and
+// the cache. Let's also assume using llifstream to open a file and
// std::filesystem::remove are not atomic (which will be pretty much the
// case).
diff --git a/indra/llfilesystem/lllfsthread.cpp b/indra/llfilesystem/lllfsthread.cpp
index 6a882f64a8..69f7085290 100644
--- a/indra/llfilesystem/lllfsthread.cpp
+++ b/indra/llfilesystem/lllfsthread.cpp
@@ -51,6 +51,7 @@ S32 LLLFSThread::updateClass(U32 ms_elapsed)
//static
void LLLFSThread::cleanupClass()
{
+ LL_PROFILE_ZONE_SCOPED;
llassert(sLocal != NULL);
sLocal->setQuitting();
while (sLocal->getPending())
diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp
index 7cfadb889d..8d544b360b 100644
--- a/indra/llimagej2coj/llimagej2coj.cpp
+++ b/indra/llimagej2coj/llimagej2coj.cpp
@@ -227,11 +227,11 @@ static void opj_free_user_data_write(void * user_data)
*/
static U32 estimate_num_layers(U32 surface)
{
- if (surface <= 1024) return 2; // Tiny (≤32×32)
- else if (surface <= 16384) return 3; // Small (≤128×128)
- else if (surface <= 262144) return 4; // Medium (≤512×512)
+ if (surface <= 1024) return 2; // Tiny (<=32x32)
+ else if (surface <= 16384) return 3; // Small (<=128x128)
+ else if (surface <= 262144) return 4; // Medium (<=512x512)
else if (surface <= 1048576) return 5; // Up to ~1MP
- else return 6; // Up to ~1.5–2MP
+ else return 6; // Up to ~1.5-2MP
}
/**
diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp
index a7edc3a383..19ad49c6cc 100644
--- a/indra/llinventory/llinventory.cpp
+++ b/indra/llinventory/llinventory.cpp
@@ -33,6 +33,7 @@
#include "llxorcipher.h"
#include "llsd.h"
#include "llsdserialize.h"
+#include "llstreamtools.h"
#include "message.h"
#include <boost/tokenizer.hpp>
@@ -870,6 +871,42 @@ bool LLInventoryItem::importLegacyStream(std::istream& input_stream)
}
mDescription.assign(valuestr);
+
+ // Currently server side doesn't handle escaped newline right
+ // and they end up unescaped.
+ // And even if we do copy the data correctly, moving the item
+ // from notecard using CopyInventoryFromNotecard drops the
+ // content after first the new line.
+ //
+ // Check if the description ends with | on this line
+ if (strchr(buffer, '|') == nullptr)
+ {
+ // No | found, continue reading lines until we find one
+ char next_buffer[MAX_STRING];
+ while (input_stream.good())
+ {
+ input_stream.getline(next_buffer, MAX_STRING);
+
+ // Look for | delimiter
+ char* pipe_pos = strchr(next_buffer, '|');
+ if (pipe_pos != nullptr)
+ {
+ // Found the delimiter, add text up to it
+ *pipe_pos = '\0'; // Terminate at the pipe
+ mDescription += '\n';
+ mDescription += next_buffer;
+ break;
+ }
+ else
+ {
+ // No delimiter yet, add the whole line
+ mDescription += '\n';
+ mDescription += next_buffer;
+ }
+ }
+ }
+
+ unescape_string(mDescription);
LLStringUtil::replaceNonstandardASCII(mDescription, ' ');
/* TODO -- ask Ian about this code
const char *donkey = mDescription.c_str();
@@ -963,7 +1000,12 @@ bool LLInventoryItem::exportLegacyStream(std::ostream& output_stream, bool inclu
output_stream << buffer;
mSaleInfo.exportLegacyStream(output_stream);
output_stream << "\t\tname\t" << mName.c_str() << "|\n";
- output_stream << "\t\tdesc\t" << mDescription.c_str() << "|\n";
+ // Note: Currently server side doesn't handle newline right for notecards.
+ // Even if we escape or replace all newlines with spaces, notecard's
+ // import buffer still ends up with newlines.
+ std::string desc = mDescription;
+ escape_string(desc);
+ output_stream << "\t\tdesc\t" << desc.c_str() << "|\n";
output_stream << "\t\tcreation_date\t" << mCreationDate << "\n";
output_stream << "\t}\n";
return true;
@@ -984,12 +1026,14 @@ void LLInventoryItem::asLLSD( LLSD& sd ) const
if (mThumbnailUUID.notNull())
{
- sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID);
+ LLSD& thumbnail = sd[INV_THUMBNAIL_LABEL];
+ thumbnail[INV_ASSET_ID_LABEL] = mThumbnailUUID;
}
if (mFavorite)
{
- sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite);
+ LLSD& favorite = sd[INV_FAVORITE_LABEL];
+ favorite[INV_TOGGLED_LABEL] = mFavorite;
}
if (!mRuntime.empty())
@@ -1011,7 +1055,7 @@ void LLInventoryItem::asLLSD( LLSD& sd ) const
cipher.encrypt(shadow_id.mData, UUID_BYTES);
sd[INV_SHADOW_ID_LABEL] = shadow_id;
}
- sd[INV_ASSET_TYPE_LABEL] = std::string(LLAssetType::lookup(mType));
+ sd[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(mType);
const std::string inv_type_str = LLInventoryType::lookup(mInventoryType);
if(!inv_type_str.empty())
{
@@ -1050,217 +1094,231 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new)
end = sd.endMap();
for (i = sd.beginMap(); i != end; ++i)
{
- if (i->first == INV_ITEM_ID_LABEL)
- {
- mUUID = i->second;
- continue;
- }
+ // Use string length as a fast pre-filter before string comparison
+ const std::string& key = i->first;
+ const LLSD& value = i->second;
+ const size_t key_len = key.length();
- if (i->first == INV_PARENT_ID_LABEL)
+ switch (key_len)
{
- mParentUUID = i->second;
- continue;
- }
-
- if (i->first == INV_THUMBNAIL_LABEL)
- {
- const LLSD &thumbnail_map = i->second;
- if (thumbnail_map.has(INV_ASSET_ID_LABEL))
- {
- mThumbnailUUID = thumbnail_map[INV_ASSET_ID_LABEL];
- }
- /* Example:
- <key> asset_id </key>
- <uuid> acc0ec86 - 17f2 - 4b92 - ab41 - 6718b1f755f7 </uuid>
- <key> perms </key>
- <integer> 8 </integer>
- <key>service</key>
- <integer> 3 </integer>
- <key>version</key>
- <integer> 1 </key>
- */
- continue;
- }
+ case 4: // "name", "desc", "type"
+ if (key == INV_NAME_LABEL) // "name"
+ {
+ mName = value.asString();
+ LLStringUtil::replaceNonstandardASCII(mName, ' ');
+ LLStringUtil::replaceChar(mName, '|', ' ');
+ continue;
+ }
+ if (key == INV_DESC_LABEL) // "desc"
+ {
+ mDescription = value.asString();
+ LLStringUtil::replaceNonstandardASCII(mDescription, ' ');
+ continue;
+ }
+ if (key == INV_ASSET_TYPE_LABEL) // "type"
+ {
+ if (value.isString())
+ {
+ mType = LLAssetType::lookup(value.asStringRef().c_str());
+ }
+ else if (value.isInteger())
+ {
+ S8 type = (U8)value.asInteger();
+ mType = static_cast<LLAssetType::EType>(type);
+ }
+ continue;
+ }
+ break;
- if (i->first == INV_THUMBNAIL_ID_LABEL)
- {
- mThumbnailUUID = i->second.asUUID();
- continue;
- }
+ case 5: // "flags"
+ if (key == INV_FLAGS_LABEL)
+ {
+ if (value.isBinary())
+ {
+ mFlags = ll_U32_from_sd(value);
+ }
+ else if (value.isInteger())
+ {
+ mFlags = value.asInteger();
+ }
+ continue;
+ }
+ break;
- if (i->first == INV_FAVORITE_LABEL)
- {
- const LLSD& favorite_map = i->second;
- if (favorite_map.has(INV_TOGGLED_LABEL))
- {
- mFavorite = favorite_map[INV_TOGGLED_LABEL].asBoolean();
- }
- continue;
- }
+ case 6: // "script"
+ if (key == INV_SCRIPT_LABEL)
+ {
+ const LLSD& script_map = value;
+ if (script_map.has(INV_RUNTIME_LABEL))
+ {
+ mRuntime = script_map[INV_RUNTIME_LABEL].asString();
+ }
+ else
+ {
+ // Clear stale runtime data when a script block is
+ // present without an explicit runtime value.
+ mRuntime.clear();
+ }
+ continue;
+ }
+ break;
- if (i->first == INV_SCRIPT_LABEL)
- {
- const LLSD& script_map = i->second;
- const std::string w = INV_RUNTIME_LABEL;
- if (script_map.has(w))
- {
- mRuntime = script_map[w].asString();
- }
- else
- {
- // Clear any stale runtime when a script block is present
- // but no explicit runtime value is provided.
- mRuntime.clear();
- }
- continue;
- }
+ case 7: // "item_id"
+ if (key == INV_ITEM_ID_LABEL)
+ {
+ mUUID = value;
+ continue;
+ }
+ break;
- if (i->first == INV_METADATA_LABEL)
- {
- // Server (non-AIS) exports thumbnail/favorite/script nested under
- // a "metadata" wrapper; mirror the legacy-stream parser behavior.
- const LLSD& metadata = i->second;
- if (metadata.has(INV_THUMBNAIL_LABEL))
- {
- const LLSD& thumbnail = metadata[INV_THUMBNAIL_LABEL];
- if (thumbnail.has(INV_ASSET_ID_LABEL))
+ case 8: // "asset_id", "inv_type"
+ if (key == INV_ASSET_ID_LABEL)
{
- mThumbnailUUID = thumbnail[INV_ASSET_ID_LABEL].asUUID();
+ mAssetUUID = value;
+ continue;
}
- }
- if (metadata.has(INV_FAVORITE_LABEL))
- {
- const LLSD& favorite = metadata[INV_FAVORITE_LABEL];
- if (favorite.has(INV_TOGGLED_LABEL))
+ if (key == INV_INVENTORY_TYPE_LABEL) // "inv_type"
{
- mFavorite = favorite[INV_TOGGLED_LABEL].asBoolean();
+ if (value.isString())
+ {
+ mInventoryType = LLInventoryType::lookup(value.asStringRef().c_str());
+ }
+ else if (value.isInteger())
+ {
+ S8 type = (U8)value.asInteger();
+ mInventoryType = static_cast<LLInventoryType::EType>(type);
+ }
+ continue;
}
- }
- if (metadata.has(INV_SCRIPT_LABEL)
- && metadata[INV_SCRIPT_LABEL].has(INV_RUNTIME_LABEL))
- {
- mRuntime = metadata[INV_SCRIPT_LABEL][INV_RUNTIME_LABEL].asString();
- }
- continue;
- }
-
- if (i->first == INV_PERMISSIONS_LABEL)
- {
- mPermissions.importLLSD(i->second);
- continue;
- }
-
- if (i->first == INV_SALE_INFO_LABEL)
- {
- // Sale info used to contain next owner perm. It is now in
- // the permissions. Thus, we read that out, and fix legacy
- // objects. It's possible this op would fail, but it
- // should pick up the vast majority of the tasks.
- bool has_perm_mask = false;
- U32 perm_mask = 0;
- if (!mSaleInfo.fromLLSD(i->second, has_perm_mask, perm_mask))
- {
- return false;
- }
- if (has_perm_mask)
- {
- if (perm_mask == PERM_NONE)
+ if (key == INV_FAVORITE_LABEL) // "favorite"
{
- perm_mask = mPermissions.getMaskOwner();
+ if (value.has(INV_TOGGLED_LABEL))
+ {
+ mFavorite = value[INV_TOGGLED_LABEL].asBoolean();
+ }
+ continue;
}
- // fair use fix.
- if (!(perm_mask & PERM_COPY))
+ if (key == INV_METADATA_LABEL)
{
- perm_mask |= PERM_TRANSFER;
+ // Server (non-AIS) exports thumbnail, favorite, and
+ // script metadata under a "metadata" wrapper.
+ const LLSD& metadata = value;
+ if (metadata.has(INV_THUMBNAIL_LABEL))
+ {
+ const LLSD& thumbnail = metadata[INV_THUMBNAIL_LABEL];
+ if (thumbnail.has(INV_ASSET_ID_LABEL))
+ {
+ mThumbnailUUID = thumbnail[INV_ASSET_ID_LABEL].asUUID();
+ }
+ }
+ if (metadata.has(INV_FAVORITE_LABEL))
+ {
+ const LLSD& favorite = metadata[INV_FAVORITE_LABEL];
+ if (favorite.has(INV_TOGGLED_LABEL))
+ {
+ mFavorite = favorite[INV_TOGGLED_LABEL].asBoolean();
+ }
+ }
+ if (metadata.has(INV_SCRIPT_LABEL)
+ && metadata[INV_SCRIPT_LABEL].has(INV_RUNTIME_LABEL))
+ {
+ mRuntime = metadata[INV_SCRIPT_LABEL][INV_RUNTIME_LABEL].asString();
+ }
+ continue;
}
- mPermissions.setMaskNext(perm_mask);
- }
- continue;
- }
-
- if (i->first == INV_SHADOW_ID_LABEL)
- {
- mAssetUUID = i->second;
- LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES);
- cipher.decrypt(mAssetUUID.mData, UUID_BYTES);
- continue;
- }
-
- if (i->first == INV_ASSET_ID_LABEL)
- {
- mAssetUUID = i->second;
- continue;
- }
-
- if (i->first == INV_LINKED_ID_LABEL)
- {
- mAssetUUID = i->second;
- continue;
- }
-
- if (i->first == INV_ASSET_TYPE_LABEL)
- {
- LLSD const &label = i->second;
- if (label.isString())
- {
- mType = LLAssetType::lookup(label.asStringRef().c_str());
- }
- else if (label.isInteger())
- {
- S8 type = (U8) label.asInteger();
- mType = static_cast<LLAssetType::EType>(type);
- }
- continue;
- }
-
- if (i->first == INV_INVENTORY_TYPE_LABEL)
- {
- LLSD const &label = i->second;
- if (label.isString())
- {
- mInventoryType = LLInventoryType::lookup(label.asStringRef().c_str());
- }
- else if (label.isInteger())
- {
- S8 type = (U8) label.asInteger();
- mInventoryType = static_cast<LLInventoryType::EType>(type);
- }
- continue;
- }
+ break;
- if (i->first == INV_FLAGS_LABEL)
- {
- LLSD const &label = i->second;
- if (label.isBinary())
- {
- mFlags = ll_U32_from_sd(label);
- }
- else if (label.isInteger())
- {
- mFlags = label.asInteger();
- }
- continue;
- }
+ case 9: // "parent_id", "shadow_id", "linked_id", "sale_info", "thumbnail"
+ if (key == INV_PARENT_ID_LABEL)
+ {
+ mParentUUID = value;
+ continue;
+ }
+ if (key == INV_SHADOW_ID_LABEL)
+ {
+ mAssetUUID = value;
+ LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES);
+ cipher.decrypt(mAssetUUID.mData, UUID_BYTES);
+ continue;
+ }
+ if (key == INV_LINKED_ID_LABEL)
+ {
+ mAssetUUID = value;
+ continue;
+ }
+ if (key == INV_SALE_INFO_LABEL)
+ {
+ // Sale info used to contain next owner perm. It is now in
+ // the permissions. Thus, we read that out, and fix legacy
+ // objects. It's possible this op would fail, but it
+ // should pick up the vast majority of the tasks.
+ bool has_perm_mask = false;
+ U32 perm_mask = 0;
+ if (!mSaleInfo.fromLLSD(value, has_perm_mask, perm_mask))
+ {
+ return false;
+ }
+ if (has_perm_mask)
+ {
+ if (perm_mask == PERM_NONE)
+ {
+ perm_mask = mPermissions.getMaskOwner();
+ }
+ // fair use fix.
+ if (!(perm_mask & PERM_COPY))
+ {
+ perm_mask |= PERM_TRANSFER;
+ }
+ mPermissions.setMaskNext(perm_mask);
+ }
+ continue;
+ }
+ if (key == INV_THUMBNAIL_LABEL)
+ {
+ if (value.has(INV_ASSET_ID_LABEL))
+ {
+ mThumbnailUUID = value[INV_ASSET_ID_LABEL];
+ }
+ /* Example:
+ <key> asset_id </key>
+ <uuid> acc0ec86 - 17f2 - 4b92 - ab41 - 6718b1f755f7 </uuid>
+ <key> perms </key>
+ <integer> 8 </integer>
+ <key>service</key>
+ <integer> 3 </integer>
+ <key>version</key>
+ <integer> 1 </key>
+ */
+ continue;
+ }
+ break;
+ case 10: // "created_at"
+ if (key == INV_CREATION_DATE_LABEL)
+ {
+ mCreationDate = value.asInteger();
+ continue;
+ }
+ break;
- if (i->first == INV_NAME_LABEL)
- {
- mName = i->second.asString();
- LLStringUtil::replaceNonstandardASCII(mName, ' ');
- LLStringUtil::replaceChar(mName, '|', ' ');
- continue;
- }
+ case 11: // "permissions"
+ if (key == INV_PERMISSIONS_LABEL)
+ {
+ mPermissions.importLLSD(value);
+ continue;
+ }
+ break;
- if (i->first == INV_DESC_LABEL)
- {
- mDescription = i->second.asString();
- LLStringUtil::replaceNonstandardASCII(mDescription, ' ');
- continue;
- }
+ case 12: // "thumbnail_id"
+ if (key == INV_THUMBNAIL_ID_LABEL)
+ {
+ mThumbnailUUID = value.asUUID();
+ continue;
+ }
+ break;
- if (i->first == INV_CREATION_DATE_LABEL)
- {
- mCreationDate = i->second.asInteger();
- continue;
+ default:
+ // Unknown field - skip
+ break;
}
}
@@ -1336,12 +1394,14 @@ LLSD LLInventoryCategory::asLLSD() const
if (mThumbnailUUID.notNull())
{
- sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID);
+ LLSD& thumbnail = sd[INV_THUMBNAIL_LABEL];
+ thumbnail[INV_ASSET_ID_LABEL] = mThumbnailUUID;
}
if (mFavorite)
{
- sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite);
+ LLSD& favorite = sd[INV_FAVORITE_LABEL];
+ favorite[INV_TOGGLED_LABEL] = mFavorite;
}
return sd;
@@ -1603,17 +1663,19 @@ void LLInventoryCategory::exportLLSD(LLSD& cat_data) const
{
cat_data[INV_FOLDER_ID_LABEL] = mUUID;
cat_data[INV_PARENT_ID_LABEL] = mParentUUID;
- cat_data[INV_ASSET_TYPE_LABEL] = std::string(LLAssetType::lookup(mType));
+ cat_data[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(mType);
cat_data[INV_PREFERRED_TYPE_LABEL] = LLFolderType::lookup(mPreferredType);
cat_data[INV_NAME_LABEL] = mName;
if (mThumbnailUUID.notNull())
{
- cat_data[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID);
+ LLSD& thumbnail = cat_data[INV_THUMBNAIL_LABEL];
+ thumbnail[INV_ASSET_ID_LABEL] = mThumbnailUUID;
}
if (mFavorite)
{
- cat_data[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite);
+ LLSD& favorite = cat_data[INV_FAVORITE_LABEL];
+ favorite[INV_TOGGLED_LABEL] = mFavorite;
}
}
diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp
index cf45f93168..21e5bc0ab4 100644
--- a/indra/llkdu/llimagej2ckdu.cpp
+++ b/indra/llkdu/llimagej2ckdu.cpp
@@ -231,11 +231,11 @@ struct LLKDUMessageError : public LLKDUMessage
{
// According to the documentation nat found:
// http://pirlwww.lpl.arizona.edu/resources/guide/software/Kakadu/html_pages/globals__kdu$mize_errors.html
- // "If a kdu_error object is destroyed, handler→flush will be called with
+ // "If a kdu_error object is destroyed, handler->flush will be called with
// an end_of_message argument equal to true and the process will
// subsequently be terminated through exit. The termination may be
// avoided, however, by throwing an exception from within the message
- // terminating handler→flush call."
+ // terminating handler->flush call."
// So throwing an exception here isn't arbitrary: we MUST throw an
// exception if we want to recover from a KDU error.
// Because this confused me: the above quote specifically refers to
diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp
index 082ab85e51..ece52db1b1 100644
--- a/indra/llmath/llvolume.cpp
+++ b/indra/llmath/llvolume.cpp
@@ -52,6 +52,7 @@
#include "llmeshoptimizer.h"
#include "lltimer.h"
#include "llvolumeoctree.h"
+#include "workqueue.h"
#include "mikktspace/mikktspace.hh"
@@ -1971,6 +1972,8 @@ LLVolume::LLVolume(const LLVolumeParams &params, const F32 detail, const bool ge
mNumHullIndices = 0;
// set defaults
+ mSculptValidationCache.fill(LLSculptValidationState::Unvalidated);
+
if (mParams.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE)
{
mPathp = new LLDynamicPath();
@@ -1998,6 +2001,13 @@ void LLVolume::resizePath(S32 length)
setDirty();
}
+void LLVolume::setDirty()
+{
+ mPathp->setDirty();
+ mProfilep->setDirty();
+ mSculptValidationCache.fill(LLSculptValidationState::Unvalidated);
+}
+
void LLVolume::regen()
{
generate();
@@ -2292,30 +2302,149 @@ bool LLVolume::unpackVolumeFaces(std::istream& is, S32 size)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
+ // Sanity-check before even trying to decompress
+ constexpr S32 MAX_MESH_COMPRESSED_SIZE = 128 * 1024 * 1024; // 128 MB
+ const LLUUID& mesh_id = getParams().getSculptID();
+
+ if (size <= 0 || size > MAX_MESH_COMPRESSED_SIZE)
+ {
+ LL_WARNS("MeshStreaming") << "Rejecting implausible compressed mesh size " << size
+ << " for mesh id " << mesh_id << LL_ENDL;
+ return false;
+ }
+
//input stream is now pointing at a zlib compressed block of LLSD
//decompress block
- LLSD mdl;
- U32 uzip_result = LLUZipHelper::unzip_llsd(mdl, is, size);
- if (uzip_result != LLUZipHelper::ZR_OK)
+ try
+ {
+ LLSD mdl;
+ U32 uzip_result = LLUZipHelper::unzip_llsd(mdl, is, size);
+ if (uzip_result != LLUZipHelper::ZR_OK)
+ {
+ LL_DEBUGS("MeshStreaming") << "Failed to unzip LLSD blob for LoD with code " << uzip_result << " , will probably fetch from sim again." << LL_ENDL;
+ return false;
+ }
+ return unpackVolumeFacesInternal(mdl);
+ }
+ catch (const std::bad_alloc&)
+ {
+ constexpr S32 SMALL_MESH_THRESHOLD = 4000;
+ if (size < SMALL_MESH_THRESHOLD)
+ {
+ // showOutOfMemory and LL_ERRS must run on the main thread.
+ // Post to mainloop WorkQueue, mirroring LLThread::tryRun().
+ LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
+ bool done = false;
+ if (main_queue)
+ {
+ const LLUUID mesh_id_copy = mesh_id; // capture by value for the lambda
+ done = main_queue->post([mesh_id_copy, size]()
+ {
+ LLError::LLUserWarningMsg::showOutOfMemory();
+ LL_ERRS("MeshStreaming") << "Out of memory unpacking mesh id " << mesh_id_copy
+ << " of compressed size " << size << LL_ENDL;
+ });
+ }
+ if (!done)
+ {
+ // No main queue available (e.g. during shutdown)
+ LL_WARNS("MeshStreaming") << "Out of memory unpacking mesh id " << mesh_id
+ << " of compressed size " << size << " (main queue unavailable)" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("MeshStreaming") << "Out of memory unpacking mesh id " << mesh_id
+ << " of compressed size " << size << LL_ENDL;
+ }
+ return false;
+ }
+ catch (const std::exception& e)
{
- LL_DEBUGS("MeshStreaming") << "Failed to unzip LLSD blob for LoD with code " << uzip_result << " , will probably fetch from sim again." << LL_ENDL;
+ LL_WARNS("MeshStreaming") << "Exception unpacking mesh id " << mesh_id
+ << " of compressed size " << size << ": " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (...)
+ {
+ LL_WARNS("MeshStreaming") << "Unknown exception unpacking mesh id " << mesh_id
+ << " of compressed size " << size << LL_ENDL;
return false;
}
- return unpackVolumeFacesInternal(mdl);
}
bool LLVolume::unpackVolumeFaces(U8* in_data, S32 size)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
+ constexpr S32 MAX_MESH_COMPRESSED_SIZE = 128 * 1024 * 1024; // 128 MB
+ const LLUUID& mesh_id = getParams().getSculptID();
+
+ if (!in_data || size <= 0 || size > MAX_MESH_COMPRESSED_SIZE)
+ {
+ LL_WARNS("MeshStreaming") << "Rejecting implausible compressed mesh size " << size
+ << " for mesh id " << mesh_id << LL_ENDL;
+ return false;
+ }
+
//input data is now pointing at a zlib compressed block of LLSD
//decompress block
- LLSD mdl;
- U32 uzip_result = LLUZipHelper::unzip_llsd(mdl, in_data, size);
- if (uzip_result != LLUZipHelper::ZR_OK)
+ try
{
- LL_DEBUGS("MeshStreaming") << "Failed to unzip LLSD blob for LoD with code " << uzip_result << " , will probably fetch from sim again." << LL_ENDL;
+ LLSD mdl;
+ U32 uzip_result = LLUZipHelper::unzip_llsd(mdl, in_data, size);
+ if (uzip_result != LLUZipHelper::ZR_OK)
+ {
+ LL_DEBUGS("MeshStreaming") << "Failed to unzip LLSD blob for LoD, mesh id " << mesh_id
+ << ", code " << uzip_result << " , will probably fetch from sim again." << LL_ENDL;
+ return false;
+ }
+ return unpackVolumeFacesInternal(mdl);
+ }
+ catch (const std::bad_alloc&)
+ {
+ constexpr S32 SMALL_MESH_THRESHOLD = 4000;
+ if (size < SMALL_MESH_THRESHOLD)
+ {
+ // showOutOfMemory and LL_ERRS must run on the main thread.
+ // Post to mainloop WorkQueue, mirroring LLThread::tryRun().
+ LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
+ bool done = false;
+ if (main_queue)
+ {
+ const LLUUID mesh_id_copy = mesh_id; // capture by value for the lambda
+ done = main_queue->post([mesh_id_copy, size]()
+ {
+ LLError::LLUserWarningMsg::showOutOfMemory();
+ LL_ERRS("MeshStreaming") << "Out of memory unpacking mesh id " << mesh_id_copy
+ << " of compressed size " << size << LL_ENDL;
+ });
+ }
+ if (!done)
+ {
+ // No main queue available (e.g. during shutdown)
+ LL_WARNS("MeshStreaming") << "Out of memory unpacking mesh id " << mesh_id
+ << " of compressed size " << size << " (main queue unavailable)" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("MeshStreaming") << "Out of memory unpacking mesh id " << mesh_id
+ << " of compressed size " << size << LL_ENDL;
+ }
+ return false;
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS("MeshStreaming") << "Exception unpacking mesh id " << mesh_id
+ << " of compressed size " << size << ": " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (...)
+ {
+ LL_WARNS("MeshStreaming") << "Unknown exception unpacking mesh id " << mesh_id
+ << " of compressed size " << size << LL_ENDL;
return false;
}
- return unpackVolumeFacesInternal(mdl);
}
bool LLVolume::unpackVolumeFacesInternal(const LLSD& mdl)
@@ -2385,11 +2514,31 @@ bool LLVolume::unpackVolumeFacesInternal(const LLSD& mdl)
//copy out vertices
U32 num_verts = static_cast<U32>(pos.size())/(3*2);
+ if (num_verts == 0)
+ {
+ LL_WARNS() << "Zero vertices for face index: " << i << LL_ENDL;
+ face.resizeIndices(3);
+ face.resizeVertices(1);
+ face.mPositions->clear();
+ face.mNormals->clear();
+ face.mTexCoords->setZero();
+ memset(face.mIndices, 0, sizeof(U16) * 3);
+ continue;
+ }
+
+ if (num_verts > 65535) // U16 indices
+ {
+ LL_WARNS() << "Invalid vertex count " << num_verts << " exceeds maximum for face index: " << i << LL_ENDL;
+ mVolumeFaces.clear();
+ return false;
+ }
+
face.resizeVertices(num_verts);
if (num_verts > 0 && !face.mPositions)
{
LL_WARNS() << "Failed to allocate " << num_verts << " vertices for face index: " << i << " Total: " << face_count << LL_ENDL;
+ face.resizeVertices(0);
face.resizeIndices(0);
continue;
}
@@ -3140,10 +3289,100 @@ S32 sculpt_sides(F32 detail)
}
}
+static bool validate_sculpt_geometry(const LLVolume* volume)
+{
+ if (!volume || volume->getNumVolumeFaces() == 0)
+ {
+ return true;
+ }
+
+ // Validate each face in the sculpt
+ for (S32 face_idx = 0; face_idx < volume->getNumVolumeFaces(); ++face_idx)
+ {
+ const LLVolumeFace& face = volume->getVolumeFace(face_idx);
+
+ // Skip validation for degenerate faces
+ if (face.mNumVertices < 3 || face.mNumIndices < 3)
+ {
+ continue;
+ }
+
+ // Calculate 'bounding' surface
+ LLVector4a extent_size;
+ extent_size.setSub(face.mExtents[1], face.mExtents[0]);
+
+ F32 width = extent_size[0];
+ F32 height = extent_size[1];
+ F32 depth = extent_size[2];
+
+ F32 bounding_surface = width * height + width * depth + height * depth;
+ bounding_surface *= 2;
+
+ if (!llfinite(bounding_surface) || bounding_surface <= FLT_EPSILON)
+ {
+ return false;
+ }
+
+ // Calculate total surface area of all triangles
+ F32 total_triangle_area = 0.0f;
+ S32 num_triangles = face.mNumIndices / 3;
+
+ for (S32 i = 0; i < num_triangles; ++i)
+ {
+ S32 idx_base = i * 3;
+ U16 idx0 = face.mIndices[idx_base];
+ U16 idx1 = face.mIndices[idx_base + 1];
+ U16 idx2 = face.mIndices[idx_base + 2];
+
+ // Validate indices
+ if (idx0 >= face.mNumVertices || idx1 >= face.mNumVertices || idx2 >= face.mNumVertices)
+ {
+ continue;
+ }
+ const LLVector4a& v0 = face.mPositions[idx0];
+ const LLVector4a& v1 = face.mPositions[idx1];
+ const LLVector4a& v2 = face.mPositions[idx2];
+
+ // Calculate triangle area
+ LLVector4a edge1, edge2, cross;
+ edge1.setSub(v1, v0);
+ edge2.setSub(v2, v0);
+ cross.setCross3(edge1, edge2);
+
+ F32 triangle_area = cross.getLength3().getF32() * 0.5f;
+ total_triangle_area += triangle_area;
+ }
+
+ if (!llfinite(total_triangle_area))
+ {
+ return false;
+ }
+
+ // The idea here is that the total area of a sculpt is normally comparable
+ // to the area of a bounding box. But a random overlapping collection of
+ // triangles has a significantly larger area than a sculpt normally has.
+ F32 area_ratio = total_triangle_area / bounding_surface;
+
+ constexpr F32 MAX_AREA_TO_AREA_RATIO = 16.0f;
+ if (area_ratio > MAX_AREA_TO_AREA_RATIO)
+ {
+ LL_DEBUGS("LLVOLUME") << "Sculpt rejected: excessive triangle area."
+ << " Face index: " << face_idx
+ << " Total triangle area: " << total_triangle_area << " sq m"
+ << " Bounding surface area: " << bounding_surface << " sq m"
+ << " Ratio: " << area_ratio
+ << " Triangle count: " << num_triangles
+ << " Bounds: [" << width << " x " << height << " x " << depth << "]" << LL_ENDL;
+ return false;
+ }
+ }
+
+ return true;
+}
// determine the number of vertices in both s and t direction for this sculpt
-void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 detail, S32& s, S32& t)
+static void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 detail, S32& s, S32& t)
{
// this code has the following properties:
// 1) the aspect ratio of the mesh is as close as possible to the ratio of the map
@@ -3253,10 +3492,62 @@ void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components,
mSculptLevel = sculpt_level;
- // Delete any existing faces so that they get regenerated
- mVolumeFaces.clear();
+ LLSculptValidationState cached_state = LLSculptValidationState::Valid;
+ if (!data_is_empty && (sculpt_level >= 0) && sculpt_level < SCULPT_CACHE_SIZE)
+ {
+ // validation might be expensive, so we do it only once per lod.
+ cached_state = mSculptValidationCache[sculpt_level];
+ }
- createVolumeFaces();
+ switch (cached_state)
+ {
+ case LLSculptValidationState::Valid:
+ {
+ // Delete any existing faces, then regenerate
+ mVolumeFaces.clear();
+ createVolumeFaces();
+ break;
+ }
+ case LLSculptValidationState::Invalid:
+ {
+ // Invalid geometry
+ // Regenerate mesh with an empty placeholder
+ mVolumeFaces.clear();
+ sculptGenerateEmptyPlaceholder();
+ createVolumeFaces();
+ break;
+ }
+ case LLSculptValidationState::Unvalidated:
+ {
+ // First time at this LOD
+ mVolumeFaces.clear();
+ createVolumeFaces();
+
+ bool valid_geometry = true;
+
+ // Todo: can this be backed into createVolumeFaces?
+ // Or calculated without having to call createVolumeFaces first?
+ // Todo 2: should the same be done for meshes?
+ valid_geometry = validate_sculpt_geometry(this);
+ mSculptValidationCache[sculpt_level] = valid_geometry ? LLSculptValidationState::Valid : LLSculptValidationState::Invalid;
+
+ if (!valid_geometry)
+ {
+ LL_WARNS("LLVOLUME") << "Sculpt failed geometry validation - either invalid image or malicious. "
+ << "Replacing with placeholder. Image: " << mParams.getSculptID() << LL_ENDL;
+
+ // Clear the malicious faces
+ mVolumeFaces.clear();
+
+ // Regenerate mesh with empty placeholder
+ sculptGenerateEmptyPlaceholder();
+
+ // Recreate faces with placeholder geometry
+ createVolumeFaces();
+ }
+ break;
+ }
+ } //switch (cached_state)
}
@@ -5679,7 +5970,40 @@ bool LLVolumeFace::cacheOptimize(bool gen_tangents)
mOptimized = true;
if (gen_tangents && mNormals && mTexCoords)
- { // generate mikkt space tangents before cache optimizing since the index buffer may change
+ {
+ if (!mPositions || !mIndices || mNumVertices <= 0 || mNumIndices <= 0)
+ {
+ LL_WARNS_ONCE("LLVolume") << "Invalid volume face data for tangent generation: "
+ << "mPositions=" << (void*)mPositions
+ << ", mIndices=" << (void*)mIndices
+ << ", mNumVertices=" << mNumVertices
+ << ", mNumIndices=" << mNumIndices << LL_ENDL;
+ return false;
+ }
+
+ if (mNumIndices % 3 != 0)
+ {
+ LL_WARNS_ONCE("LLVolume") << "Non-triangulated mesh, mNumIndices=" << mNumIndices << LL_ENDL;
+ return false;
+ }
+
+ for (S32 i = 0; i < mNumIndices; ++i)
+ {
+ if (mIndices[i] >= mNumVertices)
+ {
+ LL_WARNS_ONCE("LLVolume") << "Out of bounds index detected: mIndices[" << i << "]="
+ << mIndices[i] << " >= mNumVertices=" << mNumVertices << LL_ENDL;
+ return false;
+ }
+ }
+
+ if (mNormalizedScale.mV[0] == 0.0f || mNormalizedScale.mV[1] == 0.0f || mNormalizedScale.mV[2] == 0.0f)
+ {
+ LL_WARNS_ONCE("LLVolume") << "Invalid normalized scale: " << mNormalizedScale << LL_ENDL;
+ return false;
+ }
+
+ // generate mikkt space tangents before cache optimizing since the index buffer may change
// a bit of a hack to do this here, but this function gets called exactly once for the lifetime of a mesh
// and is executed on a background thread
MikktData data(this);
diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h
index 3496928f7b..86a7bf0e6a 100644
--- a/indra/llmath/llvolume.h
+++ b/indra/llmath/llvolume.h
@@ -60,6 +60,8 @@ class LLVolumeOctree;
#include "llalignedarray.h"
#include "llrigginginfo.h"
+#include <array>
+
//============================================================================
constexpr S32 MIN_DETAIL_FACES = 6;
@@ -1040,7 +1042,7 @@ public:
const LLVector4a& getMeshPt(const U32 i) const { return mMesh[i]; }
- void setDirty() { mPathp->setDirty(); mProfilep->setDirty(); }
+ void setDirty();
void regen();
void genTangents(S32 face);
@@ -1131,6 +1133,11 @@ public:
bool mIsMeshAssetLoaded;
bool mIsMeshAssetUnavaliable;
+ // Cache for sculpt geometry validation results
+ static constexpr S32 SCULPT_CACHE_SIZE = 6; // 0 to 5 inclusive
+ enum class LLSculptValidationState : S8 { Unvalidated, Valid, Invalid };
+ std::array<LLSculptValidationState, SCULPT_CACHE_SIZE> mSculptValidationCache;
+
const LLVolumeParams mParams;
LLPath *mPathp;
LLProfile *mProfilep;
diff --git a/indra/llmessage/llcircuit.cpp b/indra/llmessage/llcircuit.cpp
index c2b1a2f069..eaee7b0112 100644
--- a/indra/llmessage/llcircuit.cpp
+++ b/indra/llmessage/llcircuit.cpp
@@ -346,8 +346,7 @@ S32 LLCircuitData::resendUnackedPackets(const F64Seconds now)
packetp->mBuffer[0] |= LL_RESENT_FLAG; // tag packet id as being a resend
- gMessageSystem->mPacketRing.sendPacket(packetp->mSocket,
- (char *)packetp->mBuffer, packetp->mBufferLength,
+ gMessageSystem->sendPacketToSocket((char *)packetp->mBuffer, packetp->mBufferLength,
packetp->mHost);
mThrottles.throttleOverflow(TC_RESEND, packetp->mBufferLength * 8.f);
@@ -973,7 +972,7 @@ bool LLCircuitData::updateWatchDogTimers(LLMessageSystem *msgsys)
{
// let's call this one a loss!
mPacketsLost++;
- gMessageSystem->mDroppedPackets++;
+ gMessageSystem->mLostPackets++;
if(gMessageSystem->mVerboseLog)
{
std::ostringstream str;
diff --git a/indra/llmessage/llpacketbuffer.h b/indra/llmessage/llpacketbuffer.h
index ac4012d330..a08e6c22e5 100644
--- a/indra/llmessage/llpacketbuffer.h
+++ b/indra/llmessage/llpacketbuffer.h
@@ -38,6 +38,9 @@ public:
LLPacketBuffer(S32 hSocket); // receive a packet
~LLPacketBuffer();
+ LLPacketBuffer(const LLPacketBuffer&) = default;
+ LLPacketBuffer& operator=(const LLPacketBuffer&) = default;
+
S32 getSize() const { return mSize; }
const char *getData() const { return mData; }
LLHost getHost() const { return mHost; }
@@ -46,11 +49,18 @@ public:
void init(S32 hSocket);
void init(const char* buffer, S32 data_size, const LLHost& host);
+ // Whether LLCircuitData::checkPacketInID() has already been run for this
+ // packet (done at socket-read time, before this packet was sorted into
+ // the high/low priority inbound queue).
+ bool getPacketIDChecked() const { return mPacketIDChecked; }
+ void setPacketIDChecked(bool checked) { mPacketIDChecked = checked; }
+
protected:
char mData[NET_BUFFER_SIZE]; // packet data /* Flawfinder : ignore */
S32 mSize; // size of buffer in bytes
LLHost mHost; // source/dest IP and port
LLHost mReceivingIF; // source/dest IP and port
+ bool mPacketIDChecked = false;
};
#endif
diff --git a/indra/llmessage/llpacketring.cpp b/indra/llmessage/llpacketring.cpp
index b8284334ea..bade413e61 100644
--- a/indra/llmessage/llpacketring.cpp
+++ b/indra/llmessage/llpacketring.cpp
@@ -28,344 +28,121 @@
#include "llpacketring.h"
-#if LL_WINDOWS
- #include <winsock2.h>
-#else
- #include <sys/socket.h>
- #include <netinet/in.h>
-#endif
-
-// linden library includes
#include "llerror.h"
-#include "lltimer.h"
-#include "llproxy.h"
-#include "llrand.h"
-#include "message.h"
-#include "u64.h"
-constexpr S16 MAX_BUFFER_RING_SIZE = 1024;
+constexpr S16 MAX_BUFFER_RING_SIZE = 8192;
+
+// DANGER: don't adjust DEFAULT_BUFFER_RING_SIZE unless you know what
+// you're doing. Its value affects the "buffer load rate" which is used
+// to supply backpressure to an overloaded nework queue.
constexpr S16 DEFAULT_BUFFER_RING_SIZE = 256;
-LLPacketRing::LLPacketRing ()
- : mPacketRing(DEFAULT_BUFFER_RING_SIZE, nullptr)
+LLPacketRing::LLPacketRing()
+ : mRing(DEFAULT_BUFFER_RING_SIZE, nullptr)
{
LLHost invalid_host;
- for (size_t i = 0; i < mPacketRing.size(); ++i)
+ for (size_t i = 0; i < mRing.size(); ++i)
{
- mPacketRing[i] = new LLPacketBuffer(invalid_host, nullptr, 0);
+ mRing[i] = new LLPacketBuffer(invalid_host, nullptr, 0);
}
}
-LLPacketRing::~LLPacketRing ()
+LLPacketRing::~LLPacketRing()
{
- for (auto packet : mPacketRing)
+ for (auto* packet : mRing)
{
delete packet;
}
- mPacketRing.clear();
+ mRing.clear();
mNumBufferedPackets = 0;
mNumBufferedBytes = 0;
mHeadIndex = 0;
}
-S32 LLPacketRing::receivePacket (S32 socket, char *datap)
-{
- bool drop = computeDrop();
- return (mNumBufferedPackets > 0) ?
- receiveOrDropBufferedPacket(datap, drop) :
- receiveOrDropPacket(socket, datap, drop);
-}
-
-bool send_packet_helper(int socket, const char * datap, S32 data_size, LLHost host)
-{
- if (!LLProxy::isSOCKSProxyEnabled())
- {
- return send_packet(socket, datap, data_size, host.getAddress(), host.getPort());
- }
-
- char headered_send_buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE];
-
- proxywrap_t *socks_header = static_cast<proxywrap_t*>(static_cast<void*>(&headered_send_buffer));
- socks_header->rsv = 0;
- socks_header->addr = host.getAddress();
- socks_header->port = htons(host.getPort());
- socks_header->atype = ADDRESS_IPV4;
- socks_header->frag = 0;
-
- memcpy(headered_send_buffer + SOCKS_HEADER_SIZE, datap, data_size);
-
- return send_packet( socket,
- headered_send_buffer,
- data_size + SOCKS_HEADER_SIZE,
- LLProxy::getInstance()->getUDPProxy().getAddress(),
- LLProxy::getInstance()->getUDPProxy().getPort());
-}
-
-bool LLPacketRing::sendPacket(int socket, const char * datap, S32 data_size, LLHost host)
+void LLPacketRing::pushPacket(const LLPacketBuffer& packet)
{
- mActualBytesOut += data_size;
- return send_packet_helper(socket, datap, data_size, host);
-}
-
-void LLPacketRing::dropPackets (U32 num_to_drop)
-{
- mPacketsToDrop += num_to_drop;
-}
-
-void LLPacketRing::setDropPercentage (F32 percent_to_drop)
-{
- mDropPercentage = percent_to_drop;
-}
-
-bool LLPacketRing::computeDrop()
-{
- bool drop= (mDropPercentage > 0.0f && (ll_frand(100.f) < mDropPercentage));
- if (drop)
- {
- ++mPacketsToDrop;
- }
- if (mPacketsToDrop > 0)
- {
- --mPacketsToDrop;
- drop = true;
- }
- return drop;
-}
-
-S32 LLPacketRing::receiveOrDropPacket(S32 socket, char *datap, bool drop)
-{
- S32 packet_size = 0;
-
- // pull straight from socket
- if (LLProxy::isSOCKSProxyEnabled())
+ S16 ring_size = (S16)mRing.size();
+ if (mNumBufferedPackets >= ring_size && ring_size < MAX_BUFFER_RING_SIZE)
{
- char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */
- packet_size = receive_packet(socket, buffer);
- if (packet_size > 0)
- {
- mActualBytesIn += packet_size;
- }
-
- if (packet_size > SOCKS_HEADER_SIZE)
- {
- if (drop)
- {
- packet_size = 0;
- }
- else
- {
- // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6)
- packet_size -= SOCKS_HEADER_SIZE; // The unwrapped packet size
- memcpy(datap, buffer + SOCKS_HEADER_SIZE, packet_size);
- proxywrap_t * header = static_cast<proxywrap_t*>(static_cast<void*>(buffer));
- mLastSender.setAddress(header->addr);
- mLastSender.setPort(ntohs(header->port));
- mLastReceivingIF = ::get_receiving_interface();
- }
- }
- else
- {
- packet_size = 0;
- }
- }
- else
- {
- packet_size = receive_packet(socket, datap);
- if (packet_size > 0)
- {
- mActualBytesIn += packet_size;
- if (drop)
- {
- packet_size = 0;
- }
- else
- {
- mLastSender = ::get_sender();
- mLastReceivingIF = ::get_receiving_interface();
- }
- }
+ expandRing();
+ ring_size = (S16)mRing.size();
}
- return packet_size;
-}
-S32 LLPacketRing::receiveOrDropBufferedPacket(char *datap, bool drop)
-{
- assert(mNumBufferedPackets > 0);
- S32 packet_size = 0;
+ LLPacketBuffer* slot = mRing[mHeadIndex];
+ S32 old_size = slot->getSize();
- S16 ring_size = (S16)(mPacketRing.size());
- S16 packet_index = (mHeadIndex + ring_size - mNumBufferedPackets) % ring_size;
- LLPacketBuffer* packet = mPacketRing[packet_index];
- packet_size = packet->getSize();
- mLastSender = packet->getHost();
- mLastReceivingIF = packet->getReceivingInterface();
+ *slot = packet;
- --mNumBufferedPackets;
- mNumBufferedBytes -= packet_size;
- if (mNumBufferedPackets == 0)
- {
- assert(mNumBufferedBytes == 0);
- }
+ mHeadIndex = (mHeadIndex + 1) % ring_size;
- if (!drop)
+ if (mNumBufferedPackets < ring_size)
{
- if (packet_size > 0)
- {
- memcpy(datap, packet->getData(), packet_size);
- }
- else
- {
- assert(false);
- }
+ ++mNumBufferedPackets;
+ mNumBufferedBytes += packet.getSize();
}
else
{
- packet_size = 0;
+ // Ring is at maximum capacity; oldest packet was overwritten.
+ // This is VERY BAD because we've already ACKed the packet we're loosing
+ // (if it was "reliable").
+ LL_WARNS("PacketRing") << "buffer overflow at " << mNumBufferedPackets << " packets" << LL_ENDL;
+ mNumBufferedBytes += packet.getSize() - old_size;
}
- return packet_size;
}
-S32 LLPacketRing::bufferInboundPacket(S32 socket)
+bool LLPacketRing::popPacket(LLPacketBuffer& packet)
{
- if (mNumBufferedPackets == mPacketRing.size() && mNumBufferedPackets < MAX_BUFFER_RING_SIZE)
+ if (mNumBufferedPackets <= 0)
{
- expandRing();
+ return false;
}
- LLPacketBuffer* packet = mPacketRing[mHeadIndex];
- S32 old_packet_size = packet->getSize();
- S32 packet_size = 0;
- if (LLProxy::isSOCKSProxyEnabled())
- {
- char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */
- packet_size = receive_packet(socket, buffer);
- if (packet_size > 0)
- {
- mActualBytesIn += packet_size;
- if (packet_size > SOCKS_HEADER_SIZE)
- {
- // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6)
+ S16 ring_size = (S16)mRing.size();
+ S16 tail_index = (mHeadIndex + ring_size - mNumBufferedPackets) % ring_size;
- proxywrap_t * header = static_cast<proxywrap_t*>(static_cast<void*>(buffer));
- LLHost sender;
- sender.setAddress(header->addr);
- sender.setPort(ntohs(header->port));
+ LLPacketBuffer* slot = mRing[tail_index];
+ S32 packet_size = slot->getSize();
- packet_size -= SOCKS_HEADER_SIZE; // The unwrapped packet size
- packet->init(buffer + SOCKS_HEADER_SIZE, packet_size, sender);
+ packet = *slot;
- mHeadIndex = (mHeadIndex + 1) % (S16)(mPacketRing.size());
- if (mNumBufferedPackets < MAX_BUFFER_RING_SIZE)
- {
- ++mNumBufferedPackets;
- mNumBufferedBytes += packet_size;
- }
- else
- {
- // we overwrote an older packet
- mNumBufferedBytes += packet_size - old_packet_size;
- }
- }
- else
- {
- packet_size = 0;
- }
- }
- }
- else
- {
- packet->init(socket);
- packet_size = packet->getSize();
- if (packet_size > 0)
- {
- mActualBytesIn += packet_size;
+ --mNumBufferedPackets;
+ mNumBufferedBytes -= packet_size;
- mHeadIndex = (mHeadIndex + 1) % (S16)(mPacketRing.size());
- if (mNumBufferedPackets < MAX_BUFFER_RING_SIZE)
- {
- ++mNumBufferedPackets;
- mNumBufferedBytes += packet_size;
- }
- else
- {
- // we overwrote an older packet
- mNumBufferedBytes += packet_size - old_packet_size;
- }
- }
- }
- return packet_size;
-}
+ llassert(mNumBufferedPackets > 0 || mNumBufferedBytes == 0);
-S32 LLPacketRing::drainSocket(S32 socket)
-{
- // drain into buffer
- S32 packet_size = 1;
- S32 num_loops = 0;
- S32 old_num_packets = mNumBufferedPackets;
- while (packet_size > 0)
- {
- packet_size = bufferInboundPacket(socket);
- ++num_loops;
- }
- S32 num_dropped_packets = (num_loops - 1 + old_num_packets) - mNumBufferedPackets;
- if (num_dropped_packets > 0)
- {
- // It will eventually be accounted by mDroppedPackets
- // and mPacketsLost, but track it here for logging purposes.
- mNumDroppedPackets += num_dropped_packets;
- }
- return (S32)(mNumBufferedPackets);
+ return true;
}
bool LLPacketRing::expandRing()
{
- // compute larger size
- constexpr S16 BUFFER_RING_EXPANSION = 256;
- S16 old_size = (S16)(mPacketRing.size());
+ constexpr S16 BUFFER_RING_EXPANSION = 512;
+ S16 old_size = (S16)mRing.size();
S16 new_size = llmin(old_size + BUFFER_RING_EXPANSION, MAX_BUFFER_RING_SIZE);
if (new_size == old_size)
{
- // mPacketRing is already maxed out
return false;
}
- // make a larger ring and copy packet pointers
+ // Lay existing entries out linearly in FIFO order starting at index 0.
std::vector<LLPacketBuffer*> new_ring(new_size, nullptr);
for (S16 i = 0; i < old_size; ++i)
{
S16 j = (mHeadIndex + i) % old_size;
- new_ring[i] = mPacketRing[j];
+ new_ring[i] = mRing[j];
}
- // allocate new packets for the remainder of new_ring
LLHost invalid_host;
for (S16 i = old_size; i < new_size; ++i)
{
new_ring[i] = new LLPacketBuffer(invalid_host, nullptr, 0);
}
- // swap the rings and reset mHeadIndex
- mPacketRing.swap(new_ring);
+ mRing.swap(new_ring);
mHeadIndex = mNumBufferedPackets;
return true;
}
F32 LLPacketRing::getBufferLoadRate() const
{
- // goes up to MAX_BUFFER_RING_SIZE
return (F32)mNumBufferedPackets / (F32)DEFAULT_BUFFER_RING_SIZE;
}
-
-void LLPacketRing::dumpPacketRingStats()
-{
- mNumDroppedPacketsTotal += mNumDroppedPackets;
- LL_INFOS("Messaging") << "Packet ring stats: " << std::endl
- << "Buffered packets: " << mNumBufferedPackets << std::endl
- << "Buffered bytes: " << mNumBufferedBytes << std::endl
- << "Dropped packets current: " << mNumDroppedPackets << std::endl
- << "Dropped packets total: " << mNumDroppedPacketsTotal << std::endl
- << "Dropped packets percentage: " << mDropPercentage << "%" << std::endl
- << "Actual in bytes: " << mActualBytesIn << std::endl
- << "Actual out bytes: " << mActualBytesOut << LL_ENDL;
- mNumDroppedPackets = 0;
-}
diff --git a/indra/llmessage/llpacketring.h b/indra/llmessage/llpacketring.h
index 572dcbd271..5315e5a5bd 100644
--- a/indra/llmessage/llpacketring.h
+++ b/indra/llmessage/llpacketring.h
@@ -1,7 +1,16 @@
/**
* @file llpacketring.h
- * @brief definition of LLPacketRing class for implementing a resend,
- * drop, or delay in packet transmissions
+ * @brief LLPacketRing: a simple ring buffer for LLPacketBuffers.
+ *
+ * LLPacketRing stores incoming UDP packets that have already been received
+ * from the network socket. It has no socket or proxy awareness; callers
+ * push packets in with pushPacket() and retrieve them in FIFO order with
+ * popPacket().
+ *
+ * The ring starts at DEFAULT_BUFFER_RING_SIZE slots and grows in increments
+ * of BUFFER_RING_EXPANSION up to MAX_BUFFER_RING_SIZE. Once at the ceiling,
+ * pushPacket() silently overwrites the oldest queued packet to make room for
+ * the incoming one.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
@@ -29,9 +38,7 @@
#include <vector>
-#include "llhost.h"
#include "llpacketbuffer.h"
-#include "llthrottle.h"
class LLPacketRing
@@ -40,71 +47,26 @@ public:
LLPacketRing();
~LLPacketRing();
- // receive one packet: either buffered or from the socket
- S32 receivePacket (S32 socket, char *datap);
-
- // send one packet
- bool sendPacket(int h_socket, const char * send_buffer, S32 buf_size, LLHost host);
-
- // drains packets from socket and returns final mNumBufferedPackets
- S32 drainSocket(S32 socket);
+ // Copy 'packet' onto the tail of the ring, growing the ring or
+ // overwriting the oldest entry when the ring is at capacity.
+ void pushPacket(const LLPacketBuffer& packet);
- void dropPackets(U32);
- void setDropPercentage (F32 percent_to_drop);
-
- inline LLHost getLastSender() const;
- inline LLHost getLastReceivingInterface() const;
-
- S32 getActualInBytes() const { return mActualBytesIn; }
- S32 getActualOutBytes() const { return mActualBytesOut; }
- S32 getAndResetActualInBits() { S32 bits = mActualBytesIn * 8; mActualBytesIn = 0; return bits;}
- S32 getAndResetActualOutBits() { S32 bits = mActualBytesOut * 8; mActualBytesOut = 0; return bits;}
+ // Copy the head (oldest) packet into 'packet'.
+ // Returns true if a packet was available, false if the ring was empty.
+ bool popPacket(LLPacketBuffer& packet);
S32 getNumBufferedPackets() const { return (S32)(mNumBufferedPackets); }
- S32 getNumBufferedBytes() const { return mNumBufferedBytes; }
- S32 getNumDroppedPackets() const { return mNumDroppedPacketsTotal + mNumDroppedPackets; }
-
- F32 getBufferLoadRate() const; // from 0 to 4 (0 - empty, 1 - default size is full)
- void dumpPacketRingStats();
-protected:
- // returns 'true' if we should intentionally drop a packet
- bool computeDrop();
-
- // returns packet_size of received packet, zero or less if no packet found
- S32 receiveOrDropPacket(S32 socket, char *datap, bool drop);
- S32 receiveOrDropBufferedPacket(char *datap, bool drop);
+ S32 getNumBufferedBytes() const { return mNumBufferedBytes; }
- // returns packet_size of packet buffered
- S32 bufferInboundPacket(S32 socket);
+ // Ratio of buffered packets to DEFAULT_BUFFER_RING_SIZE (0 = empty, 1 = nominal full).
+ F32 getBufferLoadRate() const;
- // returns 'true' if ring was expanded
+private:
+ // Returns true if the ring was expanded, false if already at the ceiling.
bool expandRing();
-protected:
- std::vector<LLPacketBuffer*> mPacketRing;
- S16 mHeadIndex { 0 };
+ std::vector<LLPacketBuffer*> mRing;
+ S16 mHeadIndex { 0 };
S16 mNumBufferedPackets { 0 };
- S32 mNumDroppedPackets { 0 };
- S32 mNumDroppedPacketsTotal { 0 };
- S32 mNumBufferedBytes { 0 };
-
- S32 mActualBytesIn { 0 };
- S32 mActualBytesOut { 0 };
- F32 mDropPercentage { 0.0f }; // % of inbound packets to drop
- U32 mPacketsToDrop { 0 }; // drop next inbound n packets
-
- // These are the sender and receiving_interface for the last packet delivered by receivePacket()
- LLHost mLastSender;
- LLHost mLastReceivingIF;
+ S32 mNumBufferedBytes { 0 };
};
-
-
-inline LLHost LLPacketRing::getLastSender() const
-{
- return mLastSender;
-}
-
-inline LLHost LLPacketRing::getLastReceivingInterface() const
-{
- return mLastReceivingIF;
-}
diff --git a/indra/llmessage/llqueryflags.h b/indra/llmessage/llqueryflags.h
index 227d28ba5c..8dfb74aab0 100644
--- a/indra/llmessage/llqueryflags.h
+++ b/indra/llmessage/llqueryflags.h
@@ -56,8 +56,8 @@ const U32 DFQ_NAME_SORT = 0x1 << 19;
const U32 DFQ_LIMIT_BY_PRICE = 0x1 << 20;
const U32 DFQ_LIMIT_BY_AREA = 0x1 << 21;
-const U32 DFQ_FILTER_MATURE = 0x1 << 22;
-const U32 DFQ_PG_PARCELS_ONLY = 0x1 << 23;
+const U32 DFQ_FILTER_MATURE = 0x1 << 22; // legacy, will not work with any from DFQ_INC_NEW_VIEWER
+const U32 DFQ_PG_PARCELS_ONLY = 0x1 << 23; // legacy, will not work with any from DFQ_INC_NEW_VIEWER
const U32 DFQ_INC_PG = 0x1 << 24; // Flags appear in 1.23 viewer or later
const U32 DFQ_INC_MATURE = 0x1 << 25;
diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp
index df58c8a7f3..db7405f93f 100644
--- a/indra/llmessage/message.cpp
+++ b/indra/llmessage/message.cpp
@@ -78,6 +78,8 @@
#include "lltransfertargetvfile.h"
#include "llcorehttputil.h"
#include "llpounceable.h"
+#include "llproxy.h"
+#include "llrand.h"
// Constants
//const char* MESSAGE_LOG_FILENAME = "message.log";
@@ -173,7 +175,7 @@ void LLMessageSystem::init()
mTotalBytesIn = 0;
mTotalBytesOut = 0;
- mDroppedPackets = 0; // total dropped packets in
+ mLostPackets = 0; // total lost packets out
mResentPackets = 0; // total resent packets out
mFailedResendPackets = 0; // total resend failure packets out
mOffCircuitPackets = 0; // total # of off-circuit packets rejected
@@ -184,6 +186,13 @@ void LLMessageSystem::init()
mIncomingCompressedSize = 0;
mCurrentRecvPacketID = 0;
+ mActualBytesIn = 0;
+ mActualBytesOut = 0;
+ mDropPercentage = 0.0f;
+ mPacketsToDrop = 0;
+ mNumDroppedPackets = 0;
+ mNumDroppedPacketsTotal = 0;
+
mMessageFileVersionNumber = 0.f;
mTimingCallback = NULL;
@@ -508,18 +517,17 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count )
bool recv_reliable = false;
bool recv_resent = false;
- S32 acks = 0;
+ S32 num_acks = 0;
S32 true_rcv_size = 0;
+ bool recv_packet_id_checked = false;
U8* buffer = mTrueReceiveBuffer;
- mTrueReceiveSize = mPacketRing.receivePacket(mSocket, (char *)mTrueReceiveBuffer);
+ mTrueReceiveSize = receivePacketOrDrop((char *)mTrueReceiveBuffer, recv_packet_id_checked);
// If you want to dump all received packets into SecondLife.log, uncomment this
//dumpPacketToLog();
receive_size = mTrueReceiveSize;
- mLastSender = mPacketRing.getLastSender();
- mLastReceivingIF = mPacketRing.getLastReceivingInterface();
if (receive_size < (S32) LL_MINIMUM_VALID_PACKET_SIZE)
{
@@ -535,24 +543,23 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count )
}
else
{
- LLHost host;
- LLCircuitData* cdp;
-
- // note if packet acks are appended.
+ // handle any packet ACKs (for outbound messages) found at tail of inbound message
if(buffer[0] & LL_ACK_FLAG)
{
- acks += buffer[--receive_size];
+ // Note: these ACKs may have already been handled if this was a buffered message
+ // but it doesn't hurt to handle them again.
+ num_acks += buffer[--receive_size];
true_rcv_size = receive_size;
- if(receive_size >= ((S32)(acks * sizeof(TPACKETID) + LL_MINIMUM_VALID_PACKET_SIZE)))
+ if(receive_size >= ((S32)(num_acks * sizeof(TPACKETID) + LL_MINIMUM_VALID_PACKET_SIZE)))
{
- receive_size -= acks * sizeof(TPACKETID);
+ receive_size -= num_acks * sizeof(TPACKETID);
}
else
{
// mal-formed packet. ignore it and continue with
// the next one
LL_WARNS("Messaging") << "Malformed packet received. Packet size "
- << receive_size << " with invalid no. of acks " << acks
+ << receive_size << " with invalid no. of acks " << num_acks
<< LL_ENDL;
valid_packet = false;
continue;
@@ -562,20 +569,20 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count )
// process the message as normal
mIncomingCompressedSize = zeroCodeExpand(&buffer, &receive_size);
mCurrentRecvPacketID = ntohl(*((U32*)(&buffer[1])));
- host = getSender();
+ LLHost host = getSender();
const bool resetPacketId = true;
- cdp = findCircuit(host, resetPacketId);
+ LLCircuitData* cdp = findCircuit(host, resetPacketId);
// At this point, cdp is now a pointer to the circuit that
// this message came in on if it's valid, and NULL if the
// circuit was bogus.
- if(cdp && (acks > 0) && ((S32)(acks * sizeof(TPACKETID)) < (true_rcv_size)))
+ if(cdp && (num_acks > 0) && ((S32)(num_acks * sizeof(TPACKETID)) < (true_rcv_size)))
{
TPACKETID packet_id;
U32 mem_id=0;
- for(S32 i = 0; i < acks; ++i)
+ for(S32 i = 0; i < num_acks; ++i)
{
true_rcv_size -= sizeof(TPACKETID);
memcpy(&mem_id, &mTrueReceiveBuffer[true_rcv_size], /* Flawfinder: ignore*/
@@ -630,7 +637,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count )
str << tbuf << "(unknown)"
<< (recv_reliable ? " reliable" : "")
<< " resent "
- << ((acks > 0) ? "acks" : "")
+ << ((num_acks > 0) ? "acks" : "")
<< " DISCARD DUPLICATE";
LL_INFOS("Messaging") << str.str() << LL_ENDL;
}
@@ -680,7 +687,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count )
if ( valid_packet )
{
- logValidMsg(cdp, host, recv_reliable, recv_resent, acks>0 );
+ logValidMsg(cdp, host, recv_reliable, recv_resent, num_acks>0, recv_packet_id_checked );
valid_packet = mTemplateMessageReader->readMessage(buffer, host);
}
@@ -724,7 +731,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count )
// Check to see if we need to print debug info
if ((mt_sec - mCircuitPrintTime) > mCircuitPrintFreq)
{
- mPacketRing.dumpPacketRingStats();
+ dumpPacketRingStats();
dumpCircuitInfo();
mCircuitPrintTime = mt_sec;
}
@@ -749,6 +756,10 @@ S32 LLMessageSystem::getReceiveBytes() const
}
}
+F32 LLMessageSystem::getBufferLoadRate() const
+{
+ return llmax(mHighPriorityInbound.getBufferLoadRate(), mLowPriorityInbound.getBufferLoadRate());
+}
void LLMessageSystem::processAcks(LockMessageChecker&, F32 collect_time)
{
@@ -822,7 +833,300 @@ void LLMessageSystem::processAcks(LockMessageChecker&, F32 collect_time)
S32 LLMessageSystem::drainUdpSocket()
{
- return mPacketRing.drainSocket(mSocket);
+ S32 packet_size = 1;
+ S32 num_loops = 0;
+ S32 old_num_buffered_packets = getNumBufferedPackets();
+ while (packet_size > 0)
+ {
+ packet_size = bufferInboundPacket();
+ ++num_loops;
+ }
+ S32 num_dropped_packets = (num_loops - 1 + old_num_buffered_packets) - getNumBufferedPackets();
+ if (num_dropped_packets > 0)
+ {
+ mNumDroppedPackets += num_dropped_packets;
+ }
+ return getNumBufferedPackets();
+}
+
+bool LLMessageSystem::computeDrop()
+{
+ bool drop = (mDropPercentage > 0.0f && (ll_frand(100.f) < mDropPercentage));
+ if (drop)
+ {
+ ++mPacketsToDrop;
+ }
+ if (mPacketsToDrop > 0)
+ {
+ --mPacketsToDrop;
+ drop = true;
+ }
+ return drop;
+}
+
+bool LLMessageSystem::isHighPriorityMessage(const LLPacketBuffer& pkt) const
+{
+ S32 size = pkt.getSize();
+
+ // avoid stepping out of bounds
+ const S32 MIN_VALID_PACKET_SIZE = LL_PACKET_ID_SIZE + 4;
+ if (size < MIN_VALID_PACKET_SIZE)
+ {
+ return false;
+ }
+
+ // We want to prioritize crucial messages used to establish viewer <--> simulator connection,
+ // which are all low-frequency. A simple approximation is to just prioritize all non high-
+ // frequency messages.
+ //
+ // High frequency messages use a single byte for message_id whereas medium- and low-
+ // frequency messages have 255 at the first byte (which is after the LL_PACKET_ID_SIZE
+ // bytes of packet_id).
+ const U8* header = (const U8*)pkt.getData() + LL_PACKET_ID_SIZE;
+ if (header[0] != 255)
+ {
+ // high-frequency message
+ return false;
+ }
+
+ // BUG: The low-frequency AvatarAppearance message will be ignored if its agent is unknown
+ // and the agent is created upon receipt of its first high-frequency ObjectUpdate message.
+ // This race condition will be exacerbated by our default prioritization strategy.
+ //
+ // WORKAROUND: All middle- and low-frequency messages are high-priority except AvatarAppearance
+ //
+ // AvatarAppearance is "Low 158" which means it is stored in four bytes: 0xff 0xff 0x00 0x9E
+ // the last two bytes represent 158 in a BigEndian U16
+ return header[1] != 255 && header[2] != 0 && header[3] != 158;
+}
+
+void LLMessageSystem::dropPackets(U32 num_to_drop)
+{
+ mPacketsToDrop += num_to_drop;
+}
+
+void LLMessageSystem::setDropPercentage(F32 percent_to_drop)
+{
+ mDropPercentage = percent_to_drop;
+}
+
+S32 LLMessageSystem::receivePacketOrDrop(char* datap, bool& packet_id_already_checked)
+{
+ packet_id_already_checked = false;
+
+ if (getNumBufferedPackets() > 0)
+ {
+ LLHost invalid_host;
+ LLPacketBuffer pkt(invalid_host, nullptr, 0);
+ if (!mHighPriorityInbound.popPacket(pkt))
+ {
+ mLowPriorityInbound.popPacket(pkt);
+ }
+
+ S32 packet_size = pkt.getSize();
+ mLastSender = pkt.getHost();
+ mLastReceivingIF = pkt.getReceivingInterface();
+ packet_id_already_checked = pkt.getPacketIDChecked();
+
+ if (packet_size > 0)
+ {
+ memcpy(datap, pkt.getData(), packet_size);
+ }
+ return packet_size;
+ }
+
+ // Read directly from the socket. checkPacketInID() has not run yet for
+ // this packet.
+ bool drop = computeDrop();
+ S32 packet_size = 0;
+ if (LLProxy::isSOCKSProxyEnabled())
+ {
+ char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */
+ packet_size = receive_packet(mSocket, buffer);
+ if (packet_size > 0)
+ {
+ mActualBytesIn += packet_size;
+ }
+ if (packet_size > SOCKS_HEADER_SIZE)
+ {
+ if (drop)
+ {
+ packet_size = 0;
+ }
+ else
+ {
+ // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6)
+ packet_size -= SOCKS_HEADER_SIZE;
+ memcpy(datap, buffer + SOCKS_HEADER_SIZE, packet_size);
+ proxywrap_t* header = static_cast<proxywrap_t*>(static_cast<void*>(buffer));
+ mLastSender.setAddress(header->addr);
+ mLastSender.setPort(ntohs(header->port));
+ mLastReceivingIF = ::get_receiving_interface();
+ }
+ }
+ else
+ {
+ packet_size = 0;
+ }
+ }
+ else
+ {
+ packet_size = receive_packet(mSocket, datap);
+ if (packet_size > 0)
+ {
+ mActualBytesIn += packet_size;
+ if (drop)
+ {
+ packet_size = 0;
+ }
+ else
+ {
+ mLastSender = ::get_sender();
+ mLastReceivingIF = ::get_receiving_interface();
+ }
+ }
+ }
+ return packet_size;
+}
+
+S32 LLMessageSystem::bufferInboundPacket()
+{
+ LLHost invalid_host;
+ LLPacketBuffer pkt(invalid_host, nullptr, 0);
+ S32 packet_size = 0;
+
+ if (LLProxy::isSOCKSProxyEnabled())
+ {
+ char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */
+ packet_size = receive_packet(mSocket, buffer);
+ if (packet_size > 0)
+ {
+ mActualBytesIn += packet_size;
+ if (packet_size > SOCKS_HEADER_SIZE)
+ {
+ // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6)
+ proxywrap_t* header = static_cast<proxywrap_t*>(static_cast<void*>(buffer));
+ LLHost sender;
+ sender.setAddress(header->addr);
+ sender.setPort(ntohs(header->port));
+ packet_size -= SOCKS_HEADER_SIZE;
+ pkt.init(buffer + SOCKS_HEADER_SIZE, packet_size, sender);
+ }
+ else
+ {
+ packet_size = 0;
+ }
+ }
+ }
+ else
+ {
+ pkt.init(mSocket);
+ packet_size = pkt.getSize();
+ if (packet_size > 0)
+ {
+ mActualBytesIn += packet_size;
+ }
+ }
+
+ if (packet_size >= (S32)LL_MINIMUM_VALID_PACKET_SIZE && !computeDrop())
+ {
+ const char* data = pkt.getData();
+ LLCircuitData* cdp = mCircuitInfo.findCircuit(pkt.getHost());
+ TPACKETID recv_packet_id = ntohl(*((U32*)(&data[1])));
+
+ // Harvest piggybacked ACKs for outbound messages from the packet tail of this inbound message
+ if (cdp && (data[0] & LL_ACK_FLAG))
+ {
+ U8 num_acks = (U8)data[packet_size - 1];
+ S32 true_rcv_size = packet_size - 1;
+ if (true_rcv_size >= (S32)(num_acks * sizeof(TPACKETID) + LL_MINIMUM_VALID_PACKET_SIZE))
+ {
+ TPACKETID ack_id;
+ U32 mem_id = 0;
+ for (S32 i = 0; i < num_acks; ++i)
+ {
+ true_rcv_size -= sizeof(TPACKETID);
+ memcpy(&mem_id, &data[true_rcv_size], sizeof(TPACKETID)); /* Flawfinder: ignore */
+ ack_id = ntohl(mem_id);
+ cdp->ackReliablePacket(ack_id);
+ }
+ if (!cdp->getUnackedPacketCount())
+ {
+ mCircuitInfo.mUnackedCircuitMap.erase(cdp->mHost);
+ }
+ }
+ }
+
+ if (cdp)
+ {
+ // ACK inbound reliable packet ASAP
+ if ((data[0] & LL_RELIABLE_FLAG))
+ {
+ cdp->collectRAck(recv_packet_id);
+ }
+
+ // Check packet sequencing here, in true socket-arrival order, before
+ // this packet is sorted into the high/low priority inbound queue.
+ // Skip genuine duplicate resends, same as checkMessages()/logValidMsg()
+ // would do further downstream.
+ bool recv_resent = (data[0] & LL_RESENT_FLAG) != 0;
+ if (!recv_resent || !cdp->isDuplicateResend(recv_packet_id))
+ {
+ cdp->checkPacketInID(recv_packet_id, recv_resent);
+ pkt.setPacketIDChecked(true);
+ }
+ }
+
+ if (isHighPriorityMessage(pkt))
+ {
+ mHighPriorityInbound.pushPacket(pkt);
+ }
+ else
+ {
+ mLowPriorityInbound.pushPacket(pkt);
+ }
+ }
+
+ return packet_size;
+}
+
+bool LLMessageSystem::sendPacketToSocket(const char* datap, S32 data_size, LLHost host)
+{
+ mActualBytesOut += data_size;
+ if (!LLProxy::isSOCKSProxyEnabled())
+ {
+ return send_packet(mSocket, datap, data_size, host.getAddress(), host.getPort());
+ }
+
+ char headered_send_buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE];
+
+ proxywrap_t* socks_header = static_cast<proxywrap_t*>(static_cast<void*>(&headered_send_buffer));
+ socks_header->rsv = 0;
+ socks_header->addr = host.getAddress();
+ socks_header->port = htons(host.getPort());
+ socks_header->atype = ADDRESS_IPV4;
+ socks_header->frag = 0;
+
+ memcpy(headered_send_buffer + SOCKS_HEADER_SIZE, datap, data_size);
+
+ return send_packet(mSocket,
+ headered_send_buffer,
+ data_size + SOCKS_HEADER_SIZE,
+ LLProxy::getInstance()->getUDPProxy().getAddress(),
+ LLProxy::getInstance()->getUDPProxy().getPort());
+}
+
+void LLMessageSystem::dumpPacketRingStats()
+{
+ mNumDroppedPacketsTotal += mNumDroppedPackets;
+ LL_INFOS("Messaging") << "buffered_packets=" << getNumBufferedPackets()
+ << "buffered_bytes=" << (mHighPriorityInbound.getNumBufferedBytes() + mLowPriorityInbound.getNumBufferedBytes())
+ << "recently_dropped=" << mNumDroppedPackets
+ << "total_dropped=" << mNumDroppedPacketsTotal
+ << "dropped_percentage=" << mDropPercentage << "%"
+ << "bytes_IN=" << mActualBytesIn
+ << "bytes_OUT=" << mActualBytesOut << LL_ENDL;
+ mNumDroppedPackets = 0;
}
void LLMessageSystem::copyMessageReceivedToSend()
@@ -1277,7 +1581,7 @@ S32 LLMessageSystem::sendMessage(const LLHost &host)
}
bool success;
- success = mPacketRing.sendPacket(mSocket, (char *)buf_ptr, buffer_length, host);
+ success = sendPacketToSocket((char *)buf_ptr, buffer_length, host);
if (!success)
{
@@ -1401,7 +1705,13 @@ void LLMessageSystem::logTrustedMsgFromUntrustedCircuit( const LLHost& host )
}
}
-void LLMessageSystem::logValidMsg(LLCircuitData *cdp, const LLHost& host, bool recv_reliable, bool recv_resent, bool recv_acks )
+void LLMessageSystem::logValidMsg(
+ LLCircuitData *cdp,
+ const LLHost& host,
+ bool recv_reliable,
+ bool recv_resent,
+ bool recv_acks,
+ bool skip_packet_id_check )
{
if (mNumMessageCounts >= MAX_MESSAGE_COUNT_NUM)
{
@@ -1418,8 +1728,13 @@ void LLMessageSystem::logValidMsg(LLCircuitData *cdp, const LLHost& host, bool r
if (cdp)
{
- // update circuit packet ID tracking (missing/out of order packets)
- cdp->checkPacketInID( mCurrentRecvPacketID, recv_resent );
+ if (!skip_packet_id_check)
+ {
+ // update circuit packet ID tracking (missing/out of order packets)
+ // Already done in bufferInboundPacket(), in true socket-arrival
+ // order, if this packet came off the high/low priority queues.
+ cdp->checkPacketInID( mCurrentRecvPacketID, recv_resent );
+ }
cdp->addBytesIn( (S32Bytes)mTrueReceiveSize );
}
@@ -2607,7 +2922,7 @@ void LLMessageSystem::summarizeLogs(std::ostream& str)
str << buffer << std::endl << std::endl;
buffer = llformat( "SendPacket failures: %20d", mSendPacketFailureCount);
str << buffer << std::endl;
- buffer = llformat( "Dropped packets: %20d", mDroppedPackets);
+ buffer = llformat( "Dropped packets: %20d", getTotalNumDroppedPackets());
str << buffer << std::endl;
buffer = llformat( "Resent packets: %20d", mResentPackets);
str << buffer << std::endl;
@@ -2639,6 +2954,7 @@ void LLMessageSystem::summarizeLogs(std::ostream& str)
void end_messaging_system(bool print_summary)
{
+ LL_PROFILE_ZONE_SCOPED;
gTransferManager.cleanup();
LLTransferTargetVFile::updateQueue(true); // shutdown LLTransferTargetVFile
if (gMessageSystem)
@@ -3333,7 +3649,7 @@ void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_
void LLMessageSystem::dumpPacketToLog()
{
- LL_WARNS("Messaging") << "Packet Dump from:" << mPacketRing.getLastSender() << LL_ENDL;
+ LL_WARNS("Messaging") << "Packet Dump from:" << mLastSender << LL_ENDL;
LL_WARNS("Messaging") << "Packet Size:" << mTrueReceiveSize << LL_ENDL;
char line_buffer[256]; /* Flawfinder: ignore */
S32 i;
diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h
index 14cdc48a07..e23a67c04a 100644
--- a/indra/llmessage/message.h
+++ b/indra/llmessage/message.h
@@ -119,6 +119,49 @@ public:
// Repeat for number of messages in file
//
+// UDP Packet Buffer Layout
+//
+// Every UDP message sent or received by LLMessageSystem uses the following
+// on-wire layout. Offsets are defined in EPacketHeaderLayout below.
+//
+// Byte(s) Name Description
+// ------- ---- -----------
+// 0 Flags Bit-field (see flag constants below):
+// 0x80 LL_ZERO_CODE_FLAG – body is zero-run-length encoded
+// 0x40 LL_RELIABLE_FLAG – sender expects a packet ACK
+// 0x20 LL_RESENT_FLAG – this is a retransmission
+// 0x10 LL_ACK_FLAG – piggybacked ACKs are appended
+// at the tail of this packet
+// 1-4 Packet ID Sequence number, U32 in network (big-endian) byte order.
+// 5 Offset Byte offset from PHL_NAME to the start of the message
+// body (past the message-ID bytes). Zero for most messages.
+// 6+ Message ID Variable-length message type identifier:
+// High-frequency (1 byte, values 0x01–0xFE)
+// Medium-frequency (2 bytes, 0xFF hh)
+// Low-frequency (4 bytes, 0xFF 0xFF hh ll)
+// ... Body Message block data, described by the message template.
+// Present only when LL_ZERO_CODE_FLAG is clear; otherwise
+// the body (everything after byte 5) is zero-coded (see
+// below). The 6-byte header is never zero-coded.
+//
+// Optional ACK tail (present when LL_ACK_FLAG is set in the flags byte):
+//
+// ... ACK IDs N packet-sequence IDs being acknowledged, each a U32 in
+// network byte order, packed contiguously immediately before
+// the ACK count byte. Read in reverse: walk backwards from
+// just before the count byte, 4 bytes at a time.
+// last ACK Count U8 giving N, the number of appended ACK IDs (max 255).
+// This is the very last byte of the UDP payload.
+//
+// Zero-coding (applied when LL_ZERO_CODE_FLAG is set):
+//
+// Runs of zero bytes in the body are replaced by a two-byte token:
+// 0x00 N – represents (N + 1) zero bytes, for N in 1..254
+// 0x00 0x00 N – represents (256 + N) zero bytes (wrap/overflow case)
+// A literal 0x00 byte that starts no run is encoded as 0x00 0x00 0x00.
+// The six-byte packet header is excluded from zero-coding and is always
+// transmitted as-is.
+
// Constants
const S32 MAX_MESSAGE_INTERNAL_NAME_SIZE = 255;
const S32 MAX_BUFFER_SIZE = NET_BUFFER_SIZE;
@@ -291,8 +334,11 @@ class LLMessageSystem : public LLMessageSenderInterface
bool mBlockUntrustedInterface;
LLHost mUntrustedInterface;
+ protected:
+ LLPacketRing mHighPriorityInbound;
+ LLPacketRing mLowPriorityInbound;
+
public:
- LLPacketRing mPacketRing;
LLReliablePacketParams mReliablePacketParams;
// Set this flag to true when you want *very* verbose logs.
@@ -334,7 +380,7 @@ public:
U32 mReliablePacketsIn; // total reliable packets in
U32 mReliablePacketsOut; // total reliable packets out
- U32 mDroppedPackets; // total dropped packets in
+ U32 mLostPackets; // total reliable outbound packets declared lost
U32 mResentPackets; // total resent packets out
U32 mFailedResendPackets; // total resend failure packets out
U32 mOffCircuitPackets; // total # of off-circuit packets rejected
@@ -420,6 +466,25 @@ public:
// returns total number of buffered packets after the drain
S32 drainUdpSocket();
+ // Inbound Packet-loss simulation controls
+ void dropPackets(U32 num_to_drop);
+ void setDropPercentage(F32 percent_to_drop);
+
+ // UDP byte-accounting
+ S32 getActualInBytes() const { return mActualBytesIn; }
+ S32 getActualOutBytes() const { return mActualBytesOut; }
+ S32 getAndResetActualInBits() { S32 bits = mActualBytesIn * 8; mActualBytesIn = 0; return bits; }
+ S32 getAndResetActualOutBits() { S32 bits = mActualBytesOut * 8; mActualBytesOut = 0; return bits; }
+
+ // Get number of "dropped" inbound packets
+ S32 getTotalNumDroppedPackets() const { return mNumDroppedPacketsTotal + mNumDroppedPackets; }
+
+ S32 getNumBufferedPackets() const { return mHighPriorityInbound.getNumBufferedPackets() + mLowPriorityInbound.getNumBufferedPackets(); }
+ void dumpPacketRingStats();
+
+ // Send datap to host via mSocket (with SOCKS proxy support if enabled).
+ bool sendPacketToSocket(const char* datap, S32 data_size, LLHost host);
+
bool isMessageFast(const char *msg);
bool isMessage(const char *msg)
{
@@ -754,7 +819,7 @@ public:
S32 getReceiveBytes() const;
S32 getUnackedListSize() const { return mUnackedListSize; }
- F32 getBufferLoadRate() const { return mPacketRing.getBufferLoadRate(); }
+ F32 getBufferLoadRate() const;
//const char* getCurrentSMessageName() const { return mCurrentSMessageName; }
//const char* getCurrentSBlockName() const { return mCurrentSBlockName; }
@@ -845,7 +910,7 @@ private:
void logMsgFromInvalidCircuit( const LLHost& sender, bool recv_reliable );
void logTrustedMsgFromUntrustedCircuit( const LLHost& sender );
- void logValidMsg(LLCircuitData *cdp, const LLHost& sender, bool recv_reliable, bool recv_resent, bool recv_acks );
+ void logValidMsg(LLCircuitData *cdp, const LLHost& sender, bool recv_reliable, bool recv_resent, bool recv_acks, bool skip_packet_id_check );
class LLMessageCountInfo
{
@@ -898,6 +963,34 @@ private:
S32 mIncomingCompressedSize; // original size of compressed msg (0 if uncomp.)
TPACKETID mCurrentRecvPacketID; // packet ID of current receive packet (for reporting)
+ // Socket I/O helpers
+
+ // Receive one packet: pop from ring if buffered, else read from mSocket.
+ // Sets mLastSender and mLastReceivingIF.
+ // Sets packet_id_already_checked to whether checkPacketInID() was already
+ // run for this packet back when it was buffered (see bufferInboundPacket()).
+ // Returns packet_size, or 0 if no packet or packet was dropped.
+ S32 receivePacketOrDrop(char* datap, bool& packet_id_already_checked);
+
+ // Read one raw packet from mSocket into inbound message queues
+ // Returns packet_size (0 if no packet was available).
+ S32 bufferInboundPacket();
+
+ // Returns true if the next inbound packet should be intentionally dropped.
+ bool computeDrop();
+
+ // Returns true if pkt carries a high-priority message and should be queued
+ // in mHighPriorityInbound.
+ bool isHighPriorityMessage(const LLPacketBuffer& pkt) const;
+
+ // Packet-loss simulation and byte-accounting state
+ S32 mActualBytesIn;
+ S32 mActualBytesOut;
+ F32 mDropPercentage; // % of inbound packets to drop
+ U32 mPacketsToDrop; // drop next N inbound packets
+ S32 mNumDroppedPackets; // inbound
+ S32 mNumDroppedPacketsTotal;// inbound
+
LLMessageBuilder* mMessageBuilder;
LLTemplateMessageBuilder* mTemplateMessageBuilder;
LLSDMessageBuilder* mLLSDMessageBuilder;
diff --git a/indra/llmessage/net.cpp b/indra/llmessage/net.cpp
index 2be5a9e5b6..0df38d040a 100644
--- a/indra/llmessage/net.cpp
+++ b/indra/llmessage/net.cpp
@@ -326,7 +326,7 @@ S32 receive_packet(int hSocket, char * receiveBuffer)
return 0;
if (WSAECONNRESET == WSAGetLastError())
return 0;
- LL_INFOS() << "receivePacket() failed, Error: " << WSAGetLastError() << LL_ENDL;
+ LL_INFOS() << "receive_packet() failed, Error: " << WSAGetLastError() << LL_ENDL;
}
return nRet;
diff --git a/indra/llphysicsextensionsos/llphysicsextensions.cpp b/indra/llphysicsextensionsos/llphysicsextensions.cpp
index 3bb8ffbf1a..295ab41a17 100644
--- a/indra/llphysicsextensionsos/llphysicsextensions.cpp
+++ b/indra/llphysicsextensionsos/llphysicsextensions.cpp
@@ -72,6 +72,7 @@
//=============================================================================
/*static */bool LLPhysicsExtensions::quitSystem()
{
+ LL_PROFILE_ZONE_SCOPED;
return LLPhysicsExtensionsImpl::quitSystem();
}
//=============================================================================
diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp
index 71414d3a19..2269d7adc8 100644
--- a/indra/llplugin/llpluginclassmedia.cpp
+++ b/indra/llplugin/llpluginclassmedia.cpp
@@ -33,12 +33,16 @@
#include "llpluginmessageclasses.h"
#include "llcontrol.h"
+#include <cmath>
+
extern LLControlGroup gSavedSettings;
#if LL_DARWIN || LL_LINUX
extern bool gHiDPISupport;
#endif
static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256;
+static const U32 MIN_DEBUG_PORT = 1024;
+static const U32 MAX_DEBUG_PORT = 65535;
static int nextPowerOf2( int value )
{
@@ -90,6 +94,7 @@ bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::s
void LLPluginClassMedia::reset()
{
+ LL_PROFILE_ZONE_SCOPED;
if(mPlugin)
{
mPlugin->requestShutdown();
@@ -960,6 +965,34 @@ void LLPluginClassMedia::showPageSource()
sendMessage(message);
}
+
+static U32 assignCefDebuggingPort()
+{
+ U32 base_port = gSavedSettings.getU32("CEFRemoteDebuggingPort");
+ if (base_port == 0)
+ {
+ return 0;
+ }
+ base_port = llclamp<U32>(base_port, MIN_DEBUG_PORT, MAX_DEBUG_PORT);
+
+ static U32 last_base_port = 0;
+ static U32 offset = 0;
+ if (base_port != last_base_port)
+ {
+ last_base_port = base_port;
+ offset = 0;
+ }
+
+ U32 new_port = base_port + offset;
+ if (new_port > MAX_DEBUG_PORT)
+ {
+ return 0;
+ }
+
+ ++offset;
+ return new_port;
+}
+
void LLPluginClassMedia::setUserDataPath(const std::string &user_data_path_cache,
const std::string &username,
const std::string &user_data_path_cef_log)
@@ -971,6 +1004,10 @@ void LLPluginClassMedia::setUserDataPath(const std::string &user_data_path_cache
bool cef_verbose_log = gSavedSettings.getBOOL("CefVerboseLog");
message.setValueBoolean("cef_verbose_log", cef_verbose_log);
+
+ U32 cef_remote_debugging_port = assignCefDebuggingPort();
+ message.setValueU32("cef_remote_debugging_port", cef_remote_debugging_port);
+ mCefRemoteDebuggingPort = cef_remote_debugging_port;
sendMessage(message);
}
@@ -1625,7 +1662,13 @@ void LLPluginClassMedia::setLoop(bool loop)
void LLPluginClassMedia::setVolume(float volume)
{
- if(volume != mRequestedVolume)
+ // VLC's volume is integer, 0 to 100 range. When converted from float,
+ // changes below 0.01 won't be noticed by VLC.
+ // CEF's audio range is DWORD, 0 to 65535. But changes so small are
+ // inaudible and don't warrant the overhead, so use a bigger epsilon
+ // to avoid extra messages and locking.
+ constexpr float VOLUME_EPSILON = 0.002f;
+ if (std::abs(volume - mRequestedVolume) > VOLUME_EPSILON)
{
mRequestedVolume = volume;
diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h
index 43514687ed..153a7eb823 100644
--- a/indra/llplugin/llpluginclassmedia.h
+++ b/indra/llplugin/llpluginclassmedia.h
@@ -169,6 +169,11 @@ public:
std::string getPluginVersion() { return mPlugin?mPlugin->getPluginVersion():std::string(""); };
+ int getProcessID() { return mPlugin ? (int)mPlugin->getProcessID() : 0; };
+
+ // 0 means remote debugging is disabled for this plugin instance.
+ U32 getCefRemoteDebuggingPort() const { return mCefRemoteDebuggingPort; };
+
bool getDisableTimeout() { return mPlugin?mPlugin->getDisableTimeout():false; };
void setDisableTimeout(bool disable) { if(mPlugin) mPlugin->setDisableTimeout(disable); };
@@ -438,6 +443,8 @@ protected:
F64 mSleepTime;
+ U32 mCefRemoteDebuggingPort {0};
+
bool mCanUndo;
bool mCanRedo;
bool mCanCut;
diff --git a/indra/llplugin/llpluginmessagepipe.cpp b/indra/llplugin/llpluginmessagepipe.cpp
index 085afc0944..1e58e6c49b 100644
--- a/indra/llplugin/llpluginmessagepipe.cpp
+++ b/indra/llplugin/llpluginmessagepipe.cpp
@@ -121,8 +121,9 @@ bool LLPluginMessagePipe::addMessage(const std::string &message)
mOutputStartIndex = 0;
}
+ mOutput.reserve(mOutput.size() + message.size() + 1);
mOutput += message;
- mOutput += MESSAGE_DELIMITER; // message separator
+ mOutput.push_back(MESSAGE_DELIMITER); // message separator
return true;
}
diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h
index ea604ca8d7..3cfd4211d7 100644
--- a/indra/llplugin/llpluginprocessparent.h
+++ b/indra/llplugin/llpluginprocessparent.h
@@ -119,6 +119,8 @@ public:
bool getDisableTimeout() { return mDisableTimeout; };
void setDisableTimeout(bool disable) { mDisableTimeout = disable; };
+ LLProcess::id getProcessID() const { return mProcess ? mProcess->getProcessID() : 0; }
+
void setLaunchTimeout(F32 timeout) { mPluginLaunchTimeout = timeout; };
void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; };
diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp
index 7a638dd625..fd755e35dd 100644
--- a/indra/llprimitive/llprimitive.cpp
+++ b/indra/llprimitive/llprimitive.cpp
@@ -1370,6 +1370,12 @@ bool LLPrimitive::packTEMessage(LLDataPacker &dp) const
S32 LLPrimitive::parseTEMessage(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num, LLTEContents& tec)
{
+ return parseTEMessage(mesgsys, block_name, block_num, tec, getNumTEs());
+}
+
+// static
+S32 LLPrimitive::parseTEMessage(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num, LLTEContents& tec, U8 face_count)
+{
S32 retval = 0;
// temp buffer for material ID processing
// data will end up in tec.material_id[]
@@ -1403,7 +1409,7 @@ S32 LLPrimitive::parseTEMessage(LLMessageSystem* mesgsys, char const* block_name
tec.packed_buffer[tec.size] = 0x00;
++tec.size;
- tec.face_count = llmin((U32)getNumTEs(),(U32)LLTEContents::MAX_TES);
+ tec.face_count = llmin((U32)face_count,(U32)LLTEContents::MAX_TES);
U8 *cur_ptr = tec.packed_buffer;
LL_DEBUGS("TEXTUREENTRY") << "Texture Entry with buffere sized: " << tec.size << LL_ENDL;
diff --git a/indra/llprimitive/llprimitive.h b/indra/llprimitive/llprimitive.h
index c3e3e19ee9..21bf52610e 100644
--- a/indra/llprimitive/llprimitive.h
+++ b/indra/llprimitive/llprimitive.h
@@ -502,6 +502,9 @@ public:
S32 unpackTEMessage(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num); // Variable num of blocks
S32 unpackTEMessage(LLDataPacker &dp);
S32 parseTEMessage(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num, LLTEContents& tec);
+ // Same as above, but usable before an instance exists to derive the face count from
+ // (e.g. decoding a message for an object that hasn't been created yet).
+ static S32 parseTEMessage(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num, LLTEContents& tec, U8 face_count);
S32 applyParsedTEMessage(LLTEContents& tec);
#ifdef CHECK_FOR_FINITE
diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp
index cc4d6ad5ef..5af6b61513 100644
--- a/indra/llrender/llfontfreetype.cpp
+++ b/indra/llrender/llfontfreetype.cpp
@@ -110,6 +110,7 @@ LLFontManager::~LLFontManager()
LLFontGlyphInfo::LLFontGlyphInfo(U32 index, EFontGlyphType glyph_type)
: mGlyphIndex(index),
mGlyphType(glyph_type),
+ mChar(0),
mWidth(0), // In pixels
mHeight(0), // In pixels
mXAdvance(0.f), // In pixels
@@ -127,6 +128,7 @@ LLFontGlyphInfo::LLFontGlyphInfo(U32 index, EFontGlyphType glyph_type)
LLFontGlyphInfo::LLFontGlyphInfo(const LLFontGlyphInfo& fgi)
: mGlyphIndex(fgi.mGlyphIndex)
, mGlyphType(fgi.mGlyphType)
+ , mChar(fgi.mChar)
, mWidth(fgi.mWidth)
, mHeight(fgi.mHeight)
, mXAdvance(fgi.mXAdvance)
@@ -151,7 +153,8 @@ LLFontFreetype::LLFontFreetype()
mFTFace(nullptr),
mRenderGlyphCount(0),
mStyle(0),
- mPointSize(0)
+ mPointSize(0),
+ mMaxDigitWidth(0.0f)
{
}
@@ -347,6 +350,10 @@ F32 LLFontFreetype::getXAdvance(llwchar wch) const
LLFontGlyphInfo* gi = getGlyphInfo(wch, EFontGlyphType::Unspecified);
if (gi)
{
+ if (wch >= '0' && wch <= '9' && mMaxDigitWidth > 0.0f)
+ {
+ return mMaxDigitWidth;
+ }
return gi->mXAdvance;
}
else
@@ -367,6 +374,12 @@ F32 LLFontFreetype::getXAdvance(const LLFontGlyphInfo* glyph) const
if (mFTFace == nullptr)
return 0.0;
+ // Use max digit width for tabular numbers
+ if (mWeight > 0 && glyph->mChar >= '0' && glyph->mChar <= '9' && mMaxDigitWidth > 0.0f)
+ {
+ return mMaxDigitWidth;
+ }
+
return glyph->mXAdvance;
}
@@ -388,8 +401,27 @@ F32 LLFontFreetype::getXKerning(const LLFontGlyphInfo* left_glyph_info, const LL
if (mFTFace == nullptr)
return 0.0;
- U32 left_glyph = left_glyph_info ? left_glyph_info->mGlyphIndex : 0;
- U32 right_glyph = right_glyph_info ? right_glyph_info->mGlyphIndex : 0;
+ U32 left_glyph = 0;
+ U32 right_glyph = 0;
+
+ if (left_glyph_info)
+ {
+ if (mWeight > 0 && left_glyph_info->mChar >= '0' && left_glyph_info->mChar <= '9')
+ {
+ // Disable kerning for digits when using tabular numbers
+ return 0.0;
+ }
+ left_glyph = left_glyph_info->mGlyphIndex;
+ }
+ if (right_glyph_info)
+ {
+ if (mWeight > 0 && right_glyph_info->mChar >= '0' && right_glyph_info->mChar <= '9')
+ {
+ // Disable kerning for digits when using tabular numbers
+ return 0.0;
+ }
+ right_glyph = right_glyph_info->mGlyphIndex;
+ }
FT_Vector delta;
@@ -589,6 +621,7 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l
mFontBitmapCachep->nextOpenPos(width, pos_x, pos_y, bitmap_glyph_type, bitmap_num);
LLFontGlyphInfo* gi = new LLFontGlyphInfo(glyph_index, requested_glyph_type);
+ gi->mChar = wch;
gi->mXBitmapOffset = pos_x;
gi->mYBitmapOffset = pos_y;
gi->mBitmapEntry = std::make_pair(bitmap_glyph_type, bitmap_num);
@@ -605,6 +638,14 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l
gi->mXAdvance = fontp->mFTFace->glyph->advance.x / 64.f;
gi->mYAdvance = fontp->mFTFace->glyph->advance.y / 64.f;
+ if (mWeight > 0 && wch >= '0' && wch <= '9')
+ {
+ // Digits are supposed to be preloaded, and buffers
+ // refresh when new chars get added, so this lazy load
+ // should not cause any issues.
+ mMaxDigitWidth = llmax(mMaxDigitWidth, gi->mXAdvance);
+ }
+
insertGlyphInfo(wch, gi);
if (requested_glyph_type != bitmap_glyph_type)
@@ -761,7 +802,50 @@ void LLFontFreetype::renderGlyph(EFontGlyphType bitmap_type, U32 glyph_index, ll
llassert_always_msg(FT_Err_Ok == error, message.c_str());
}
- llassert_always(! FT_Render_Glyph(mFTFace->glyph, gFontRenderMode) );
+ // TODO: Make this more sturdy, make asserts/ll_errs conditional
+ // to non-critical characters.
+ // Temporarily leaving them for data gathering, but unicode chars
+ // like emojis should not cause the app to crash and should either
+ // fallback to some predetermined bitmap or simply return.
+
+ // Verify glyph slot is valid
+ if (!mFTFace->glyph)
+ {
+ LL_ERRS() << "FT_Load_Glyph succeeded but glyph slot is null for wchar " << llformat("U+%xu", U32(wch)) << LL_ENDL;
+ return;
+ }
+
+ // Check if bitmap buffer is already allocated
+ // It can potentially be preallocated for:
+ // 1. SVG/color glyphs rendered by FreeType's SVG_RendererHooks
+ // 2. Embedded bitmap fonts
+ // 3. Some Color emoji that use FT_LOAD_COLOR
+ if (!mFTFace->glyph->bitmap.buffer)
+ {
+ error = FT_Render_Glyph(mFTFace->glyph, gFontRenderMode);
+ if (error != FT_Err_Ok)
+ {
+ std::string render_message = llformat(
+ "Error %d (%s) rendering wchar %u glyph %u: format=%lu, pixel_mode=%d, render_mode=%d",
+ error, FT_Error_String(error), wch, glyph_index,
+ (unsigned long)mFTFace->glyph->format, mFTFace->glyph->bitmap.pixel_mode, gFontRenderMode);
+
+ // Try with FT_RENDER_MODE_NORMAL as fallback
+ if (gFontRenderMode != FT_RENDER_MODE_NORMAL)
+ {
+ LL_WARNS_ONCE() << render_message << LL_ENDL;
+ error = FT_Render_Glyph(mFTFace->glyph, FT_RENDER_MODE_NORMAL);
+ if (error != FT_Err_Ok)
+ {
+ LL_ERRS() << "Fallback to FT_RENDER_MODE_NORMAL failed. " << render_message << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_ERRS() << render_message << LL_ENDL;
+ }
+ }
+ }
mRenderGlyphCount++;
}
@@ -797,6 +881,7 @@ void LLFontFreetype::resetBitmapCache()
}
mCharGlyphInfoMap.clear();
mFontBitmapCachep->reset();
+ mMaxDigitWidth = 0.0f;
// Adding default glyph is skipped for fallback fonts here as well as in loadFace().
// This if was added as fix for EXT-4971.
diff --git a/indra/llrender/llfontfreetype.h b/indra/llrender/llfontfreetype.h
index 12a4052cae..3191ca8df7 100644
--- a/indra/llrender/llfontfreetype.h
+++ b/indra/llrender/llfontfreetype.h
@@ -77,6 +77,8 @@ struct LLFontGlyphInfo
U32 mGlyphIndex;
EFontGlyphType mGlyphType;
+ llwchar mChar;
+
// Metrics
S32 mWidth; // In pixels
@@ -148,6 +150,9 @@ public:
F32 getXKerning(llwchar char_left, llwchar char_right) const; // Get the kerning between the two characters
F32 getXKerning(const LLFontGlyphInfo* left_glyph_info, const LLFontGlyphInfo* right_glyph_info) const; // Get the kerning between the two characters
+ F32 getMaxDigitWidth() const { return mMaxDigitWidth; }
+ S32 getFontWeight() const { return mWeight; }
+
LLFontGlyphInfo* getGlyphInfo(llwchar wch, EFontGlyphType glyph_type) const;
void reset(F32 vert_dpi, F32 horz_dpi);
@@ -186,6 +191,7 @@ private:
F32 mAscender;
F32 mDescender;
F32 mLineHeight;
+ mutable F32 mMaxDigitWidth;
LLFT_Face mFTFace;
diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp
index 5d99c35047..bb5a12375e 100644
--- a/indra/llrender/llfontgl.cpp
+++ b/indra/llrender/llfontgl.cpp
@@ -345,6 +345,14 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
break;
}
+ // Calculate horizontal offset for tabular numbers (center narrow digits)
+ F32 x_offset = 0.0f;
+ if (mFontFreetype->getFontWeight() > 0 && fgi->mChar >= '0' && fgi->mChar <= '9' && mFontFreetype->getMaxDigitWidth() > 0.0f)
+ {
+ // use mXAdvance directly here, since we don't want to get max width instead.
+ x_offset = (mFontFreetype->getMaxDigitWidth() - fgi->mXAdvance) * 0.5f;
+ }
+
// Draw the text at the appropriate location
//Specify vertices and texture coordinates
LLRectf uv_rect((fgi->mXBitmapOffset) * inv_width,
@@ -352,9 +360,9 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
(fgi->mXBitmapOffset + fgi->mWidth) * inv_width,
(fgi->mYBitmapOffset - PAD_UVY) * inv_height);
// snap glyph origin to whole screen pixel
- LLRectf screen_rect((F32)ll_round(cur_render_x + (F32)fgi->mXBearing),
+ LLRectf screen_rect((F32)ll_round(cur_render_x + (F32)fgi->mXBearing + x_offset),
(F32)ll_round(cur_render_y + (F32)fgi->mYBearing),
- (F32)ll_round(cur_render_x + (F32)fgi->mXBearing) + (F32)fgi->mWidth,
+ (F32)ll_round(cur_render_x + (F32)fgi->mXBearing + x_offset) + (F32)fgi->mWidth,
(F32)ll_round(cur_render_y + (F32)fgi->mYBearing) - (F32)fgi->mHeight);
if (glyph_count >= GLYPH_BATCH_SIZE)
@@ -375,7 +383,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
col, style_to_add, shadow, drop_shadow_strength);
chars_drawn++;
- cur_x += fgi->mXAdvance;
+ cur_x += mFontFreetype->getXAdvance(fgi);
cur_y += fgi->mYAdvance;
llwchar next_char = wstr[i+1];
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index 5357a79bae..8545f634a3 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -1042,11 +1042,6 @@ void LLGLManager::initWGL()
{
LL_WARNS("RenderInit") << "No ARB WGL PBuffer extensions" << LL_ENDL;
}
-
- if(!mGLExtensions.contains("WGL_ARB_render_texture"))
- {
- LL_WARNS("RenderInit") << "No ARB WGL render texture extensions" << LL_ENDL;
- }
#endif
}
@@ -1264,7 +1259,7 @@ bool LLGLManager::initGL()
}
if (mVRAM != 0)
{
- LL_WARNS("RenderInit") << "VRAM Detected (AMDAssociations):" << mVRAM << LL_ENDL;
+ LL_INFOS("RenderInit") << "VRAM Detected (AMDAssociations):" << mVRAM << LL_ENDL;
}
} else
#endif
@@ -1279,7 +1274,7 @@ bool LLGLManager::initGL()
if (mVRAM != 0)
{
- LL_WARNS("RenderInit") << "VRAM Detected (GLXMesaQueryRenderer):" << mVRAM << LL_ENDL;
+ LL_INFOS("RenderInit") << "VRAM Detected (GLXMesaQueryRenderer):" << mVRAM << LL_ENDL;
}
}
}
@@ -1295,7 +1290,7 @@ bool LLGLManager::initGL()
if (mVRAM != 0)
{
- LL_WARNS("RenderInit") << "VRAM Detected (NVXGpuMemoryInfo):" << mVRAM << LL_ENDL;
+ LL_INFOS("RenderInit") << "VRAM Detected (NVXGpuMemoryInfo):" << mVRAM << LL_ENDL;
}
}
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 868284031d..fafc92b5d0 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -540,6 +540,7 @@ void LLImageGL::init(bool usemipmaps, bool allow_compression)
mIsMask = false;
mNeedsAlphaAndPickMask = true ;
+ mAlphaAnalysisSerial = 0;
mAlphaStride = 0 ;
mAlphaOffset = 0 ;
@@ -1945,6 +1946,9 @@ void LLImageGL::destroyGLTexture()
mTexName = 0;
mGLTextureCreated = false ;
}
+
+ // Invalidate pending jobs
+ ++mAlphaAnalysisSerial;
}
//force to invalidate the gl texture, most likely a sculpty texture
@@ -2104,6 +2108,9 @@ void LLImageGL::setNeedsAlphaAndPickMask(bool need_mask)
{
mAlphaOffset = INVALID_OFFSET ;
mIsMask = false;
+
+ // Invalidate pending jobs
+ ++mAlphaAnalysisSerial;
}
}
}
@@ -2181,11 +2188,16 @@ void LLImageGL::calcAlphaChannelOffsetAndStride()
}
}
-void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
+bool LLImageGL::analyzeAlphaData(
+ const void* data_in,
+ U32 w,
+ U32 h,
+ S8 alpha_offset,
+ S8 alpha_stride)
{
- if(!data_in || sSkipAnalyzeAlpha || !mNeedsAlphaAndPickMask)
+ if (!data_in)
{
- return ;
+ return false;
}
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
@@ -2194,7 +2206,7 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
U32 alphatotal = 0;
U32 sample[16];
- memset(sample, 0, sizeof(U32)*16);
+ memset(sample, 0, sizeof(U32) * 16);
// generate histogram of quantized alpha.
// also add-in the histogram of a 2x2 box-sampled version. The idea is
@@ -2205,46 +2217,46 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
{
llassert(w % 2 == 0);
llassert(h % 2 == 0);
- const GLubyte* rowstart = ((const GLubyte*) data_in) + mAlphaOffset;
+ const GLubyte* rowstart = ((const GLubyte*)data_in) + alpha_offset;
for (U32 y = 0; y < h; y += 2)
{
- const GLubyte* current = rowstart;
+ const unsigned char* current = rowstart;
for (U32 x = 0; x < w; x += 2)
{
const U32 s1 = current[0];
alphatotal += s1;
- const U32 s2 = current[w * mAlphaStride];
+ const U32 s2 = current[w * alpha_stride];
alphatotal += s2;
- current += mAlphaStride;
+ current += alpha_stride;
const U32 s3 = current[0];
alphatotal += s3;
- const U32 s4 = current[w * mAlphaStride];
+ const U32 s4 = current[w * alpha_stride];
alphatotal += s4;
- current += mAlphaStride;
+ current += alpha_stride;
- ++sample[s1/16];
- ++sample[s2/16];
- ++sample[s3/16];
- ++sample[s4/16];
+ ++sample[s1 / 16];
+ ++sample[s2 / 16];
+ ++sample[s3 / 16];
+ ++sample[s4 / 16];
- const U32 asum = (s1+s2+s3+s4);
+ const U32 asum = (s1 + s2 + s3 + s4);
alphatotal += asum;
- sample[asum/(16*4)] += 4;
+ sample[asum / (16 * 4)] += 4;
}
- rowstart += 2 * w * mAlphaStride;
+ rowstart += 2 * w * alpha_stride;
}
length *= 2; // we sampled everything twice, essentially
}
else
{
- const GLubyte* current = ((const GLubyte*) data_in) + mAlphaOffset;
+ const unsigned char* current = ((const unsigned char*)data_in) + alpha_offset;
for (U32 i = 0; i < length; i++)
{
const U32 s1 = *current;
alphatotal += s1;
- ++sample[s1/16];
- current += mAlphaStride;
+ ++sample[s1 / 16];
+ current += alpha_stride;
}
}
@@ -2271,15 +2283,103 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
upperhalftotal += sample[i];
}
- if (midrangetotal > length/48 || // lots of midrange, or
- (lowerhalftotal == length && alphatotal != 0) || // all close to transparent but not all totally transparent, or
- (upperhalftotal == length && alphatotal != 255*length)) // all close to opaque but not all totally opaque
+ if (midrangetotal > length / 48 ||
+ (lowerhalftotal == length && alphatotal != 0) ||
+ (upperhalftotal == length && alphatotal != 255 * length))
{
- mIsMask = false; // not suitable for masking
+ return false; // not suitable for masking
}
else
{
- mIsMask = true;
+ return true; // is a mask
+ }
+}
+
+void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
+{
+ // if mNeedsAlphaAndPickMask is true, then offset and stride are supposed to be valid.
+ if (!data_in || sSkipAnalyzeAlpha || !mNeedsAlphaAndPickMask)
+ return;
+
+ // Already on a worker thread or a small image - analyze immediately
+ if (!on_main_thread() || (w < 64 && h < 64))
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ // Should have been incremented by destroyGLTexture,
+ // but increment either way, for extra safety.
+ ++mAlphaAnalysisSerial;
+
+ mIsMask = analyzeAlphaData(data_in, w, h, mAlphaOffset, mAlphaStride);
+ return;
+ }
+
+ // On main thread - defer to worker thread
+
+ // Capture context
+ const S8 alpha_offset = mAlphaOffset;
+ const S8 alpha_stride = mAlphaStride;
+ const U32 request_serial = ++mAlphaAnalysisSerial;
+
+ // Copy data for worker thread
+ const size_t data_size = size_t(w) * size_t(h) * size_t(alpha_stride);
+ U8* data_copy = new (std::nothrow) U8[data_size];
+
+ if (!data_copy)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ mIsMask = analyzeAlphaData(data_in, w, h, alpha_offset, alpha_stride);
+ return;
+ }
+ memcpy(data_copy, static_cast<const U8*>(data_in), data_size);
+
+ // Viewer can rapidly switch between lods, which would invalidate
+ // previous analysis results.
+ // Use a serial number to filter out obsolete analysis results.
+
+ ref(); // Keep texture alive
+
+ auto mainq = mMainQueue.lock();
+
+ // Get the GL thread queue
+ auto workerq = LLImageGLThread::sEnabledTextures ?
+ LL::WorkQueue::getInstance("LLImageGL") : // Use the image processing queue if available
+ LL::WorkQueue::getInstance("General"); // Fallback to general
+
+ bool posted_job = false;
+ if (mainq && workerq)
+ {
+ posted_job = mainq->postTo(
+ workerq,
+ // Worker thread: analyze alpha
+ [data_copy, w, h, alpha_offset, alpha_stride]() -> bool
+ {
+ LL_PROFILE_ZONE_NAMED("Deffered alpha mask analysis");
+ bool is_mask = LLImageGL::analyzeAlphaData(data_copy, w, h, alpha_offset, alpha_stride);
+ delete[] data_copy;
+ return is_mask;
+ },
+ // Main thread: apply result
+ [this, request_serial](bool is_mask)
+ {
+ // Only apply if no newer analysis has been requested
+ if (mAlphaAnalysisSerial == request_serial)
+ {
+ mIsMask = is_mask;
+ }
+ unref();
+ }
+ );
+
+ // Conservative default until analysis completes
+ mIsMask = false;
+ }
+ if (!posted_job)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ // Queues not available - fall back to synchronous analysis
+ delete[] data_copy;
+ mIsMask = analyzeAlphaData(data_in, w, h, mAlphaOffset, mAlphaStride);
+ unref();
}
}
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 773b647d69..0e186ca833 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -111,6 +111,7 @@ public:
protected:
virtual ~LLImageGL();
+ static bool analyzeAlphaData(const void* data_in, U32 w, U32 h, S8 alpha_offset, S8 alpha_stride);
void analyzeAlpha(const void* data_in, U32 w, U32 h);
void calcAlphaChannelOffsetAndStride();
@@ -242,6 +243,7 @@ private:
bool mIsMask;
bool mNeedsAlphaAndPickMask;
+ LLAtomicU32 mAlphaAnalysisSerial; // for request tracking.
S8 mAlphaStride ;
S8 mAlphaOffset ;
diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp
index 8bbc9a8176..6a39a34c4e 100644
--- a/indra/llrender/llrender.cpp
+++ b/indra/llrender/llrender.cpp
@@ -968,7 +968,7 @@ void LLRender::syncMatrices()
shader->uniformMatrix3fv(LLShaderMgr::NORMAL_MATRIX, 1, GL_FALSE, norm_mat);
}
- if (shader->getUniformLocation(LLShaderMgr::INVERSE_MODELVIEW_MATRIX))
+ if (shader->getUniformLocation(LLShaderMgr::INVERSE_MODELVIEW_MATRIX) >= 0)
{
shader->uniformMatrix4fv(LLShaderMgr::INVERSE_MODELVIEW_MATRIX, 1, GL_FALSE, glm::value_ptr(cached_inv_mdv));
}
@@ -1000,14 +1000,14 @@ void LLRender::syncMatrices()
// GZ: This was previously disabled seemingly due to a bug involving the deferred renderer's regular pushing and popping of mats.
// We're reenabling this and cleaning up the code around that - that would've been the appropriate course initially.
// Anything beyond the standard proj and inv proj mats are special cases. Please setup special uniforms accordingly in the future.
- if (shader->getUniformLocation(LLShaderMgr::INVERSE_PROJECTION_MATRIX))
+ if (shader->getUniformLocation(LLShaderMgr::INVERSE_PROJECTION_MATRIX) >= 0)
{
glm::mat4 inv_proj = glm::inverse(mat);
shader->uniformMatrix4fv(LLShaderMgr::INVERSE_PROJECTION_MATRIX, 1, false, glm::value_ptr(inv_proj));
}
// Used by some full screen effects - such as full screen lights, glow, etc.
- if (shader->getUniformLocation(LLShaderMgr::IDENTITY_MATRIX))
+ if (shader->getUniformLocation(LLShaderMgr::IDENTITY_MATRIX) >= 0)
{
shader->uniformMatrix4fv(LLShaderMgr::IDENTITY_MATRIX, 1, GL_FALSE, glm::value_ptr(glm::identity<glm::mat4>()));
}
diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp
index d8a620cf5b..9f7c244fe9 100644
--- a/indra/llrender/llshadermgr.cpp
+++ b/indra/llrender/llshadermgr.cpp
@@ -1085,7 +1085,14 @@ void LLShaderMgr::persistShaderCacheMetadata()
return;
}
- LL_INFOS("ShaderMgr") << "Persisting shader cache metadata to disk" << LL_ENDL;
+ if (mShaderCacheDir.empty() || !LLFile::isdir(mShaderCacheDir))
+ {
+ LL_WARNS("ShaderMgr") << "Invalid shader cache directory: " << mShaderCacheDir << LL_ENDL;
+ return;
+ }
+
+ size_t total_entries = mShaderBinaryCache.size();
+ LL_INFOS("ShaderMgr") << "Persisting shader " << (S32)total_entries << " cache metadata entries to disk" << LL_ENDL;
LLSD out;
// Settings and shader cache get saved at different time, thus making
@@ -1096,6 +1103,8 @@ void LLShaderMgr::persistShaderCacheMetadata()
out["shaders"] = LLSD::emptyMap();
LLSD &shaders = out["shaders"];
+ size_t removed = 0;
+
static const F32 LRU_TIME = (60.f * 60.f) * 24.f * 7.f; // 14 days
const F32 current_time = (F32)LLTimer::getTotalSeconds();
for (auto it = mShaderBinaryCache.begin(); it != mShaderBinaryCache.end();)
@@ -1104,8 +1113,9 @@ void LLShaderMgr::persistShaderCacheMetadata()
if ((shader_metadata.mLastUsedTime + LRU_TIME) < current_time)
{
std::string shader_path = gDirUtilp->add(mShaderCacheDir, it->first.asString() + ".shaderbin");
- LLFile::remove(shader_path);
+ LLFile::remove(shader_path, ENOENT);
it = mShaderBinaryCache.erase(it);
+ removed++;
}
else
{
@@ -1119,14 +1129,32 @@ void LLShaderMgr::persistShaderCacheMetadata()
}
std::string meta_out_path = gDirUtilp->add(mShaderCacheDir, "shaderdata.llsd");
+ if (shaders.size() == 0)
+ {
+ LL_WARNS("ShaderMgr") << "No shader cache entries to persist, removing cache metadata file" << LL_ENDL;
+ LLFile::remove(meta_out_path);
+ return;
+ }
+
llofstream outstream(meta_out_path, std::ios_base::out | std::ios_base::binary);
if (!outstream.is_open())
{
LL_WARNS("ShaderMgr") << "Failed to open file. Unable to save shader cache to: " << mShaderCacheDir << LL_ENDL;
return;
}
+
LLSDSerialize::toBinary(out, outstream);
+ if (outstream.fail())
+ {
+ LL_WARNS("ShaderMgr") << "Failed to serialize shader cache metadata" << LL_ENDL;
+ outstream.close();
+ LLFile::remove(meta_out_path); // Clean up partial write
+ return;
+ }
outstream.close();
+
+ LL_INFOS("ShaderMgr") << "Persisted " << (S32)shaders.size()
+ << " entries. Removed " << (S32)removed << " entries." << LL_ENDL;
}
bool LLShaderMgr::loadCachedProgramBinary(LLGLSLShader* shader)
@@ -1140,34 +1168,56 @@ bool LLShaderMgr::loadCachedProgramBinary(LLGLSLShader* shader)
{
std::string in_path = gDirUtilp->add(mShaderCacheDir, shader->mShaderHash.asString() + ".shaderbin");
auto& shader_info = binary_iter->second;
- if (shader_info.mBinaryLength > 0)
+
+ try
{
- std::vector<U8> in_data;
- in_data.resize(shader_info.mBinaryLength);
- std::error_code ec;
- LLFile filep = LLFile(in_path, LLFile::in | LLFile::binary, ec);
- if (!ec && (bool)filep)
+ constexpr GLsizei MAX_SHADER_BINARY_SIZE = 1024 * 1024; // 1 MB, normally around 10KB
+ if (shader_info.mBinaryLength > 0 && shader_info.mBinaryLength <= MAX_SHADER_BINARY_SIZE)
{
- size_t result = filep.read(in_data.data(), in_data.size(), ec);
- filep.close();
+ std::vector<U8> in_data;
+ in_data.resize(shader_info.mBinaryLength);
- if (result == in_data.size())
+ std::error_code ec;
+ LLFile filep = LLFile(in_path, LLFile::in | LLFile::binary, ec);
+ if (!ec && (bool)filep)
{
- GLenum error = glGetError(); // Clear current error
- glProgramBinary(shader->mProgramObject, shader_info.mBinaryFormat, in_data.data(), shader_info.mBinaryLength);
+ size_t result = filep.read(in_data.data(), in_data.size(), ec);
+ filep.close();
+
+ if (result == in_data.size())
+ {
+ GLenum error = glGetError(); // Clear current error
+ glProgramBinary(shader->mProgramObject, shader_info.mBinaryFormat, in_data.data(), shader_info.mBinaryLength);
- error = glGetError();
- GLint success = GL_TRUE;
- glGetProgramiv(shader->mProgramObject, GL_LINK_STATUS, &success);
- if (error == GL_NO_ERROR && success == GL_TRUE)
+ error = glGetError();
+ GLint success = GL_TRUE;
+ glGetProgramiv(shader->mProgramObject, GL_LINK_STATUS, &success);
+ if (error == GL_NO_ERROR && success == GL_TRUE)
+ {
+ binary_iter->second.mLastUsedTime = (F32)LLTimer::getTotalSeconds();
+ LL_INFOS() << "Loaded cached binary for shader: " << shader->mName << LL_ENDL;
+ return true;
+ }
+ }
+ else
{
- binary_iter->second.mLastUsedTime = (F32)LLTimer::getTotalSeconds();
- LL_INFOS() << "Loaded cached binary for shader: " << shader->mName << LL_ENDL;
- return true;
+ LL_WARNS("ShaderMgr") << "Incomplete read of shader binary. Expected: "
+ << in_data.size() << ", read: " << result << LL_ENDL;
}
}
}
}
+ catch (const std::bad_alloc&)
+ {
+ LL_WARNS("ShaderMgr") << "Failed to allocate memory for shader binary ("
+ << shader_info.mBinaryLength << " bytes) for: "
+ << shader->mName << LL_ENDL;
+ }
+ catch (const std::exception& err)
+ {
+ LL_WARNS("ShaderMgr") << "Caught exception " << err.what() << " while loading shader binary for: " << shader->mName << LL_ENDL;
+ }
+
//an error occured, normally we would print log but in this case it means the shader needs recompiling.
LL_INFOS() << "Failed to load cached binary for shader: " << shader->mName << " falling back to compilation" << LL_ENDL;
LLFile::remove(in_path);
diff --git a/indra/llrender/lluiimage.cpp b/indra/llrender/lluiimage.cpp
index dc18bf16bf..d0b6bcbf66 100644
--- a/indra/llrender/lluiimage.cpp
+++ b/indra/llrender/lluiimage.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -31,6 +31,12 @@
// Project includes
#include "lluiimage.h"
+#include <algorithm>
+
+// Static member initialization
+std::vector<LLPointer<LLUIImage> > LLUIImage::sImageList;
+size_t LLUIImage::sCleanupIndex = 0;
+bool LLUIImage::sEnableDisplayListsCollection = true;
LLUIImage::LLUIImage(const std::string& name, LLPointer<LLTexture> image)
: mName(name),
@@ -49,6 +55,12 @@ LLUIImage::LLUIImage(const std::string& name, LLPointer<LLTexture> image)
LLUIImage::~LLUIImage()
{
delete mImageLoaded;
+
+ // Unregister from global cleanup list (sanity check)
+ // But it's supposed to be cleared already, else we wouldn't
+ // be destructing this object.
+ unregisterFromGlobalCleanup();
+ mDisplayLists.clear();
}
S32 LLUIImage::getWidth() const
@@ -63,6 +75,199 @@ S32 LLUIImage::getHeight() const
return ll_round((F32)mImage->getHeight(0) * mClipRegion.getHeight());
}
+buffer_data_list_t* LLUIImage::findDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const
+{
+ LLImageGL* gl_image = mImage->getGLTexture();
+ if (!gl_image)
+ {
+ return nullptr;
+ }
+
+ LLVector3 ui_translation = gGL.getUITranslation();
+ LLVector3 ui_scale = gGL.getUIScale();
+ LLGLuint tex_name = gl_image->getTexName();
+
+ auto key = PackedKey::create(x, y, width, height, color, solid_color, ui_translation, ui_scale, tex_name);
+
+ auto it = mDisplayLists.find(key);
+ if (it != mDisplayLists.end())
+ {
+ it->second.last_used = std::chrono::steady_clock::now();
+ return &it->second.list;
+ }
+ return nullptr;
+}
+
+buffer_data_list_t* LLUIImage::genDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const
+{
+ LL_PROFILE_ZONE_SCOPED;
+
+ LLImageGL* gl_image = mImage->getGLTexture();
+ if (!gl_image)
+ {
+ // Don't cache when texture hasn't been created yet
+ // draw just aborts in this case, so don't draw either.
+ return nullptr;
+ }
+
+ LLVector3 ui_translation = gGL.getUITranslation();
+ LLVector3 ui_scale = gGL.getUIScale();
+
+ // Get the GL texture name - this uniquely identifies the current texture state
+ // including discard level changes, texture recreation, etc.
+ LLGLuint tex_name = gl_image->getTexName();
+
+ auto key = PackedKey::create(x, y, width, height, color, solid_color, ui_translation, ui_scale, tex_name);
+
+ CachedDisplayList cached;
+ cached.last_used = std::chrono::steady_clock::now();
+
+ // Generate the display list by capturing the draw commands
+ gGL.beginList(&cached.list);
+
+ gl_draw_scaled_image_with_border(
+ x, y,
+ width, height,
+ mImage,
+ color,
+ solid_color,
+ mClipRegion,
+ mScaleRegion,
+ mScaleStyle == SCALE_INNER);
+
+ gGL.endList();
+
+ // Insert into cache
+ // emplace, since we only call genDisplayList if key was not found.
+ auto result = mDisplayLists.emplace(key, std::move(cached));
+
+ // Register for cleanup on first buffer creation
+ if (mDisplayLists.size() == 1)
+ {
+ sImageList.push_back(const_cast<LLUIImage*>(this));
+ }
+
+ return &result.first->second.list;
+}
+
+void LLUIImage::invalidateDisplayLists()
+{
+ mDisplayLists.clear();
+
+ unregisterFromGlobalCleanup();
+}
+
+void LLUIImage::cleanupDisplayLists()
+{
+ if (mDisplayLists.empty())
+ {
+ llassert(false); //it shouldn't be in this list
+ unregisterFromGlobalCleanup();
+ // marks current position for a recheck. Increments after cleanupDisplayLists.
+ if (sCleanupIndex > 0)
+ {
+ sCleanupIndex--;
+ }
+ else if (sImageList.empty())
+ {
+ sCleanupIndex = 0;
+ }
+ else
+ {
+ sCleanupIndex = sImageList.size() - 1;
+ }
+ return;
+ }
+
+ // Time threshold for cleaning up unused display lists (global cleanup)
+ constexpr std::chrono::seconds DISPLAY_LIST_TIMEOUT{ 2 };
+ auto now = std::chrono::steady_clock::now();
+
+ // Remove display lists that haven't been used recently
+ for (auto it = mDisplayLists.begin(); it != mDisplayLists.end(); )
+ {
+ if (now - it->second.last_used > DISPLAY_LIST_TIMEOUT)
+ {
+ it = mDisplayLists.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ // Unregister from cleanup list if all display lists were removed
+ if (mDisplayLists.empty())
+ {
+ unregisterFromGlobalCleanup();
+ // marks current position for a recheck. Increments after cleanupDisplayLists.
+ if (sCleanupIndex > 0)
+ {
+ sCleanupIndex--;
+ }
+ else if (sImageList.empty())
+ {
+ sCleanupIndex = 0;
+ }
+ else
+ {
+ sCleanupIndex = sImageList.size() - 1;
+ }
+ }
+}
+
+void LLUIImage::unregisterFromGlobalCleanup()
+{
+ auto list_it = std::find(sImageList.begin(), sImageList.end(), this);
+ if (list_it != sImageList.end())
+ {
+ // Swap with last element and pop (O(1) removal)
+ *list_it = sImageList.back();
+ sImageList.pop_back();
+ }
+}
+
+// static
+void LLUIImage::updateClass()
+{
+ if (sImageList.empty())
+ {
+ return;
+ }
+
+ // Clean up a batch of images each frame to amortize the cost
+ // ensuring all images are checked regularly
+ // Note: buffers often get obsolete in batches, perhaps
+ // increase rate of cleanup after a buffer was removed?
+ // and decrease rate if no buffer were removed and creates
+ // for a while?
+ constexpr size_t BATCH_SIZE = 8;
+ size_t images_to_process = std::min(BATCH_SIZE, sImageList.size());
+
+ for (size_t i = 0; i < images_to_process; ++i)
+ {
+ if (sCleanupIndex >= sImageList.size())
+ {
+ sCleanupIndex = 0;
+ }
+
+ sImageList[sCleanupIndex]->cleanupDisplayLists();
+ ++sCleanupIndex;
+ }
+}
+
+void LLUIImage::cleanupClass()
+{
+ std::vector<LLPointer<LLUIImage> > list_copy(sImageList);
+ sImageList.clear();
+ for (LLUIImage* image : list_copy)
+ {
+ // invalidateDisplayLists will attempt to clear sImageList
+ image->invalidateDisplayLists();
+ }
+ sCleanupIndex = 0;
+}
+
void LLUIImage::draw3D(const LLVector3& origin_agent, const LLVector3& x_axis, const LLVector3& y_axis,
const LLRect& rect, const LLColor4& color)
{
@@ -77,7 +282,7 @@ void LLUIImage::draw3D(const LLVector3& origin_agent, const LLVector3& x_axis, c
}
else
{
- border_scale = (F32)rect.getWidth() / border_width;
+ border_scale = (F32)rect.getWidth() / border_width;
}
}
@@ -124,6 +329,8 @@ void LLUIImage::onImageLoaded()
{
(*mImageLoaded)();
}
+
+ invalidateDisplayLists();
}
namespace LLInitParam
diff --git a/indra/llrender/lluiimage.h b/indra/llrender/lluiimage.h
index 7dde84f295..0998ac17b4 100644
--- a/indra/llrender/lluiimage.h
+++ b/indra/llrender/lluiimage.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -34,10 +34,15 @@
#include "llinitparam.h"
#include "lltexture.h"
#include "llrender2dutils.h"
+#include "llvertexbuffer.h"
#include <boost/signals2.hpp>
+#include <bit>
+#include <chrono>
#include <type_traits>
+#include <unordered_map>
+#include <vector>
extern const LLColor4 UI_VERTEX_COLOR;
@@ -58,21 +63,28 @@ public:
LL_FORCE_INLINE void setClipRegion(const LLRectf& region)
{
mClipRegion = region;
+ // This happens when image becomes loaded
+ invalidateDisplayLists();
}
LL_FORCE_INLINE void setScaleRegion(const LLRectf& region)
{
mScaleRegion = region;
+ // This happens when image becomes loaded
+ invalidateDisplayLists();
}
LL_FORCE_INLINE void setScaleStyle(EScaleStyle style)
{
mScaleStyle = style;
+ // This happens when image becomes loaded
+ invalidateDisplayLists();
}
LL_FORCE_INLINE LLPointer<LLTexture> getImage() { return mImage; }
LL_FORCE_INLINE const LLPointer<LLTexture>& getImage() const { return mImage; }
+ LL_FORCE_INLINE void draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const;
LL_FORCE_INLINE void draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color = UI_VERTEX_COLOR) const;
LL_FORCE_INLINE void draw(S32 x, S32 y, const LLColor4& color = UI_VERTEX_COLOR) const;
LL_FORCE_INLINE void draw(const LLRect& rect, const LLColor4& color = UI_VERTEX_COLOR) const { draw(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color); }
@@ -85,6 +97,10 @@ public:
LL_FORCE_INLINE void drawBorder(const LLRect& rect, const LLColor4& color, S32 border_width) const { drawBorder(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color, border_width); }
LL_FORCE_INLINE void drawBorder(S32 x, S32 y, const LLColor4& color, S32 border_width) const { drawBorder(x, y, getWidth(), getHeight(), color, border_width); }
+ // Note: draw3D is not cached with display lists because it uses world-space rendering
+ // with dynamic transforms (gl_segmented_rect_3d_tex). These calls are infrequent and
+ // highly dynamic, making caching ineffective. The 2D UI methods benefit from caching
+ // because they're called many times per frame with the same dimensions.
void draw3D(const LLVector3& origin_agent, const LLVector3& x_axis, const LLVector3& y_axis, const LLRect& rect, const LLColor4& color);
LL_FORCE_INLINE const std::string& getName() const { return mName; }
@@ -100,7 +116,107 @@ public:
void onImageLoaded();
+ // Global cleanup of unused display lists across all LLUIImage instances
+ // Should be called periodically (e.g., once per frame or when memory pressure is detected)
+ static void updateClass();
+ static void cleanupClass();
+
+ static void enableDisplayListsCollection(bool enable) { sEnableDisplayListsCollection = enable; }
+
protected:
+ // Packed key for identifying unique display list configurations
+ struct PackedKey
+ {
+ uint64_t position; // x and y coordinates (32 bits each)
+ uint64_t color_flags; // RGBA color (8 bits each) + solid_color flag (1 bit)
+ uint64_t dimensions; // width and height (32 bits each)
+ uint64_t translate; // UI translation (32 bits each for X and Y)
+ uint64_t scale; // UI scale (32 bits each for X and Y)
+ uint64_t tex_name; // OpenGL texture name (32 bits) + padding
+
+ constexpr bool operator==(const PackedKey& other) const
+ {
+ return position == other.position &&
+ color_flags == other.color_flags &&
+ dimensions == other.dimensions &&
+ translate == other.translate &&
+ scale == other.scale &&
+ tex_name == other.tex_name;
+ }
+
+ struct Hash
+ {
+ std::size_t operator()(const PackedKey& key) const
+ {
+ return static_cast<std::size_t>(key.position ^ key.color_flags ^
+ key.dimensions ^ key.translate ^
+ key.scale ^ key.tex_name);
+ }
+ };
+
+ // Static factory function to create PackedKey from parameters
+ static constexpr PackedKey create(S32 x, S32 y, S32 width, S32 height,
+ const LLColor4& color, bool solid_color,
+ const LLVector3& translate, const LLVector3& scale,
+ LLGLuint texture_name)
+ {
+ auto float_to_u8 = [](F32 f) -> uint8_t {
+ return static_cast<uint8_t>(llclamp(f * 255.0f, 0.0f, 255.0f));
+ };
+
+ auto float_to_bits = [](F32 f) -> uint32_t {
+ return std::bit_cast<uint32_t>(f);
+ };
+
+ uint8_t r = float_to_u8(color.mV[VRED]);
+ uint8_t g = float_to_u8(color.mV[VGREEN]);
+ uint8_t b = float_to_u8(color.mV[VBLUE]);
+ uint8_t a = float_to_u8(color.mV[VALPHA]);
+
+ uint64_t pos = (static_cast<uint64_t>(static_cast<uint32_t>(x)) << 32) |
+ static_cast<uint64_t>(static_cast<uint32_t>(y));
+
+ uint64_t col = (static_cast<uint64_t>(r) << 56) |
+ (static_cast<uint64_t>(g) << 48) |
+ (static_cast<uint64_t>(b) << 40) |
+ (static_cast<uint64_t>(a) << 32) |
+ (solid_color ? 1ULL : 0ULL);
+
+ uint64_t dim = (static_cast<uint64_t>(static_cast<uint32_t>(width)) << 32) |
+ static_cast<uint64_t>(static_cast<uint32_t>(height));
+
+ uint64_t trns = (static_cast<uint64_t>(float_to_bits(translate.mV[VX])) << 32) |
+ static_cast<uint64_t>(float_to_bits(translate.mV[VY]));
+
+ uint64_t scl = (static_cast<uint64_t>(float_to_bits(scale.mV[VX])) << 32) |
+ static_cast<uint64_t>(float_to_bits(scale.mV[VY]));
+
+ // Store full 32-bit texture name in lower 32 bits (upper 32 bits unused/zero)
+ uint64_t tex = static_cast<uint64_t>(texture_name);
+
+ return PackedKey{ pos, col, dim, trns, scl, tex };
+ }
+ };
+
+ // Cached display list for a specific configuration
+ struct CachedDisplayList
+ {
+ buffer_data_list_t list;
+ std::chrono::steady_clock::time_point last_used;
+ };
+
+ // Get a display list for the given configuration
+ buffer_data_list_t* findDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const;
+ // Generate a new display list for the given configuration, draws immediately.
+ buffer_data_list_t* genDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const;
+
+ // Invalidate all cached display lists (called when image properties change)
+ void invalidateDisplayLists();
+
+ // Clean up old display lists for this image (called by updateClass)
+ void cleanupDisplayLists();
+ void unregisterFromGlobalCleanup();
+
image_loaded_signal_t* mImageLoaded;
std::string mName;
@@ -110,6 +226,14 @@ protected:
EScaleStyle mScaleStyle;
mutable S32 mCachedW;
mutable S32 mCachedH;
+
+ // Display list cache - now using PackedKey directly
+ mutable std::unordered_map<PackedKey, CachedDisplayList, PackedKey::Hash> mDisplayLists;
+
+ // Track all LLUIImage cache instances for global cleanup
+ static std::vector<LLPointer<LLUIImage> > sImageList;
+ static size_t sCleanupIndex; // Round-robin cleanup position
+ static bool sEnableDisplayListsCollection;
};
#include "lluiimage.inl"
diff --git a/indra/llrender/lluiimage.inl b/indra/llrender/lluiimage.inl
index dff1fcdfcc..d40835a0f4 100644
--- a/indra/llrender/lluiimage.inl
+++ b/indra/llrender/lluiimage.inl
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -29,30 +29,72 @@ void LLUIImage::draw(S32 x, S32 y, const LLColor4& color) const
draw(x, y, getWidth(), getHeight(), color);
}
+void LLUIImage::draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const
+{
+ if (sEnableDisplayListsCollection)
+ {
+ // Get display list for this configuration
+ buffer_data_list_t* display_list = findDisplayList(x, y, width, height, color, solid_color);
+
+ if (display_list && !display_list->empty())
+ {
+ // Deliberately empty pending verts.
+ // They aren't related to the image, so don't register them under draw zone
+ gGL.flush();
+ LL_PROFILE_ZONE_SCOPED;
+ gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE);
+
+ //gGL.pushUIMatrix();
+
+ if (solid_color)
+ {
+ gSolidColorProgram.bind();
+ }
+
+ gGL.color4fv(color.mV); // for the shader
+
+ // Replay the cached display list
+ for (LLVertexBufferData& buffer : *display_list)
+ {
+ buffer.draw();
+ }
+
+ if (solid_color)
+ {
+ gUIProgram.bind();
+ }
+ //gGL.popUIMatrix();
+ }
+ else
+ {
+ // Create, draw and capture display list.
+ // Basically a wrapper around gl_draw_scaled_image_with_border
+ // that records the output into a list.
+ genDisplayList(x, y, width, height, color, solid_color);
+ }
+ }
+ else
+ {
+ gl_draw_scaled_image_with_border(
+ x, y,
+ width, height,
+ mImage,
+ color,
+ solid_color,
+ mClipRegion,
+ mScaleRegion,
+ mScaleStyle == SCALE_INNER);
+ }
+}
+
void LLUIImage::draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const
{
- gl_draw_scaled_image_with_border(
- x, y,
- width, height,
- mImage,
- color,
- false,
- mClipRegion,
- mScaleRegion,
- mScaleStyle == SCALE_INNER);
+ draw(x, y, width, height, color, false);
}
void LLUIImage::drawSolid(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const
{
- gl_draw_scaled_image_with_border(
- x, y,
- width, height,
- mImage,
- color,
- true,
- mClipRegion,
- mScaleRegion,
- mScaleStyle == SCALE_INNER);
+ draw(x, y, width, height, color, true);
}
void LLUIImage::drawBorder(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, S32 border_width) const
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index d59ddd0fec..9b839350ae 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -939,12 +939,14 @@ void LLVertexBuffer::initClass(LLWindow* window)
{
llassert(sVBOPool == nullptr);
+#if LL_DARWIN || LL_ARM64
if (gGLManager.mIsApple)
{
LL_INFOS() << "VBO Pooling Disabled" << LL_ENDL;
sVBOPool = new LLAppleVBOPool();
}
else
+#endif
{
LL_INFOS() << "VBO Pooling Enabled" << LL_ENDL;
sVBOPool = new LLDefaultVBOPool();
@@ -1282,7 +1284,12 @@ U8* LLVertexBuffer::mapVertexBuffer(LLVertexBuffer::AttributeType type, U32 inde
count = mNumVerts - index;
}
+#if LL_DARWIN || LL_ARM64
+ // Region tracking not needed on apple silicon - it recreates entire buffer
+ // While mIsApple can be encountered under windows, this is a
+ // macOS OpenGL behavior workaround. LL_ARM64 check might be not needed
if (!gGLManager.mIsApple)
+#endif
{
U32 start = mOffsets[type] + sTypeSize[type] * index;
U32 end = start + sTypeSize[type] * count-1;
@@ -1319,7 +1326,9 @@ U8* LLVertexBuffer::mapIndexBuffer(U32 index, S32 count)
count = mNumIndices-index;
}
+#if LL_DARWIN || LL_ARM64
if (!gGLManager.mIsApple)
+#endif
{
U32 start = sizeof(U16) * index;
U32 end = start + sizeof(U16) * count-1;
@@ -1354,6 +1363,7 @@ U8* LLVertexBuffer::mapIndexBuffer(U32 index, S32 count)
// dst -- mMappedData or mMappedIndexData
void LLVertexBuffer::flush_vbo(GLenum target, U32 start, U32 end, void* data, U8* dst)
{
+#if LL_DARWIN || LL_ARM64
if (gGLManager.mIsApple)
{
// on OS X, flush_vbo doesn't actually write to the GL buffer, so be sure to call
@@ -1365,6 +1375,7 @@ void LLVertexBuffer::flush_vbo(GLenum target, U32 start, U32 end, void* data, U8
memcpy(dst+start, data, end-start+1);
}
else
+#endif
{
llassert(target == GL_ARRAY_BUFFER ? sGLRenderBuffer == mGLBuffer : sGLRenderIndices == mGLIndices);
@@ -1420,6 +1431,7 @@ void LLVertexBuffer::_unmapBuffer()
}
};
+#if LL_DARWIN || LL_ARM64
if (gGLManager.mIsApple)
{
STOP_GLERROR;
@@ -1462,6 +1474,7 @@ void LLVertexBuffer::_unmapBuffer()
STOP_GLERROR;
}
else
+#endif // LL_DARWIN || LL_ARM64
{
if (!mMappedVertexRegions.empty())
{
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index a71b1e747f..82cbc4bb5e 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -41,6 +41,7 @@ set(llui_SOURCE_FILES
llfloaterreglistener.cpp
llflyoutbutton.cpp
llfocusmgr.cpp
+ llgestureautocompletehelper.cpp
llfolderview.cpp
llfolderviewitem.cpp
llfolderviewmodel.cpp
@@ -154,6 +155,7 @@ set(llui_HEADER_FILES
llfloaterreglistener.h
llflyoutbutton.h
llfocusmgr.h
+ llgestureautocompletehelper.h
llfolderview.h
llfolderviewitem.h
llfolderviewmodel.h
diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp
index 01812fc7ab..7535295312 100644
--- a/indra/llui/llcombobox.cpp
+++ b/indra/llui/llcombobox.cpp
@@ -536,7 +536,7 @@ bool LLComboBox::setCurrentByIndex(S32 index)
if (item->getEnabled())
{
mList->selectItem(item, -1, true);
- LLSD::String label = item->getColumn(0)->getValue().asString();
+ LLSD::String label = getSelectedItemLabel();
if (mTextEntry)
{
mTextEntry->setText(label);
diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp
index 34eb1ea3fc..d863cc5143 100644
--- a/indra/llui/llflatlistview.cpp
+++ b/indra/llui/llflatlistview.cpp
@@ -31,6 +31,14 @@
#include "llflatlistview.h"
+#include "llfocusmgr.h"
+#include "llrender2dutils.h"
+#include "llui.h"
+#include "lluicolortable.h"
+#include "llwindow.h"
+
+#include <algorithm>
+
static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("flat_list_view");
const LLSD SELECTED_EVENT = LLSD().with("selected", true);
@@ -45,17 +53,28 @@ LLFlatListView::Params::Params()
multi_select("multi_select"),
keep_one_selected("keep_one_selected"),
keep_selection_visible_on_reshape("keep_selection_visible_on_reshape",false),
+ allow_reorder("allow_reorder", false),
+ drag_indicator_color("drag_indicator_color"),
no_items_text("no_items_text")
{};
void LLFlatListView::reshape(S32 width, S32 height, bool called_from_parent /* = true */)
{
S32 delta = height - getRect().getHeight();
+ LLRect visible_rc;
+ LLRect selected_rc = getLastSelectedItemRect();
+ bool keep_selection_visible = false;
+ if (delta != 0 && mKeepSelectionVisibleOnReshape && selected_rc.isValid())
+ {
+ visible_rc = getVisibleContentRect();
+ keep_selection_visible = visible_rc.overlaps(selected_rc);
+ }
+
LLScrollContainer::reshape(width, height, called_from_parent);
setItemsNoScrollWidth(width);
rearrangeItems();
- if(delta!= 0 && mKeepSelectionVisibleOnReshape)
+ if(keep_selection_visible)
{
ensureSelectedVisible();
}
@@ -91,7 +110,7 @@ bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*
}
//_4 is for MASK
- item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
+ item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT));
item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
// Children don't accept the focus
@@ -142,7 +161,7 @@ bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= tr
mItemsPanel->addChild(panel);
//_4 is for MASK
- panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
+ panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT));
panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
// Children don't accept the focus
panel->setTabStop(false);
@@ -165,7 +184,7 @@ bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= tr
mItemsPanel->addChild(panel);
//_4 is for MASK
- panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4));
+ panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4, CLICK_LEFT));
panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, item_pair, _4));
// Children don't accept the focus
panel->setTabStop(false);
@@ -217,7 +236,7 @@ bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add,
}
//_4 is for MASK
- item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
+ item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT));
item_to_add->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
rearrangeItems();
@@ -396,6 +415,11 @@ U32 LLFlatListView::size(const bool only_visible_items) const
void LLFlatListView::clear()
{
+ if (mReorderDragPair)
+ {
+ cancelReorderDrag();
+ }
+
// This will clear mSelectedItemPairs, calling all appropriate callbacks.
resetSelection();
@@ -460,6 +484,16 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)
, mIsConsecutiveSelection(false)
, mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape)
, mFocusOnItemClicked(true)
+ , mAllowReorder(p.allow_reorder)
+ , mIsReordering(false)
+ , mReorderDragPair(nullptr)
+ , mDeferredSelectPair(nullptr)
+ , mReorderMouseDownX(0)
+ , mReorderMouseDownY(0)
+ , mReorderInsertIndex(-1)
+ , mDragIndicatorColor(p.drag_indicator_color.isProvided()
+ ? p.drag_indicator_color()
+ : LLUIColorTable::instance().getColor("EmphasisColor"))
{
mBorderThickness = getBorderWidth();
@@ -520,12 +554,29 @@ LLFlatListView::~LLFlatListView()
// virtual
void LLFlatListView::draw()
{
+ // Keep scrolling while a reorder drag rests over the top/bottom edge, so the
+ // user does not have to drop, scroll, and grab again.
+ if (mIsReordering)
+ {
+ S32 local_x, local_y;
+ LLUI::getInstance()->getMousePositionLocal(this, &local_x, &local_y);
+ if (autoScroll(local_x, local_y))
+ {
+ mReorderInsertIndex = constrainInsertIndex(getInsertIndexAt(local_x, local_y));
+ }
+ }
+
// Highlight border if a child of this container has keyboard focus
if( mSelectedItemsBorder->getVisible() )
{
mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() );
}
LLScrollContainer::draw();
+
+ if (mIsReordering && mReorderInsertIndex >= 0)
+ {
+ drawReorderIndicator();
+ }
}
// virtual
@@ -602,7 +653,7 @@ void LLFlatListView::rearrangeItems()
mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
}
-void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
+void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask, EMouseClickType clicktype)
{
if (!item_pair) return;
@@ -617,6 +668,11 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
setFocus(true);
}
+ if (clicktype == CLICK_LEFT && !(mask & (MASK_SHIFT | MASK_CONTROL)))
+ {
+ armReorderDrag(item_pair);
+ }
+
bool select_item = !isSelected(item_pair);
//*TODO find a better place for that enforcing stuff
@@ -695,6 +751,16 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
return;
}
+ // When a reorderable list has a multi-selection, a plain click on one of the
+ // selected rows must not collapse the selection yet: the user may be about to
+ // drag the whole selection. Defer collapsing to mouse-up if no drag happens.
+ if (mAllowReorder && mMultipleSelection && !(mask & (MASK_CONTROL | MASK_SHIFT))
+ && isSelected(item_pair) && numSelected() > 1)
+ {
+ mDeferredSelectPair = item_pair;
+ return;
+ }
+
//no need to do additional commit on selection reset
if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(true);
@@ -715,8 +781,387 @@ void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask)
if ( !(mask & MASK_CONTROL) && mMultipleSelection && isSelected(item_pair) )
return;
- // else got same behavior as at onItemMouseClick
- onItemMouseClick(item_pair, mask);
+ // else got same behavior as at onItemMouseClick, but a right click must never start a drag
+ onItemMouseClick(item_pair, mask, CLICK_RIGHT);
+}
+
+static constexpr S32 REORDER_DRAG_THRESHOLD = 5;
+
+void LLFlatListView::armReorderDrag(item_pair_t* item_pair)
+{
+ if (!mAllowReorder)
+ {
+ return;
+ }
+
+ if (!item_pair || !item_pair->first)
+ {
+ return;
+ }
+
+ if (size() < 2)
+ {
+ return; // nothing to reorder against
+ }
+
+ if (gFocusMgr.getMouseCapture() && !hasMouseCapture())
+ {
+ return;
+ }
+
+ mReorderDragPair = item_pair;
+ mReorderDragGroup.clear();
+ mDeferredSelectPair = nullptr;
+ mIsReordering = false;
+ mReorderInsertIndex = -1;
+
+ LLUI::getInstance()->getMousePositionLocal(this, &mReorderMouseDownX, &mReorderMouseDownY);
+ gFocusMgr.setMouseCapture(this);
+}
+
+void LLFlatListView::updateReorderDrag(S32 x, S32 y)
+{
+ if (!mReorderDragPair)
+ return;
+
+ if (!mIsReordering)
+ {
+ if (abs(y - mReorderMouseDownY) < REORDER_DRAG_THRESHOLD
+ && abs(x - mReorderMouseDownX) < REORDER_DRAG_THRESHOLD)
+ {
+ return; // still within the click slop, not a drag yet
+ }
+ mIsReordering = true;
+ buildReorderGroup();
+ }
+
+ mReorderInsertIndex = constrainInsertIndex(getInsertIndexAt(x, y));
+}
+
+void LLFlatListView::buildReorderGroup()
+{
+ mReorderDragGroup.clear();
+
+ bool drag_selection = mMultipleSelection && isSelected(mReorderDragPair) && numSelected() > 1;
+
+ for (item_pair_t* pair : mItemPairs)
+ {
+ if (!pair->first->getVisible())
+ continue;
+
+ if (pair == mReorderDragPair)
+ {
+ mReorderDragGroup.push_back(pair);
+ }
+ else if (drag_selection && isSelected(pair)
+ && (!mReorderValidateCallback || mReorderValidateCallback(mReorderDragPair->second, pair->second)))
+ {
+ // only carry along selected rows that belong to the grabbed row's group
+ mReorderDragGroup.push_back(pair);
+ }
+ }
+}
+
+bool LLFlatListView::isInReorderGroup(item_pair_t* item_pair) const
+{
+ return std::find(mReorderDragGroup.begin(), mReorderDragGroup.end(), item_pair) != mReorderDragGroup.end();
+}
+
+void LLFlatListView::getReorderRemaining(pairs_list_t& remaining) const
+{
+ remaining.clear();
+ for (item_pair_t* pair : mItemPairs)
+ {
+ if (!pair->first->getVisible() || isInReorderGroup(pair))
+ continue;
+ remaining.push_back(pair);
+ }
+}
+
+void LLFlatListView::finishReorderDrag()
+{
+ if (mReorderDragPair && mIsReordering && mReorderInsertIndex >= 0)
+ {
+ pairs_list_t remaining;
+ getReorderRemaining(remaining);
+
+ // Resolve the drop boundary to the remaining row it lands before (nullptr = append).
+ item_pair_t* anchor = nullptr;
+ S32 cur = 0;
+ for (item_pair_t* pair : remaining)
+ {
+ if (cur == mReorderInsertIndex)
+ {
+ anchor = pair;
+ break;
+ }
+ ++cur;
+ }
+
+ LLSD moved_value = mReorderDragPair->second;
+
+ for (item_pair_t* pair : mReorderDragGroup)
+ {
+ mItemPairs.remove(pair);
+ }
+
+ pairs_iterator_t it = (anchor != nullptr)
+ ? std::find(mItemPairs.begin(), mItemPairs.end(), anchor)
+ : mItemPairs.end();
+ for (item_pair_t* pair : mReorderDragGroup)
+ {
+ mItemPairs.insert(it, pair); // preserves group order; it stays before anchor
+ }
+
+ rearrangeItems();
+
+ if (mReorderCallback)
+ {
+ S32 visible_index = 0;
+ for (item_pair_t* pair : mItemPairs)
+ {
+ if (!pair->first->getVisible())
+ continue;
+ if (pair == mReorderDragPair)
+ break;
+ ++visible_index;
+ }
+
+ mReorderCallback(moved_value, visible_index);
+ }
+ }
+
+ cancelReorderDrag();
+}
+
+void LLFlatListView::cancelReorderDrag()
+{
+ if (hasMouseCapture())
+ {
+ gFocusMgr.setMouseCapture(nullptr);
+ }
+
+ clearReorderDragState();
+}
+
+void LLFlatListView::clearReorderDragState()
+{
+ if (mIsReordering)
+ {
+ getWindow()->setCursor(UI_CURSOR_ARROW);
+ }
+
+ bool was_armed = (mReorderDragPair != nullptr);
+
+ mReorderDragPair = nullptr;
+ mReorderDragGroup.clear();
+ mDeferredSelectPair = nullptr;
+ mIsReordering = false;
+ mReorderInsertIndex = -1;
+
+ if (was_armed && mReorderEndedCallback)
+ {
+ mReorderEndedCallback();
+ }
+}
+
+void LLFlatListView::onMouseCaptureLost()
+{
+ clearReorderDragState();
+}
+
+void LLFlatListView::onVisibilityChange(bool new_visibility)
+{
+ if (!new_visibility && mReorderDragPair)
+ {
+ cancelReorderDrag();
+ }
+
+ LLScrollContainer::onVisibilityChange(new_visibility);
+}
+
+S32 LLFlatListView::getInsertIndexAt(S32 x, S32 y) const
+{
+ S32 panel_x, panel_y;
+ localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel);
+
+ // The drop boundary among the remaining rows is the count of remaining rows
+ // whose vertical centre sits above the cursor.
+ S32 index = 0;
+ for (item_pair_t* pair : mItemPairs)
+ {
+ if (!pair->first->getVisible() || isInReorderGroup(pair))
+ continue;
+
+ if (pair->first->getRect().getCenterY() > panel_y)
+ {
+ ++index;
+ }
+ }
+ return index;
+}
+
+LLFlatListView::item_pair_t* LLFlatListView::getReorderPairAt(S32 x, S32 y) const
+{
+ S32 panel_x, panel_y;
+ localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel);
+
+ for (item_pair_t* pair : mItemPairs)
+ {
+ if (!pair->first->getVisible())
+ continue;
+
+ // Claim the padding above each row so gap presses still resolve to a row.
+ LLRect rc = pair->first->getRect();
+ rc.mTop += mItemPad;
+ if (rc.pointInRect(panel_x, panel_y))
+ {
+ return pair;
+ }
+ }
+ return nullptr;
+}
+
+S32 LLFlatListView::constrainInsertIndex(S32 dest_index) const
+{
+ if (!mReorderValidateCallback)
+ return dest_index;
+
+ // Clamp the boundary to the contiguous run of remaining rows that share the
+ // grabbed row's group, so a drag can't leave its group.
+ pairs_list_t remaining;
+ getReorderRemaining(remaining);
+
+ const LLSD& dragged = mReorderDragPair->second;
+ S32 first_valid = -1;
+ S32 last_valid = -1;
+ S32 i = 0;
+ for (item_pair_t* pair : remaining)
+ {
+ if (mReorderValidateCallback(dragged, pair->second))
+ {
+ if (first_valid < 0)
+ first_valid = i;
+ last_valid = i;
+ }
+ ++i;
+ }
+
+ if (first_valid < 0)
+ return -1; // no valid neighbour (whole group is being dragged)
+
+ if (dest_index < first_valid)
+ return first_valid;
+ if (dest_index > last_valid + 1)
+ return last_valid + 1;
+ return dest_index;
+}
+
+void LLFlatListView::drawReorderIndicator()
+{
+ pairs_list_t remaining;
+ getReorderRemaining(remaining);
+ if (remaining.empty())
+ return;
+
+ const LLRect& panel_rc = mItemsPanel->getRect();
+ const LLColor4& color = mDragIndicatorColor.get();
+
+ // faint highlight on each row being moved
+ for (item_pair_t* pair : mReorderDragGroup)
+ {
+ const LLRect& dr = pair->first->getRect();
+ gl_rect_2d(panel_rc.mLeft + dr.mLeft, panel_rc.mBottom + dr.mTop,
+ panel_rc.mLeft + dr.mRight, panel_rc.mBottom + dr.mBottom,
+ color % 0.15f, true);
+ }
+
+ // insertion line at the drop boundary among the remaining rows
+ S32 count = (S32)remaining.size();
+ bool at_end = mReorderInsertIndex >= count;
+ S32 anchor_idx = at_end ? count - 1 : mReorderInsertIndex;
+
+ item_pair_t* anchor = nullptr;
+ S32 cur = 0;
+ for (item_pair_t* pair : remaining)
+ {
+ if (cur == anchor_idx)
+ {
+ anchor = pair;
+ break;
+ }
+ ++cur;
+ }
+ if (!anchor)
+ return;
+
+ const LLRect& item_rc = anchor->first->getRect();
+ S32 left = panel_rc.mLeft + item_rc.mLeft;
+ S32 right = panel_rc.mLeft + item_rc.mRight;
+ S32 line_y = panel_rc.mBottom + (at_end ? item_rc.mBottom : item_rc.mTop);
+
+ if (line_y < 0 || line_y > getRect().getHeight())
+ return;
+
+ gl_rect_2d(left, line_y, right, line_y - 1, color, true);
+}
+
+bool LLFlatListView::handleHover(S32 x, S32 y, MASK mask)
+{
+ if (mAllowReorder && mReorderDragPair && hasMouseCapture())
+ {
+ updateReorderDrag(x, y);
+ if (mIsReordering)
+ {
+ getWindow()->setCursor(UI_CURSOR_ARROWDRAG);
+ return true;
+ }
+ }
+ return LLScrollContainer::handleHover(x, y, mask);
+}
+
+bool LLFlatListView::handleMouseDown(S32 x, S32 y, MASK mask)
+{
+ bool handled = LLScrollContainer::handleMouseDown(x, y, mask);
+
+ // A press in the padding between rows misses every item, so the per-item
+ // mouse-down never arms a reorder. Map it to the nearest row and arm there;
+ // armReorderDrag's capture guard ignores presses a child control already took.
+ if (mAllowReorder && !mReorderDragPair && !(mask & (MASK_CONTROL | MASK_SHIFT)))
+ {
+ if (item_pair_t* pair = getReorderPairAt(x, y))
+ {
+ onItemMouseClick(pair, mask, CLICK_LEFT);
+ }
+ }
+
+ return handled || (mReorderDragPair != nullptr);
+}
+
+bool LLFlatListView::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ if (mReorderDragPair && hasMouseCapture())
+ {
+ bool was_reordering = mIsReordering;
+ item_pair_t* deferred = mDeferredSelectPair;
+ mDeferredSelectPair = nullptr;
+
+ finishReorderDrag();
+
+ if (was_reordering)
+ {
+ return true; // a drag happened: keep the (multi-)selection on the moved rows
+ }
+
+ // No drag: apply the selection collapse that was deferred on mouse-down.
+ if (deferred)
+ {
+ resetSelection(true);
+ selectItemPair(deferred, true);
+ return true;
+ }
+ }
+ return LLScrollContainer::handleMouseUp(x, y, mask);
}
bool LLFlatListView::handleKeyHere(KEY key, MASK mask)
@@ -1087,6 +1532,18 @@ bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange)
{
llassert(item_pair);
+ bool removing_reorder_pair = item_pair == mReorderDragPair
+ || std::find(mReorderDragGroup.begin(), mReorderDragGroup.end(), item_pair) != mReorderDragGroup.end();
+
+ if (removing_reorder_pair)
+ {
+ cancelReorderDrag();
+ }
+ if (item_pair == mDeferredSelectPair)
+ {
+ mDeferredSelectPair = nullptr;
+ }
+
bool deleted = false;
bool selection_changed = false;
for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h
index 39afa33be8..ae20a4b1eb 100644
--- a/indra/llui/llflatlistview.h
+++ b/indra/llui/llflatlistview.h
@@ -30,6 +30,7 @@
#include "llpanel.h"
#include "llscrollcontainer.h"
#include "lltextbox.h"
+#include "lluicolor.h"
/**
@@ -106,12 +107,28 @@ public:
/** padding between items */
Optional<U32> item_pad;
+ /** allow items to be reordered by dragging them with the mouse */
+ Optional<bool> allow_reorder;
+
+ /** colour of the insertion indicator drawn while reordering */
+ Optional<LLUIColor> drag_indicator_color;
+
/** textbox with info message when list is empty*/
Optional<LLTextBox::Params> no_items_text;
Params();
};
+ /** Fired after the user drags an item to a new position: (moved value, new visible index). */
+ typedef boost::function<void(const LLSD& value, S32 new_index)> reorder_signal_t;
+
+ /**
+ * Returns true if the dragged item is allowed to move past the given neighbour.
+ * Lets owners constrain reordering (e.g. keep items within a group). Optional;
+ * when unset any reorder is permitted.
+ */
+ typedef boost::function<bool(const LLSD& dragged, const LLSD& neighbour)> reorder_validate_signal_t;
+
// disable traversal when finding widget to hand focus off to
/*virtual*/ bool canFocusChildren() const override { return false; }
@@ -259,6 +276,18 @@ public:
/** Turn on/off selection support */
void setAllowSelection(bool can_select) { mAllowSelection = can_select; }
+ /** Turn on/off drag-to-reorder support */
+ void setAllowReorder(bool allow) { mAllowReorder = allow; }
+
+ void setReorderCallback(reorder_signal_t cb) { mReorderCallback = cb; }
+ void setReorderValidateCallback(reorder_validate_signal_t cb) { mReorderValidateCallback = cb; }
+
+ /** Fired when a reorder grab is released, whether or not it moved anything */
+ void setReorderEndedCallback(boost::function<void()> cb) { mReorderEndedCallback = cb; }
+
+ /** True while an item is grabbed for a drag-to-reorder (before or during the drag) */
+ bool isReorderActive() const { return mReorderDragPair != NULL; }
+
/** Sets flag whether onCommit should be fired if selection was changed */
// FIXME: this should really be a separate signal, since "Commit" implies explicit user action, and selection changes can happen more indirectly.
void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; }
@@ -330,7 +359,7 @@ protected:
LLFlatListView(const LLFlatListView::Params& p);
/** Manage selection on mouse events */
- void onItemMouseClick(item_pair_t* item_pair, MASK mask);
+ void onItemMouseClick(item_pair_t* item_pair, MASK mask, EMouseClickType clicktype);
void onItemRightMouseClick(item_pair_t* item_pair, MASK mask);
@@ -368,6 +397,16 @@ protected:
virtual bool handleKeyHere(KEY key, MASK mask) override;
+ virtual bool handleHover(S32 x, S32 y, MASK mask) override;
+
+ virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override;
+
+ virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override;
+
+ virtual void onMouseCaptureLost() override;
+
+ virtual void onVisibilityChange(bool new_visibility) override;
+
virtual bool postBuild() override;
virtual void onFocusReceived() override;
@@ -384,6 +423,29 @@ protected:
private:
+ // Drag-to-reorder helpers.
+ void armReorderDrag(item_pair_t* item_pair);
+ void updateReorderDrag(S32 x, S32 y);
+ void finishReorderDrag();
+ void cancelReorderDrag();
+ void clearReorderDragState();
+
+ // Builds the set of pairs being dragged (the whole selection when the grabbed
+ // row is part of a multi-selection, constrained to the validator's group).
+ void buildReorderGroup();
+ bool isInReorderGroup(item_pair_t* item_pair) const;
+
+ // The remaining visible pairs (those not being dragged), in visual order.
+ void getReorderRemaining(pairs_list_t& remaining) const;
+ // Number of leading non-dragged items whose centre sits above (x, y).
+ S32 getInsertIndexAt(S32 x, S32 y) const;
+ // Row under (x, y), counting the padding above each row, so presses in the
+ // inter-row gaps still map to a row instead of falling through.
+ item_pair_t* getReorderPairAt(S32 x, S32 y) const;
+ // Clamps an insertion boundary to the validator's contiguous group.
+ S32 constrainInsertIndex(S32 dest_index) const;
+ void drawReorderIndicator();
+
void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;}
void setNoItemsCommentVisible(bool visible) const;
@@ -427,6 +489,20 @@ private:
bool mFocusOnItemClicked;
+ /** Drag-to-reorder state */
+ bool mAllowReorder;
+ bool mIsReordering; // a drag is currently in progress
+ item_pair_t* mReorderDragPair; // the grabbed pair (drag anchor)
+ pairs_list_t mReorderDragGroup; // all pairs being dragged, in visual order
+ item_pair_t* mDeferredSelectPair; // collapse selection to this on mouse-up if no drag
+ S32 mReorderMouseDownX;
+ S32 mReorderMouseDownY;
+ S32 mReorderInsertIndex; // current drop boundary among the remaining (non-dragged) items
+ LLUIColor mDragIndicatorColor;
+ reorder_signal_t mReorderCallback;
+ reorder_validate_signal_t mReorderValidateCallback;
+ boost::function<void()> mReorderEndedCallback;
+
/** All pairs of the list */
pairs_list_t mItemPairs;
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index f1a97ee074..b5e26db1b5 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -570,6 +570,8 @@ void LLFloater::storeRectControl()
void LLFloater::storeVisibilityControl()
{
+ // Todo: this is a bit pricey, gets called each frame
+ // on LLAppViewer::idle(), optimize!
if( !sQuitting && mVisibilityControl.size() > 1 )
{
getControlGroup()->setBOOL( mVisibilityControl, getVisible() );
diff --git a/indra/llui/llflyoutbutton.cpp b/indra/llui/llflyoutbutton.cpp
index 2e198e8dbc..9d511bcf87 100644
--- a/indra/llui/llflyoutbutton.cpp
+++ b/indra/llui/llflyoutbutton.cpp
@@ -31,7 +31,7 @@
//static LLDefaultChildRegistry::Register<LLFlyoutButton> r2("flyout_button");
-const S32 FLYOUT_BUTTON_ARROW_WIDTH = 24;
+constexpr S32 FLYOUT_BUTTON_ARROW_WIDTH = 22;
LLFlyoutButton::LLFlyoutButton(const Params& p)
: LLComboBox(p),
@@ -43,9 +43,13 @@ LLFlyoutButton::LLFlyoutButton(const Params& p)
LLButton::Params bp(p.action_button);
bp.name(p.label);
bp.label(p.label);
- bp.rect.left(0).bottom(0).width(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH).height(getRect().getHeight());
+ bp.rect.left(0).bottom(0).width(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH - 2 * BTN_DROP_SHADOW).height(getRect().getHeight());
bp.click_callback.function(boost::bind(&LLFlyoutButton::onActionButtonClick, this, _2));
bp.follows.flags(FOLLOWS_ALL);
+ if (p.font.isProvided())
+ {
+ bp.font(p.font);
+ }
mActionButton = LLUICtrlFactory::create<LLButton>(bp);
addChild(mActionButton);
@@ -74,4 +78,16 @@ void LLFlyoutButton::setToggleState(bool state)
mToggleState = state;
}
+bool LLFlyoutButton::postBuild()
+{
+ bool result = LLComboBox::postBuild();
+
+ LLRect rect = getLocalRect();
+ S32 arrow_width = llmax(FLYOUT_BUTTON_ARROW_WIDTH, mArrowImage ? mArrowImage->getWidth() : 0);
+ mButton->setRect(LLRect(getRect().getWidth() - llmax(8, arrow_width) - 2 * BTN_DROP_SHADOW,
+ rect.mTop, rect.mRight, rect.mBottom));
+ mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT);
+
+ return result;
+}
diff --git a/indra/llui/llflyoutbutton.h b/indra/llui/llflyoutbutton.h
index 73190fc984..81cdcdc382 100644
--- a/indra/llui/llflyoutbutton.h
+++ b/indra/llui/llflyoutbutton.h
@@ -56,6 +56,8 @@ protected:
public:
void draw() override;
+ bool postBuild() override;
+
void setToggleState(bool state);
void onActionButtonClick(const LLSD& data);
diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp
index db4ab8487e..baab17830b 100644
--- a/indra/llui/llfolderview.cpp
+++ b/indra/llui/llfolderview.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -651,9 +651,11 @@ bool LLFolderView::startDrag()
void LLFolderView::commitRename( const LLSD& data )
{
+ // Intentionally doesn't check mRenamer->getVisible(),
+ // since this can be called from 'focus lost' event.
+ // Ex: clicking inworld should commit the rename.
finishRenamingItem();
arrange( NULL, NULL );
-
}
void LLFolderView::draw()
@@ -727,13 +729,17 @@ void LLFolderView::draw()
void LLFolderView::finishRenamingItem( void )
{
- if(!mRenamer)
+ if (!mRenamer)
{
return;
}
if( mRenameItem )
{
mRenameItem->rename( mRenamer->getText() );
+ // Clear text to avoid duplicate renames.
+ // Ex: 'return' key will trigger rename from handleKeyHere
+ // and might trigger commitRename from focus lost event.
+ mRenamer->setText(LLStringUtil::null);
}
closeRenamer();
@@ -1423,6 +1429,7 @@ bool LLFolderView::search(LLFolderViewItem* first_item, const std::string &searc
}
}
+ // Note: for inventory getSearchableName should already be 'upper' case.
std::string current_item_label(search_item->getViewModelItem()->getSearchableName());
LLStringUtil::toUpper(current_item_label);
auto search_string_length = llmin(upper_case_string.size(), current_item_label.size());
diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp
index fcc1964bd6..c8ef421677 100644
--- a/indra/llui/llfolderviewitem.cpp
+++ b/indra/llui/llfolderviewitem.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
-* Copyright (C) 2010, Linden Research, Inc.
+* Copyright (C) 2026, 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
@@ -190,7 +190,7 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p)
mItemHeight(p.item_height),
mControlLabelRotation(0.f),
mDragAndDropTarget(false),
- mLabel(utf8str_to_wstring(p.name)),
+ mLabel(utf8str_to_wstring(p.name)), // will be immediately reset in postBuild()
mRoot(p.root),
mViewModelItem(p.listener),
mIsMouseOverTitle(false),
@@ -244,21 +244,19 @@ bool LLFolderViewItem::postBuild()
llassert(vmi); // not supposed to happen, if happens, find out why and fix
if (vmi)
{
- // getDisplayName() is expensive (due to internal getLabelSuffix() and name building)
- // it also sets search strings so it requires a filter reset
+ // First getDisplayName() is expensive due to internal
+ // lazy getLabelSuffix(), it is however needed as it sets
+ // search string, which can later determine visibility.
+ // Refreshing a search string also requires a filter reset.
mLabel = utf8str_to_wstring(vmi->getDisplayName());
mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash();
- setToolTip(vmi->getName());
// Dirty the filter flag of the model from the view (CHUI-849)
vmi->dirtyFilter();
}
- // Don't do full refresh on constructor if it is possible to avoid
+ // Don't do full refresh on constructor if it is possible to avoid,
// it significantly slows down bulk view creation.
- // Todo: Ideally we need to move getDisplayName() out of constructor as well.
- // Like: make a logic that will let filter update search string,
- // while LLFolderViewItem::arrange() updates visual part
mSuffixNeedsRefresh = true;
mLabelWidthDirty = true;
return true;
@@ -363,7 +361,6 @@ void LLFolderViewItem::refresh()
mLabel = utf8str_to_wstring(vmi.getDisplayName());
mLabelFontBuffer.reset();
mIsFavorite = vmi.isFavorite() && !vmi.isItemInTrash();
- setToolTip(vmi.getName());
// icons are slightly expensive to get, can be optimized
// see LLInventoryIcon::getIcon()
mIcon = vmi.getIcon();
@@ -623,6 +620,19 @@ const std::string& LLFolderViewItem::getName( void ) const
return getViewModelItem() ? getViewModelItem()->getName() : noName;
}
+const std::string LLFolderViewItem::getToolTip() const
+{
+ // Return the item name as tooltip without storing it
+ if (!LLView::sDebugUnicode)
+ {
+ if (const LLFolderViewModelItem* vmi = getViewModelItem())
+ {
+ return vmi->getName();
+ }
+ }
+ return LLView::getToolTip();
+}
+
// LLView functionality
bool LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h
index 258a806b91..8d6de2fee3 100644
--- a/indra/llui/llfolderviewitem.h
+++ b/indra/llui/llfolderviewitem.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
-* Copyright (C) 2010, Linden Research, Inc.
+* Copyright (C) 2026, 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
@@ -253,6 +253,11 @@ public:
// viewed. This method will ask the viewed object itself.
const std::string& getName( void ) const;
+ // Override to provide lazy tooltip generation without memory overhead
+ // Inventory can consist of millions of items, yet most stay invisible,
+ // much less need to show a tooltip, so avoid storing tooltips.
+ virtual const std::string getToolTip() const;
+
// This method returns the label displayed on the view. This
// method was primarily added to allow sorting on the folder
// contents possible before the entire view has been constructed.
diff --git a/indra/llui/llgestureautocompletehelper.cpp b/indra/llui/llgestureautocompletehelper.cpp
new file mode 100644
index 0000000000..f00f1406e5
--- /dev/null
+++ b/indra/llui/llgestureautocompletehelper.cpp
@@ -0,0 +1,165 @@
+/**
+ * @file llgestureautocompletehelper.cpp
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2026, 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$
+ */
+
+#include "linden_common.h"
+
+#include "llgestureautocompletehelper.h"
+
+#include "llfloater.h"
+#include "llfloaterreg.h"
+#include "llfocusmgr.h"
+#include "lluictrl.h"
+
+constexpr char GESTURE_AUTOCOMPLETE_FLOATER[] = "gesture_autocomplete_picker";
+
+bool LLGestureAutocompleteHelper::isActive(const LLUICtrl* ctrl) const
+{
+ return mHostHandle.get() == ctrl;
+}
+
+void LLGestureAutocompleteHelper::showHelper(
+ LLUICtrl* host_ctrl,
+ const std::vector<Row>& rows,
+ size_t total,
+ std::function<void(std::string)> commit_cb)
+{
+ if (mHelperHandle.isDead())
+ {
+ LLFloater* helper_floater = LLFloaterReg::getInstance(GESTURE_AUTOCOMPLETE_FLOATER);
+ mHelperHandle = helper_floater->getHandle();
+ mHelperCommitConn = helper_floater->setCommitCallback(
+ [this](LLUICtrl*, const LLSD& param) { onCommitGesture(param.asString()); });
+ }
+
+ setHostCtrl(host_ctrl);
+ mRows = rows;
+ mTotal = total;
+ mGestureCommitCb = commit_cb;
+
+ S32 floater_x, floater_y;
+ LLRect host_rect = host_ctrl->getRect();
+ if (!host_ctrl->localPointToOtherView(0, host_rect.getHeight(), &floater_x, &floater_y, gFloaterView))
+ {
+ LL_WARNS() << "Cannot show gesture autocomplete helper for non-floater controls." << LL_ENDL;
+ return;
+ }
+
+ LLFloater* helper_floater = mHelperHandle.get();
+ LLRect rect = helper_floater->getRect();
+ rect.setLeftTopAndSize(floater_x, floater_y + rect.getHeight(), rect.getWidth(), rect.getHeight());
+ helper_floater->setRect(rect);
+
+ refreshPicker();
+}
+
+void LLGestureAutocompleteHelper::hideHelper(const LLUICtrl* ctrl)
+{
+ if (ctrl && !isActive(ctrl))
+ {
+ return;
+ }
+
+ setHostCtrl(nullptr);
+}
+
+bool LLGestureAutocompleteHelper::handleKey(const LLUICtrl* ctrl, KEY key, MASK mask)
+{
+ if (mHelperHandle.isDead() || !isActive(ctrl))
+ {
+ return false;
+ }
+
+ return mHelperHandle.get()->handleKey(key, mask, true);
+}
+
+void LLGestureAutocompleteHelper::onCommitGesture(const std::string& trigger)
+{
+ if (!mHostHandle.isDead() && mGestureCommitCb)
+ {
+ mGestureCommitCb(trigger);
+ }
+
+ hideHelper(getHostCtrl());
+}
+
+void LLGestureAutocompleteHelper::refreshPicker()
+{
+ if (mHelperHandle.isDead())
+ {
+ return;
+ }
+
+ LLFloater* helper_floater = mHelperHandle.get();
+
+ if (helper_floater->isShown())
+ {
+ helper_floater->onOpen(LLSD());
+ }
+ else
+ {
+ helper_floater->openFloater(LLSD());
+ }
+}
+
+void LLGestureAutocompleteHelper::setHostCtrl(LLUICtrl* host_ctrl)
+{
+ const LLUICtrl* cur_host_ctrl = mHostHandle.get();
+
+ if (cur_host_ctrl != host_ctrl)
+ {
+ mHostCtrlFocusLostConn.disconnect();
+ mHostHandle.markDead();
+ mGestureCommitCb = {};
+ mRows.clear();
+ mTotal = 0;
+
+ if (!mHelperHandle.isDead())
+ {
+ mHelperHandle.get()->closeFloater();
+ }
+
+ if (host_ctrl)
+ {
+ mHostHandle = host_ctrl->getHandle();
+ mHostCtrlFocusLostConn = host_ctrl->setFocusLostCallback(
+ [this](auto*)
+ {
+ // Scroll list grabs focus on click.
+ // Keep focus on the host when the click was ours.
+ LLFloater* helper_floater = mHelperHandle.get();
+ if (helper_floater && gFocusMgr.childHasKeyboardFocus(helper_floater))
+ {
+ if (LLUICtrl* host = getHostCtrl())
+ {
+ host->setFocus(true);
+ }
+ return;
+ }
+
+ hideHelper(getHostCtrl());
+ });
+ }
+ }
+}
diff --git a/indra/llui/llgestureautocompletehelper.h b/indra/llui/llgestureautocompletehelper.h
new file mode 100644
index 0000000000..6292655fc7
--- /dev/null
+++ b/indra/llui/llgestureautocompletehelper.h
@@ -0,0 +1,80 @@
+/**
+ * @file llgestureautocompletehelper.h
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2026, 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
+
+#include "llhandle.h"
+#include "llsingleton.h"
+
+#include <boost/signals2.hpp>
+#include <functional>
+#include <string>
+#include <vector>
+
+class LLFloater;
+class LLUICtrl;
+
+class LLGestureAutocompleteHelper : public LLSingleton<LLGestureAutocompleteHelper>
+{
+ LLSINGLETON(LLGestureAutocompleteHelper) {}
+ ~LLGestureAutocompleteHelper() override {}
+
+public:
+ struct Row
+ {
+ std::string value;
+ std::string trigger;
+ std::string name;
+ };
+
+ bool isActive(const LLUICtrl* ctrl) const;
+ void showHelper(
+ LLUICtrl* host_ctrl,
+ const std::vector<Row>& rows,
+ size_t total,
+ std::function<void(std::string)> commit_cb);
+ void hideHelper(const LLUICtrl* ctrl = nullptr);
+ bool handleKey(const LLUICtrl* ctrl, KEY key, MASK mask);
+ void onCommitGesture(const std::string& trigger);
+
+ const std::vector<Row>& rows() const { return mRows; }
+ size_t total() const { return mTotal; }
+
+protected:
+ void setHostCtrl(LLUICtrl* host_ctrl);
+ LLUICtrl* getHostCtrl() const { return mHostHandle.get(); }
+
+private:
+ void refreshPicker();
+
+ LLHandle<LLUICtrl> mHostHandle;
+ LLHandle<LLFloater> mHelperHandle;
+ boost::signals2::connection mHostCtrlFocusLostConn;
+ boost::signals2::connection mHelperCommitConn;
+ std::function<void(std::string)> mGestureCommitCb;
+
+ std::vector<Row> mRows;
+ size_t mTotal = 0;
+};
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 9a88083a5d..404e394ace 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -493,12 +493,13 @@ void LLLineEditor::setCursor( S32 pos )
S32 pixels_after_scroll = findPixelNearestPos();
if( pixels_after_scroll > mTextRightEdge )
{
- S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos);
- S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left)));
+ const LLWString& wtext = mText.getWString();
+ S32 width_chars_to_left = mGLFont->getWidth(wtext.c_str(), 0, mScrollHPos);
+ S32 last_visible_char = mGLFont->maxDrawableChars(wtext.c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left)));
// character immediately to left of cursor should be last one visible (SCROLL_INCREMENT_ADD will scroll in more characters)
// or first character if cursor is at beginning
S32 new_last_visible_char = llmax(0, getCursor() - 1);
- S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char);
+ S32 min_scroll = mGLFont->firstDrawableChar(wtext.c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char);
if (old_cursor_pos == last_visible_char)
{
mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD));
diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp
index e36fd45bb4..ad2f58db69 100644
--- a/indra/llui/llscrollcontainer.cpp
+++ b/indra/llui/llscrollcontainer.cpp
@@ -75,6 +75,7 @@ LLScrollContainer::Params::Params()
max_auto_scroll_rate("max_auto_scroll_rate", 1000),
max_auto_scroll_zone("max_auto_scroll_zone", 16),
reserve_scroll_corner("reserve_scroll_corner", false),
+ keep_scroll_pos("keep_scroll_pos", false),
size("size", -1)
{}
@@ -93,8 +94,12 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p)
mMaxAutoScrollRate(p.max_auto_scroll_rate),
mMaxAutoScrollZone(p.max_auto_scroll_zone),
mScrolledView(NULL),
+ mKeepScrollPos(p.keep_scroll_pos),
mSize(p.size)
{
+ mStoredDocPos[VERTICAL] = 0;
+ mStoredDocPos[HORIZONTAL] = 0;
+
static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0);
S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize);
@@ -605,6 +610,22 @@ void LLScrollContainer::updateScroll()
calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar );
S32 border_width = getBorderWidth();
+
+ // Remember the position only while the scrollbar is genuinely showing scrollable
+ // content, so a transient empty pass (e.g. a list clearing before it repopulates)
+ // cannot overwrite it with 0.
+ if (mKeepScrollPos)
+ {
+ if (show_v_scrollbar && mScrollbar[VERTICAL]->getVisible())
+ {
+ mStoredDocPos[VERTICAL] = mScrollbar[VERTICAL]->getDocPos();
+ }
+ if (show_h_scrollbar && mScrollbar[HORIZONTAL]->getVisible())
+ {
+ mStoredDocPos[HORIZONTAL] = mScrollbar[HORIZONTAL]->getDocPos();
+ }
+ }
+
if( show_v_scrollbar )
{
if( doc_rect.mTop < getRect().getHeight() - border_width )
@@ -673,6 +694,18 @@ void LLScrollContainer::updateScroll()
mScrollbar[VERTICAL]->setDocSize( doc_height );
mScrollbar[VERTICAL]->setPageSize( visible_height );
+
+ if (mKeepScrollPos)
+ {
+ if (show_v_scrollbar)
+ {
+ mScrollbar[VERTICAL]->setDocPos(mStoredDocPos[VERTICAL]);
+ }
+ if (show_h_scrollbar)
+ {
+ mScrollbar[HORIZONTAL]->setDocPos(mStoredDocPos[HORIZONTAL]);
+ }
+ }
} // end updateScroll
void LLScrollContainer::setBorderVisible(bool b)
diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h
index 859750d71c..7761824dbb 100644
--- a/indra/llui/llscrollcontainer.h
+++ b/indra/llui/llscrollcontainer.h
@@ -64,7 +64,8 @@ public:
reserve_scroll_corner,
border_visible,
hide_scrollbar,
- ignore_arrow_keys;
+ ignore_arrow_keys,
+ keep_scroll_pos;
Optional<F32> min_auto_scroll_rate,
max_auto_scroll_rate;
Optional<U32> max_auto_scroll_zone;
@@ -138,6 +139,8 @@ private:
void calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const;
LLScrollbar* mScrollbar[ORIENTATION_COUNT];
+ S32 mStoredDocPos[ORIENTATION_COUNT];
+ bool mKeepScrollPos;
S32 mSize;
bool mIsOpaque;
LLUIColor mBackgroundColor;
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index 0521853b02..802367720f 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -2288,6 +2288,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url));
registrar.add("Url.RemoveFriend", boost::bind(&LLUrlAction::removeFriend, url));
registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url));
+ registrar.add("Url.ReportAbuseObj", boost::bind(&LLUrlAction::reportAbuseObj, url));
registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url));
registrar.add("Url.ZoomInObject", boost::bind(&LLUrlAction::zoomInObject, url));
registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url));
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index bc39d9732c..62f984b8fc 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -327,6 +327,7 @@ class LLTextBase
public:
friend class LLTextSegment;
friend class LLNormalTextSegment;
+ friend class LLEmbeddedItemSegment;
friend class LLUICtrlFactory;
typedef boost::signals2::signal<bool (const LLUUID& user_id)> is_friend_signal_t;
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 7689b93374..5f1302df88 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -61,6 +61,7 @@
#include "lltooltip.h"
#include "llmenugl.h"
#include "llchatmentionhelper.h"
+#include "llgestureautocompletehelper.h"
#include <queue>
#include "llcombobox.h"
@@ -1950,7 +1951,8 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )
// not handled and let the parent take care of field movement.
if (KEY_TAB == key && mTabsToNextField)
{
- return mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask);
+ return (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask))
+ || LLGestureAutocompleteHelper::instance().handleKey(this, key, mask);
}
if (mReadOnly && mScroller)
@@ -1964,7 +1966,8 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )
if (!mReadOnly)
{
if ((mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) ||
- (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask)))
+ (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask)) ||
+ LLGestureAutocompleteHelper::instance().handleKey(this, key, mask))
{
return true;
}
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index d9742db34d..43f47a404c 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -208,6 +208,7 @@ public:
void setShowContextMenu(bool show) { mShowContextMenu = show; }
bool getShowContextMenu() const { return mShowContextMenu; }
+ void showContextMenu(S32 x, S32 y);
void showEmojiHelper();
void hideEmojiHelper();
@@ -219,7 +220,6 @@ public:
LLWString getConvertedText() const;
protected:
- void showContextMenu(S32 x, S32 y);
void drawPreeditMarker();
void removeCharOrTab();
diff --git a/indra/llui/lltextvalidate.cpp b/indra/llui/lltextvalidate.cpp
index 9a087d8230..1f2e7e65db 100644
--- a/indra/llui/lltextvalidate.cpp
+++ b/indra/llui/lltextvalidate.cpp
@@ -434,7 +434,7 @@ public:
} validatorASCIINoLeadingSpaceImpl;
Validator validateASCIINoLeadingSpace(validatorASCIINoLeadingSpaceImpl);
-class ValidatorASCIIWithNewLine : public ValidatorImpl
+class ValidatorASCIIWithNewLineNoPipe : public ValidatorImpl
{
// Used for multiline text stored on the server.
// Example is landmark description in Places SP.
@@ -446,9 +446,9 @@ class ValidatorASCIIWithNewLine : public ValidatorImpl
{
CHAR ch = str[len];
- if ((ch < 0x20 && ch != 0xA) || ch > 0x7f)
+ if ((ch < 0x20 && ch != 0xA) || ch > 0x7f || ch == '|')
{
- return setError("Validator_ShouldBeNewLineOrASCII", LLSD().with("NR", len + 1).with("CH", llsd(ch)));
+ return setError("Validator_ShouldBeNewLineOrASCIINoPipe", LLSD().with("NR", len + 1).with("CH", llsd(ch)));
}
}
@@ -458,8 +458,8 @@ class ValidatorASCIIWithNewLine : public ValidatorImpl
public:
/*virtual*/ bool validate(const std::string& str) override { return validate<char>(str); }
/*virtual*/ bool validate(const LLWString& str) override { return validate<llwchar>(str); }
-} validatorASCIIWithNewLineImpl;
-Validator validateASCIIWithNewLine(validatorASCIIWithNewLineImpl);
+} validatorASCIIWithNewLineNoPipeImpl;
+Validator validateASCIIWithNewLineNoPipe(validatorASCIIWithNewLineNoPipeImpl);
void Validators::declareValues()
{
@@ -472,7 +472,7 @@ void Validators::declareValues()
declare("alpha_num_space", validateAlphaNumSpace);
declare("ascii_printable_no_pipe", validateASCIIPrintableNoPipe);
declare("ascii_printable_no_space", validateASCIIPrintableNoSpace);
- declare("ascii_with_newline", validateASCIIWithNewLine);
+ declare("ascii_with_newline_no_pipe", validateASCIIWithNewLineNoPipe);
}
} // namespace LLTextValidate
diff --git a/indra/llui/lltextvalidate.h b/indra/llui/lltextvalidate.h
index 096c28b448..a95f79bfcb 100644
--- a/indra/llui/lltextvalidate.h
+++ b/indra/llui/lltextvalidate.h
@@ -88,7 +88,7 @@ namespace LLTextValidate
extern Validator validateASCIIPrintableNoSpace;
extern Validator validateASCII;
extern Validator validateASCIINoLeadingSpace;
- extern Validator validateASCIIWithNewLine;
+ extern Validator validateASCIIWithNewLineNoPipe;
// Add available validators to the internal map
struct Validators : public LLInitParam::TypeValuesHelper<Validator, Validators>
diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp
index 785dc85448..2a9bf2585d 100644
--- a/indra/llui/lltoolbar.cpp
+++ b/indra/llui/lltoolbar.cpp
@@ -792,6 +792,11 @@ void LLToolBar::updateLayoutAsNeeded()
mNeedsLayout = false;
}
+bool LLToolBar::postBuild()
+{
+ mCaretIcon = getChild<LLIconCtrl>("caret");
+ return LLUICtrl::postBuild();
+}
void LLToolBar::draw()
{
@@ -835,35 +840,36 @@ void LLToolBar::draw()
LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom);
// Position the caret
- if (!mCaretIcon)
- {
- mCaretIcon = getChild<LLIconCtrl>("caret");
- }
-
- LLIconCtrl* caret = mCaretIcon;
- caret->setVisible(false);
- if (mDragAndDropTarget && !mButtonCommands.empty())
+ // Todo: This shouldn't be on draw, but, as example, on hover
+ if (mCaretIcon)
{
- LLRect caret_rect = caret->getRect();
- if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL)
- {
- caret->setRect(LLRect(mDragx-caret_rect.getWidth()/2+1,
- mDragy,
- mDragx+caret_rect.getWidth()/2+1,
- mDragy-mDragGirth));
- }
- else
+ mCaretIcon->setVisible(false);
+ if (mDragAndDropTarget && !mButtonCommands.empty())
{
- caret->setRect(LLRect(mDragx,
- mDragy+caret_rect.getHeight()/2,
- mDragx+mDragGirth,
- mDragy-caret_rect.getHeight()/2));
+ LLRect caret_rect = mCaretIcon->getRect();
+ if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL)
+ {
+ mCaretIcon->setRect(LLRect(mDragx - caret_rect.getWidth() / 2 + 1,
+ mDragy,
+ mDragx + caret_rect.getWidth() / 2 + 1,
+ mDragy - mDragGirth));
+ }
+ else
+ {
+ mCaretIcon->setRect(LLRect(mDragx,
+ mDragy + caret_rect.getHeight() / 2,
+ mDragx + mDragGirth,
+ mDragy - caret_rect.getHeight() / 2));
+ }
+ mCaretIcon->setVisible(true);
}
- caret->setVisible(true);
}
LLUICtrl::draw();
- caret->setVisible(false);
+ if (mCaretIcon)
+ {
+ mCaretIcon->setVisible(false);
+ }
mDragAndDropTarget = false;
}
diff --git a/indra/llui/lltoolbar.h b/indra/llui/lltoolbar.h
index abf44f259a..b4fb3fab02 100644
--- a/indra/llui/lltoolbar.h
+++ b/indra/llui/lltoolbar.h
@@ -213,6 +213,7 @@ public:
};
// virtuals
+ bool postBuild();
void draw();
void reshape(S32 width, S32 height, bool called_from_parent = true);
bool handleRightMouseDown(S32 x, S32 y, MASK mask);
diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp
index 8b320b59cc..61a529c5d2 100644
--- a/indra/llui/llurlaction.cpp
+++ b/indra/llui/llurlaction.cpp
@@ -269,13 +269,22 @@ void LLUrlAction::reportAbuse(std::string url)
}
}
+void LLUrlAction::reportAbuseObj(std::string url)
+{
+ std::string object_id = getObjectId(url);
+ if (LLUUID::validate(object_id))
+ {
+ executeSLURL("secondlife:///app/object/" + object_id + "/reportAbuse");
+ }
+}
+
void LLUrlAction::blockObject(std::string url)
{
std::string object_id = getObjectId(url);
std::string object_name = getObjectName(url);
if (LLUUID::validate(object_id))
{
- executeSLURL("secondlife:///app/agent/" + object_id + "/block/" + LLURI::escape(object_name));
+ executeSLURL("secondlife:///app/object/" + object_id + "/block/" + LLURI::escape(object_name));
}
}
@@ -285,6 +294,6 @@ void LLUrlAction::unblockObject(std::string url)
std::string object_name = getObjectName(url);
if (LLUUID::validate(object_id))
{
- executeSLURL("secondlife:///app/agent/" + object_id + "/unblock/" + object_name);
+ executeSLURL("secondlife:///app/object/" + object_id + "/unblock/" + LLURI::escape(object_name));
}
}
diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h
index c4cfd0f3fb..d110fe56d3 100644
--- a/indra/llui/llurlaction.h
+++ b/indra/llui/llurlaction.h
@@ -89,6 +89,7 @@ public:
static void addFriend(std::string url);
static void removeFriend(std::string url);
static void reportAbuse(std::string url);
+ static void reportAbuseObj(std::string url);
static void blockObject(std::string url);
static void unblockObject(std::string url);
diff --git a/indra/llui/llxyvector.cpp b/indra/llui/llxyvector.cpp
index 1521823ce2..b3c38bf82d 100644
--- a/indra/llui/llxyvector.cpp
+++ b/indra/llui/llxyvector.cpp
@@ -211,7 +211,16 @@ void LLXYVector::draw()
mGhostY = pointY;
}
- if (abs(mValueX) >= mIncrementX || abs(mValueY) >= mIncrementY)
+ S32 dx = abs(pointX - centerX);
+ S32 dy = abs(pointY - centerY);
+ bool draw_arrow =
+ (abs(mValueX) >= mIncrementX || abs(mValueY) >= mIncrementY)
+ && (dx >= 1 || dy >= 1); // At least 1 pixel displacement
+
+ // Todo: Arrow doesn't display well with small values.
+ // Ex: (0.1, 0.05) will point to the right, as if it is (0.1, 0.0)
+ // as position will be offset by a single pixes on X, and 0 pixels on Y.
+ if (draw_arrow)
{
// draw the vector arrow
drawArrow(centerX, centerY, pointX, pointY, mArrowColor);
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
index 3d58e4e1ce..bdb629172d 100644
--- a/indra/llwebrtc/llwebrtc.cpp
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -26,6 +26,9 @@
#include "llwebrtc_impl.h"
#include <algorithm>
+#include <chrono>
+#include <future>
+#include <thread>
#include <string.h>
#include "api/audio/create_audio_device_module.h"
#include "api/audio_codecs/audio_decoder_factory.h"
@@ -395,8 +398,23 @@ void LLWebRTCImpl::init()
void LLWebRTCImpl::terminate()
{
- mWorkerThread->BlockingCall(
- [this]()
+ // Run all blocking WebRTC shutdown calls on a separate thread so that a
+ // hung BlockingCall cannot block the viewer shutdown indefinitely.
+ // Webrtc is not mission critical, we need to save personal data.
+ auto done_promise = std::make_shared<std::promise<void> >();
+ std::future<void> done_future = done_promise->get_future();
+
+ // Hand ownership of the connections to the shutdown thread. Nothing on
+ // this thread may touch them afterwards -- in the timeout case below the
+ // shutdown thread is detached and may still be working through them.
+ std::vector<webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> connections;
+ connections.swap(mPeerConnections);
+
+ std::thread shutdown_thread(
+ [this, connections = std::move(connections), done_promise]() mutable
+ {
+ mWorkerThread->BlockingCall(
+ [this]()
{
if (mDeviceModule)
{
@@ -405,34 +423,79 @@ void LLWebRTCImpl::terminate()
}
});
- for (auto &connection : mPeerConnections)
- {
- connection->terminate();
- }
+ // Close the connections inline on the signaling thread. This can't be
+ // connection->terminate(), which only *posts* the close: that queues the
+ // real work behind everything below, so the connections would be closed
+ // after the factory and the device module are gone -- or not at all, if
+ // the thread is destroyed with the task still queued.
+ //
+ // It matters that the close completes here because closing a peer
+ // connection flushes any in-flight GetStats request and runs its
+ // callback inline, and that callback calls back into the viewer's
+ // signaling observers. Those observers are only valid until
+ // llwebrtc::terminate() returns.
+ mSignalingThread->BlockingCall(
+ [&connections]()
+ {
+ for (auto& connection : connections)
+ {
+ connection->closeOnSignalingThread();
+ }
+ // Destroy the connections here, on the signaling thread, while
+ // it's still running.
+ connections.clear();
+ });
- // connection->terminate() above spawns a number of Signaling thread calls to
- // shut down the connection. The following Blocking Call will wait
- // until they're done before it's executed, allowing time to clean up.
+ // Drain anything the closes posted before dropping the factory.
+ mSignalingThread->BlockingCall([]() {});
- mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; });
+ mSignalingThread->BlockingCall([this]() {
+ mPeerConnectionFactory = nullptr;
+ });
- mWorkerThread->BlockingCall(
- [this]()
+ mWorkerThread->BlockingCall(
+ [this]()
{
if (mDeviceModule)
{
mDeviceModule->ForceTerminate();
}
- mDeviceModule = nullptr;
+ mDeviceModule = nullptr;
});
- // In case peer connections still somehow have jobs in workers,
- // only clear connections up after clearing workers.
+ done_promise->set_value();
+ });
+
+ constexpr auto WEBRTC_TERMINATE_TIMEOUT = std::chrono::seconds(10);
+ if (done_future.wait_for(WEBRTC_TERMINATE_TIMEOUT) == std::future_status::timeout)
+ {
+ RTC_LOG(LS_WARNING) << __FUNCTION__
+ << ": timed out waiting for WebRTC thread shutdown."
+ " Detaching — some WebRTC resources will be leaked.";
+ shutdown_thread.detach();
+
+ // Release the unique_ptrs WITHOUT joining/deleting: the detached thread
+ // may still be using these thread objects.
+ // The raw pointers are intentionally leaked — the process is exiting anyway
+ // and our priority is saving cache and personal data.
+ (void)mNetworkThread.release();
+ (void)mWorkerThread.release();
+ (void)mSignalingThread.release();
+
+ // mPeerConnections is already empty -- the detached thread owns the
+ // connections now and must be left to finish with them.
+ webrtc::LogMessage::RemoveLogToStream(mLogSink);
+ return;
+ }
+
+ shutdown_thread.join();
+
+ // The connections were closed and destroyed on the signaling thread before
+ // the shutdown thread finished, so it's safe to drop the threads now.
mNetworkThread = nullptr;
mWorkerThread = nullptr;
mSignalingThread = nullptr;
- mPeerConnections.clear();
webrtc::LogMessage::RemoveLogToStream(mLogSink);
}
@@ -748,6 +811,7 @@ void LLWebRTCImpl::updateDevices()
char name[webrtc::kAdmMaxDeviceNameSize];
char guid[webrtc::kAdmMaxGuidSize];
mDeviceModule->PlayoutDeviceName(index, name, guid);
+ RTC_LOG(LS_VERBOSE) << "updateDevices: playout device [" << index << "] name='" << name << "' guid='" << guid << "'";
mPlayoutDeviceList.emplace_back(name, guid);
}
@@ -766,9 +830,12 @@ void LLWebRTCImpl::updateDevices()
char name[webrtc::kAdmMaxDeviceNameSize];
char guid[webrtc::kAdmMaxGuidSize];
mDeviceModule->RecordingDeviceName(index, name, guid);
+ RTC_LOG(LS_VERBOSE) << "updateDevices: recording device [" << index << "] name='" << name << "' guid='" << guid << "'";
mRecordingDeviceList.emplace_back(name, guid);
}
+ RTC_LOG(LS_INFO) << "updateDevices, playout count: " << renderDeviceCount << "; capture count: " << captureDeviceCount;
+
for (auto &observer : mVoiceDevicesObserverList)
{
observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList);
@@ -978,6 +1045,8 @@ LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl(const webrtc::Environment
mAnswerReceived(false),
mPeerConnectionState(webrtc::PeerConnectionInterface::PeerConnectionState::kNew),
mDisconnectCount(0),
+ mStatsRequestPending(false),
+ mShuttingDown(false),
mPendingJobs(0)
{
}
@@ -1010,47 +1079,64 @@ void LLWebRTCPeerConnectionImpl::terminate()
mWebRTCImpl->PostSignalingTask(
[self]()
{
- if (self->mPeerConnection)
- {
- if (self->mDataChannel)
- {
- {
- self->mDataChannel->Close();
- self->mDataChannel = nullptr;
- }
- }
+ self->closeOnSignalingThread();
+ self->mPendingJobs--;
+ });
+}
- // to remove 'Secondlife is recording' icon from taskbar
- // if user was speaking
- auto senders = self->mPeerConnection->GetSenders();
- for (auto& sender : senders)
- {
- auto track = sender->track();
- if (track)
- {
- track->set_enabled(false);
- }
- }
+// Signaling thread only.
+void LLWebRTCPeerConnectionImpl::closeOnSignalingThread()
+{
+ // Stop issuing stats requests; one may already be in flight, and
+ // Close() below will flush it.
+ mShuttingDown = true;
- self->mPeerConnection->Close();
- if (self->mLocalStream)
- {
- auto tracks = self->mLocalStream->GetAudioTracks();
- for (auto& track : tracks)
- {
- self->mLocalStream->RemoveTrack(track);
- }
- self->mLocalStream = nullptr;
- }
- self->mPeerConnection = nullptr;
+ if (mPeerConnection)
+ {
+ if (mDataChannel)
+ {
+ mDataChannel->Close();
+ mDataChannel = nullptr;
+ }
- for (auto &observer : self->mSignalingObserverList)
- {
- observer->OnPeerConnectionClosed();
- }
+ // to remove 'Secondlife is recording' icon from taskbar
+ // if user was speaking
+ auto senders = mPeerConnection->GetSenders();
+ for (auto& sender : senders)
+ {
+ auto track = sender->track();
+ if (track)
+ {
+ track->set_enabled(false);
}
- self->mPendingJobs--;
- });
+ }
+
+ // NOTE: Close() delivers any pending GetStats report inline, before it
+ // returns, so the observer list below must still be valid here.
+ mPeerConnection->Close();
+ if (mLocalStream)
+ {
+ auto tracks = mLocalStream->GetAudioTracks();
+ for (auto& track : tracks)
+ {
+ mLocalStream->RemoveTrack(track);
+ }
+ mLocalStream = nullptr;
+ }
+ mPeerConnection = nullptr;
+
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnPeerConnectionClosed();
+ }
+ }
+
+ // Nothing may call back into the viewer past this point. On shutdown the
+ // viewer's connection objects are torn down as soon as llwebrtc::terminate()
+ // returns and they deliberately don't unset themselves as observers, so any
+ // late callback would be reaching into freed memory.
+ mSignalingObserverList.clear();
+ mDataObserverList.clear();
}
void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); }
@@ -1777,16 +1863,41 @@ void LLWebRTCPeerConnectionImpl::gatherConnectionStats()
return;
}
- auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
- [this](const LLWebRTCStatsMap& generic_stats)
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
+ mWebRTCImpl->PostSignalingTask(
+ [self]()
+ {
+ if (!self->mPeerConnection
+ || self->mShuttingDown
+ || self->mPeerConnectionState != webrtc::PeerConnectionInterface::PeerConnectionState::kConnected
+ || self->mStatsRequestPending) // signaling thread only
+ {
+ return;
+ }
+
+ self->mStatsRequestPending = true;
+
+ auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
+ [self](const LLWebRTCStatsMap& generic_stats)
{
- for (auto& observer : mSignalingObserverList)
+ self->mStatsRequestPending = false;
+
+ // This can be delivered inline from PeerConnection::Close(), which
+ // flushes pending stats requests as it tears down. Don't call out
+ // to the observers in that case -- we're on our way out.
+ if (!self->mPeerConnection || self->mShuttingDown)
+ {
+ return;
+ }
+
+ for (auto& observer : self->mSignalingObserverList)
{
observer->OnStatsDelivered(generic_stats);
}
});
- mPeerConnection->GetStats(stats_callback.get());
+ self->mPeerConnection->GetStats(stats_callback.get());
+ });
}
LLWebRTCImpl * gWebRTCImpl = nullptr;
diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h
index 28d25b8d51..cee42cf19a 100644
--- a/indra/llwebrtc/llwebrtc_impl.h
+++ b/indra/llwebrtc/llwebrtc_impl.h
@@ -585,7 +585,12 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
~LLWebRTCPeerConnectionImpl();
void init(LLWebRTCImpl * webrtc_impl);
+ // Posts closeOnSignalingThread() and returns immediately.
void terminate();
+ // The actual close. Signaling thread only. Callable directly (via a
+ // BlockingCall) when the caller needs the connection to be fully closed
+ // before it continues -- see LLWebRTCImpl::terminate().
+ void closeOnSignalingThread();
virtual void AddRef() const override = 0;
virtual webrtc::RefCountReleaseStatus Release() const override = 0;
@@ -688,6 +693,14 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
webrtc::PeerConnectionInterface::PeerConnectionState mPeerConnectionState;
uint32_t mDisconnectCount;
+ // Accessed only on the WebRTC signaling thread.
+ bool mStatsRequestPending;
+
+ // Set by closeOnSignalingThread() so that no new stats request (or other
+ // callback into the viewer) is issued while we're tearing down.
+ // Accessed only on the WebRTC signaling thread.
+ bool mShuttingDown;
+
std::atomic<int> mPendingJobs;
};
diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt
index aacaef9257..5f8a9a64eb 100644
--- a/indra/llwindow/CMakeLists.txt
+++ b/indra/llwindow/CMakeLists.txt
@@ -50,6 +50,49 @@ set(viewer_HEADER_FILES
llmousehandler.h
)
+# Platform-specific files for IDE visibility (excluded from build on other platforms)
+set(macosx_SOURCE_FILES
+ llkeyboardmacosx.cpp
+ llwindowmacosx.cpp
+ llwindowmacosx-objc.mm
+ llopenglview-objc.mm
+ )
+
+set(macosx_HEADER_FILES
+ llkeyboardmacosx.h
+ llwindowmacosx.h
+ llwindowmacosx-objc.h
+ llwindowmacosx_iokit.h
+ llopenglview-objc.h
+ llappdelegate-objc.h
+ )
+
+set(linux_SOURCE_FILES
+ llsdl.cpp
+ llkeyboardsdl.cpp
+ llwindowsdl.cpp
+ )
+
+set(linux_HEADER_FILES
+ llsdl.h
+ llkeyboardsdl.h
+ llwindowsdl.h
+ )
+
+set(windows_SOURCE_FILES
+ llwindowwin32.cpp
+ lldxhardware.cpp
+ llkeyboardwin32.cpp
+ lldragdropwin32.cpp
+ )
+
+set(windows_HEADER_FILES
+ llwindowwin32.h
+ lldxhardware.h
+ llkeyboardwin32.h
+ lldragdropwin32.h
+ )
+
set(llwindow_LINK_LIBRARIES
llcommon
llimage
@@ -63,16 +106,8 @@ set(llwindow_LINK_LIBRARIES
)
if (USE_SDL_WINDOW)
- list(APPEND viewer_SOURCE_FILES
- llsdl.cpp
- llkeyboardsdl.cpp
- llwindowsdl.cpp
- )
- list(APPEND viewer_HEADER_FILES
- llsdl.h
- llkeyboardsdl.h
- llwindowsdl.h
- )
+ list(APPEND viewer_SOURCE_FILES ${linux_SOURCE_FILES})
+ list(APPEND viewer_HEADER_FILES ${linux_HEADER_FILES})
list(APPEND llwindow_LINK_LIBRARIES
ll::SDL3
@@ -80,19 +115,8 @@ if (USE_SDL_WINDOW)
endif ()
if (DARWIN)
- list(APPEND llwindow_SOURCE_FILES
- llkeyboardmacosx.cpp
- llwindowmacosx.cpp
- llwindowmacosx-objc.mm
- llopenglview-objc.mm
- )
- list(APPEND llwindow_HEADER_FILES
- llkeyboardmacosx.h
- llwindowmacosx.h
- llwindowmacosx-objc.h
- llopenglview-objc.h
- llappdelegate-objc.h
- )
+ list(APPEND llwindow_SOURCE_FILES ${macosx_SOURCE_FILES})
+ list(APPEND llwindow_HEADER_FILES ${macosx_HEADER_FILES})
# We use a bunch of deprecated system APIs.
set_source_files_properties(
@@ -112,18 +136,8 @@ endif (DARWIN)
if (WINDOWS)
- list(APPEND llwindow_SOURCE_FILES
- llwindowwin32.cpp
- lldxhardware.cpp
- llkeyboardwin32.cpp
- lldragdropwin32.cpp
- )
- list(APPEND llwindow_HEADER_FILES
- llwindowwin32.h
- lldxhardware.h
- llkeyboardwin32.h
- lldragdropwin32.h
- )
+ list(APPEND llwindow_SOURCE_FILES ${windows_SOURCE_FILES})
+ list(APPEND llwindow_HEADER_FILES ${windows_HEADER_FILES})
list(APPEND llwindow_LINK_LIBRARIES
comdlg32 # Common Dialogs for ChooseColor
ole32
@@ -189,11 +203,49 @@ endif (llwindow_HEADER_FILES)
list(APPEND viewer_SOURCE_FILES ${viewer_HEADER_FILES})
+ # Collect all platform files for IDE visibility, excluding already-added current platform
+ set(ide_visibility_files "")
+ if (NOT DARWIN)
+ list(APPEND ide_visibility_files ${macosx_SOURCE_FILES} ${macosx_HEADER_FILES})
+ endif()
+ if (NOT LINUX)
+ list(APPEND ide_visibility_files ${linux_SOURCE_FILES} ${linux_HEADER_FILES})
+ endif()
+ if (NOT WINDOWS)
+ list(APPEND ide_visibility_files ${windows_SOURCE_FILES} ${windows_HEADER_FILES})
+ endif()
+
add_library (llwindow
${llwindow_SOURCE_FILES}
${viewer_SOURCE_FILES}
+ # Add non-platform files for IDE visibility only
+ ${ide_visibility_files}
)
+# Mark non-platform files as excluded from build
+if (WINDOWS)
+ set_source_files_properties(
+ ${macosx_SOURCE_FILES}
+ ${linux_SOURCE_FILES}
+ PROPERTIES
+ HEADER_FILE_ONLY TRUE
+ )
+elseif (DARWIN)
+ set_source_files_properties(
+ ${windows_SOURCE_FILES}
+ ${linux_SOURCE_FILES}
+ PROPERTIES
+ HEADER_FILE_ONLY TRUE
+ )
+elseif (LINUX)
+ set_source_files_properties(
+ ${macosx_SOURCE_FILES}
+ ${windows_SOURCE_FILES}
+ PROPERTIES
+ HEADER_FILE_ONLY TRUE
+ )
+endif()
+
target_link_libraries (llwindow ${llwindow_LINK_LIBRARIES})
target_include_directories(llwindow INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp
index 7331f50ba0..f997aebb9d 100644
--- a/indra/llwindow/llwindowcallbacks.cpp
+++ b/indra/llwindow/llwindowcallbacks.cpp
@@ -68,6 +68,18 @@ void LLWindowCallbacks::handleMouseLeave(LLWindow *window)
return;
}
+void LLWindowCallbacks::handlePreCloseRequest()
+{
+}
+
+void LLWindowCallbacks::handleCloseRequestCanceled()
+{
+}
+
+void LLWindowCallbacks::handleSuspendRequest()
+{
+}
+
bool LLWindowCallbacks::handleCloseRequest(LLWindow *window, bool from_user)
{
//allow the window to close
@@ -190,7 +202,7 @@ bool LLWindowCallbacks::handleTimerEvent(LLWindow *window)
return false;
}
-bool LLWindowCallbacks::handleDeviceChange(LLWindow *window)
+bool LLWindowCallbacks::handleDeviceChange(LLWindow *window, const std::string& change_type)
{
return false;
}
diff --git a/indra/llwindow/llwindowcallbacks.h b/indra/llwindow/llwindowcallbacks.h
index 59dcdd3ade..cd54ccd7c9 100644
--- a/indra/llwindow/llwindowcallbacks.h
+++ b/indra/llwindow/llwindowcallbacks.h
@@ -41,6 +41,10 @@ public:
virtual bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask);
virtual bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask);
virtual void handleMouseLeave(LLWindow *window);
+ // Called before close request is processed (ex: to create marker file in case OS is about to kill app).
+ virtual void handlePreCloseRequest();
+ virtual void handleCloseRequestCanceled();
+ virtual void handleSuspendRequest();
// return true to allow window to close, which will then cause handleQuit to be called
virtual bool handleCloseRequest(LLWindow *window, bool from_user);
virtual bool handleSessionExit(LLWindow* window);
@@ -68,7 +72,7 @@ public:
virtual void handleWindowUnblock(LLWindow *window); // window coming back after taking over CPU for a while
virtual void handleDataCopy(LLWindow *window, S32 data_type, void *data);
virtual bool handleTimerEvent(LLWindow *window);
- virtual bool handleDeviceChange(LLWindow *window);
+ virtual bool handleDeviceChange(LLWindow *window, const std::string& change_type);
virtual bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height);
virtual bool handleDisplayChanged();
virtual bool handleWindowDidChangeScreen(LLWindow *window);
diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp
index b5dc841c45..7cb681aefe 100644
--- a/indra/llwindow/llwindowmacosx.cpp
+++ b/indra/llwindow/llwindowmacosx.cpp
@@ -44,7 +44,7 @@
#include <CoreGraphics/CGDisplayConfiguration.h>
#include <IOKit/IOCFPlugIn.h>
-#include <IOKit/IOKitLib.h>
+#include "llwindowmacosx_iokit.h"
#include <IOKit/IOMessage.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDLib.h>
@@ -2379,7 +2379,7 @@ bool LLWindowMacOSX::getInputDevices(U32 device_type_filter,
io_iterator_t io_iter = 0;
// create an IO object iterator
- result = IOServiceGetMatchingServices( kIOMasterPortDefault, device_dict_ref, &io_iter );
+ result = IOServiceGetMatchingServices( kLLIOMainPort, device_dict_ref, &io_iter );
if ( kIOReturnSuccess != result )
{
LL_WARNS("Joystick") << "IOServiceGetMatchingServices failed" << LL_ENDL;
diff --git a/indra/llwindow/llwindowmacosx_iokit.h b/indra/llwindow/llwindowmacosx_iokit.h
new file mode 100644
index 0000000000..a6be2c86ef
--- /dev/null
+++ b/indra/llwindow/llwindowmacosx_iokit.h
@@ -0,0 +1,35 @@
+/**
+ * @file llwindowmacosx_iokit.h
+ * @brief IOKit compatibility for macOS deployment target differences
+ *
+ * $LicenseInfo:firstyear=2025&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2025, 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
+#include <IOKit/IOKitLib.h>
+
+// kIOMainPortDefault is the macOS 12+ rename of kIOMasterPortDefault.
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
+static const mach_port_t kLLIOMainPort = kIOMainPortDefault;
+#else
+static const mach_port_t kLLIOMainPort = kIOMasterPortDefault;
+#endif
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 6ac17f8779..a93a3150e1 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -177,6 +177,19 @@ void show_window_creation_error(const std::string& title)
LL_WARNS("Window") << title << LL_ENDL;
}
+static bool is_thread_from_current_process(DWORD thread_id)
+{
+ HANDLE thread_handle = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, thread_id);
+ if (!thread_handle)
+ {
+ return false;
+ }
+
+ const DWORD process_id = GetProcessIdOfThread(thread_handle);
+ CloseHandle(thread_handle);
+ return process_id == GetCurrentProcessId();
+}
+
HGLRC SafeCreateContext(HDC &hdc)
{
__try
@@ -453,6 +466,16 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
}
});
}
+
+ // For mainWindowProc, it should not unpause watchdog if it was paused
+ void pingWindowTimeout(std::string_view state)
+ {
+ if (mWindowTimeout && mWindowTimeout->started())
+ {
+ mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC);
+ mWindowTimeout->ping(state);
+ }
+ }
private:
// These timeout related functions are strictly for the thread.
void resumeTimeout(std::string_view state)
@@ -509,6 +532,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
:
LLWindow(callbacks, fullscreen, flags),
mAbsoluteCursorPosition(false),
+ mReceivedSCClose(false),
mMaxGLVersion(max_gl_version),
mMaxCores(max_cores)
{
@@ -1421,7 +1445,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
catch (...)
{
LOG_UNHANDLED_EXCEPTION("ChoosePixelFormat");
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1432,7 +1456,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
&pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1470,7 +1494,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
if (!SetPixelFormat(mhDC, pixel_format, &pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1478,14 +1502,14 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
if (!(mhRC = SafeCreateContext(mhDC)))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
if (!wglMakeCurrent(mhDC, mhRC))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1691,14 +1715,14 @@ const S32 max_format = (S32)num_formats - 1;
if (!mhDC)
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
if (!SetPixelFormat(mhDC, pixel_format, &pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1730,7 +1754,7 @@ const S32 max_format = (S32)num_formats - 1;
{
LL_WARNS("Window") << "No wgl_ARB_pixel_format extension!" << LL_ENDL;
// cannot proceed without wgl_ARB_pixel_format extension, shutdown same as any other gGLManager.initGL() failure
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1739,7 +1763,7 @@ const S32 max_format = (S32)num_formats - 1;
if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
&pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1761,7 +1785,7 @@ const S32 max_format = (S32)num_formats - 1;
if (!wglMakeCurrent(mhDC, mhRC))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1770,7 +1794,7 @@ const S32 max_format = (S32)num_formats - 1;
if (!gGLManager.initGL())
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1978,7 +2002,7 @@ void* LLWindowWin32::createSharedContext()
if (!rc && !(rc = wglCreateContext(mhDC)))
{
close();
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
}
return rc;
@@ -2416,18 +2440,45 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_DEVICECHANGE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DEVICECHANGE");
+ window_imp->mWindowThread->pingWindowTimeout("WM_DEVICECHANGE");
+
+ // Log detailed device change information
+ std::string change_type = "UNKNOWN";
+ switch (w_param)
+ {
+ case DBT_DEVICEARRIVAL: change_type = "DBT_DEVICEARRIVAL"; break;
+ case DBT_DEVICEREMOVECOMPLETE: change_type = "DBT_DEVICEREMOVECOMPLETE"; break;
+ case DBT_DEVNODES_CHANGED: change_type = "DBT_DEVNODES_CHANGED"; break;
+ case DBT_DEVICEQUERYREMOVE: change_type = "DBT_DEVICEQUERYREMOVE"; break;
+ case DBT_DEVICEQUERYREMOVEFAILED: change_type = "DBT_DEVICEQUERYREMOVEFAILED"; break;
+ case DBT_DEVICEREMOVEPENDING: change_type = "DBT_DEVICEREMOVEPENDING"; break;
+ case DBT_CONFIGCHANGED: change_type = "DBT_CONFIGCHANGED"; break;
+ }
+
if (w_param == DBT_DEVNODES_CHANGED || w_param == DBT_DEVICEARRIVAL)
{
- WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp));
+ WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp, change_type));
return 1;
}
+ else if (l_param)
+ {
+ const auto* hdr = reinterpret_cast<const DEV_BROADCAST_HDR*>(l_param);
+ if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
+ {
+ // Might need to register for monitor device notifications
+ // to get this message when monitor is suspended or resumed.
+ // TODO: log monitor suspending and resuming.
+ LL_INFOS("Window") << "DEVICEINTERFACE: " << change_type << LL_ENDL;
+ }
+ }
break;
}
case WM_PAINT:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_PAINT");
+ window_imp->mWindowThread->pingWindowTimeout("WM_PAINT");
GetUpdateRect(window_imp->mWindowHandle, &update_rect, FALSE);
update_width = update_rect.right - update_rect.left + 1;
update_height = update_rect.bottom - update_rect.top + 1;
@@ -2436,6 +2487,15 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
update_width, update_height));
break;
}
+ case WM_ERASEBKGND:
+ {
+ RECT client_rect;
+ if (GetClientRect(h_wnd, &client_rect))
+ {
+ FillRect((HDC)w_param, &client_rect, (HBRUSH)GetStockObject(BLACK_BRUSH));
+ }
+ return 1;
+ }
case WM_PARENTNOTIFY:
{
break;
@@ -2473,11 +2533,24 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_ACTIVATEAPP:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ACTIVATEAPP");
+ // Resolve ownership before deferring the work because thread IDs can
+ // be reused after a thread exits.
+ const bool activating_same_process_thread =
+ !w_param && is_thread_from_current_process(static_cast<DWORD>(l_param));
window_imp->post([=]()
{
// This message should be sent whenever the app gains or loses focus.
BOOL activating = (BOOL)w_param;
+ // Native dialogs run on a worker thread. Moving focus between
+ // the viewer and one of those dialogs must not be treated as
+ // switching to another application: in fullscreen that would
+ // minimize the viewer and hide its owned dialog.
+ if (!activating && activating_same_process_thread)
+ {
+ activating = TRUE;
+ }
+
if (window_imp->mFullscreen)
{
// When we run fullscreen, restoring or minimizing the app needs
@@ -2527,8 +2600,15 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_SYSCOMMAND:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SYSCOMMAND");
- switch (w_param)
+ switch (w_param & 0xFFF0)
{
+ case SC_CLOSE:
+ // User clicked close from system menu/taskbar or 'end process' from task manager
+ // Do nothing, will cause WM_CLOSE.
+ // If we don't get this message before WM_CLOSE, we are likely getting
+ // a kill from some external program. Win11 task manager Does cause SC_CLOSE.
+ window_imp->mReceivedSCClose = true;
+ break;
case SC_KEYMENU:
// Disallow the ALT key from triggering the default system menu.
return 0;
@@ -2543,9 +2623,31 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_CLOSE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE");
- // todo: WM_CLOSE can be caused by user and by task manager,
- // distinguish these cases.
- // For now assume it is always user.
+ window_imp->mWindowThread->pingWindowTimeout("WM_CLOSE");
+
+ window_imp->mCallbacks->handlePreCloseRequest(); // mark app as potentially closing
+ if (!window_imp->mReceivedSCClose)
+ {
+ // Some external program is trying to close the app.
+ // Assume that it's going to destroy process if it fails
+ // and try to fast-quit without confirmation or cleanup.
+ window_imp->post([=]()
+ {
+ // Check if app needs cleanup or can be closed immediately.
+ if (window_imp->mCallbacks->handleSessionExit(window_imp))
+ {
+ // Get the app to initiate cleanup.
+ window_imp->mCallbacks->handleQuit(window_imp);
+ }
+ });
+ return 0;
+ }
+ window_imp->mReceivedSCClose = false;
+
+ // There is no way to tell the difference between a user issued
+ // WM_CLOSE or task manager's WM_CLOSE.
+ // Assume it is a user and ask for confirmation, but create a marker file.
+ // If App keeps doing something after a second, or gets 'destroy' message clear the marker.
window_imp->post([=]()
{
// Will the app allow the window to close?
@@ -2567,6 +2669,16 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
}
return 0;
}
+ case WM_NCDESTROY:
+ LL_INFOS("Window") << "Received WM_NCDESTROY" << LL_ENDL;
+ break;
+ case WM_WTSSESSION_CHANGE:
+ {
+ // Detects Remote Desktop disconnects, fast user switching, session logoff
+ // w_param: WTS_CONSOLE_CONNECT, WTS_CONSOLE_DISCONNECT, WTS_SESSION_LOGOFF, etc.
+ LL_INFOS("Window") << "Received WM_WTSSESSION_CHANGE with wParam: " << (U32)w_param << LL_ENDL;
+ break;
+ }
case WM_QUERYENDSESSION:
{
// Generally means that OS is going to shut down or user is going to log off.
@@ -2580,6 +2692,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
// Comes after WM_QUERYENDSESSION
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ENDSESSION");
LL_INFOS("Window") << "Received WM_ENDSESSION with wParam: " << (U32)w_param << " lParam: " << (U32)l_param << LL_ENDL;
+ window_imp->mWindowThread->pingWindowTimeout("WM_ENDSESSION");
unsigned int end_session_flags = (U32)l_param;
if (w_param == TRUE // if true, session is ending
@@ -2588,8 +2701,10 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
|| (end_session_flags & ENDSESSION_CRITICAL) // will shutdown regardless of app state
|| (end_session_flags & ENDSESSION_LOGOFF)) // logoff, can delay shutdown
{
+ window_imp->mCallbacks->handlePreCloseRequest(); // mark app as closing
window_imp->post([=]()
{
+ LL_INFOS("Window") << "Shutting down due to session terminating" << LL_ENDL;
// Check if app needs cleanup or can be closed immediately.
if (window_imp->mCallbacks->handleSessionExit(window_imp))
{
@@ -2608,6 +2723,193 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
// if session is ending OS is going to take care of it.
return 0;
}
+ case WM_POWERBROADCAST:
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POWERBROADCAST");
+ switch (w_param)
+ {
+ case PBT_APMSUSPEND:
+ LL_INFOS("Window") << "System is suspending (sleep/hibernate)" << LL_ENDL;
+ // System is about to enter sleep or hibernation
+ // Viewer can't function in hibernation, try to shut down.
+ // The system allows approximately two seconds for an
+ // application to handle this notification.
+
+ // Mark app as potentially closing, to minimize issues if OS does not recover.
+ window_imp->mCallbacks->handlePreCloseRequest();
+ window_imp->post([=]()
+ {
+ window_imp->mCallbacks->handleSuspendRequest();
+ });
+ // Window thread normally doesn't block main thread, but OS can suspend
+ // immediately if we don't wait.
+ // Keep OS from suspending to give a chance to send stats.
+ ms_sleep(1000);
+ return TRUE;
+
+ case PBT_APMRESUMESUSPEND:
+ LL_INFOS("Window") << "System is resuming from suspend" << LL_ENDL;
+ window_imp->mCallbacks->handleCloseRequestCanceled();
+ return TRUE;
+
+ case PBT_APMPOWERSTATUSCHANGE:
+ LL_INFOS("Window") << "Power status has changed" << LL_ENDL;
+ // Power status change (AC/battery)
+ // Viewer requires high performance, not much we can do.
+ // about it, but log for diagnostic purposes (example:
+ // OS trying to throw viewer at an iGPU after this message)
+ return TRUE;
+
+ default:
+ LL_INFOS("Window") << "Received WM_POWERBROADCAST with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL;
+ break;
+ }
+ break;
+ }
+ case WM_POST_UNINSTALL_:
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POST_UNINSTALL_");
+ // Other instance, likely velopack, requested we quit.
+ // Don't trust PID alone (can be spoofed), verify the
+ // path for security purposes before processing.
+ // Verifying path isn't a strong varranty, if this turns
+ // up to be a risk, we will want something more secure.
+ // See sendShutdownToOtherInstances for the sender.
+
+ // LPARAM contains message type.
+ DWORD message_type = static_cast<DWORD>(l_param);
+ if (message_type == WM_POST_UNINSTALL_MSG_SHUTDOWN || message_type == WM_POST_UNINSTALL_MSG_UPDATE)
+ {
+ DWORD sender_process_id = static_cast<DWORD>(w_param);
+
+ // Make sure something didn't just send us our own process
+ DWORD our_process_id = GetCurrentProcessId();
+ if (our_process_id == sender_process_id)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ from our own process, ignoring" << LL_ENDL;
+ break;
+ }
+
+ if (sender_process_id == 0)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't get sender process ID" << LL_ENDL;
+ break;
+ }
+
+ // Open the existing sender process to verify its executable path
+ HANDLE hSenderProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, sender_process_id);
+ if (!hSenderProcess)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't open sender process" << LL_ENDL;
+ break;
+ }
+
+ // Get the actual executable path of the sender
+ wchar_t sender_exe_path[MAX_PATH];
+ DWORD size = MAX_PATH;
+ bool got_sender_path = QueryFullProcessImageNameW(hSenderProcess, 0, sender_exe_path, &size) != 0;
+ CloseHandle(hSenderProcess);
+
+ if (!got_sender_path)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't query sender executable path" << LL_ENDL;
+ break;
+ }
+
+ // Extract directory from sender's executable path
+ wchar_t sender_dir[MAX_PATH];
+ wchar_t* file_part = nullptr;
+ DWORD result = GetFullPathNameW(sender_exe_path, MAX_PATH, sender_dir, &file_part);
+
+ if (result == 0 || result >= MAX_PATH)
+ {
+ LL_WARNS("Window") << "Failed to normalize sender executable path" << LL_ENDL;
+ break;
+ }
+
+ // Remove the filename to get just directory
+ if (file_part)
+ {
+ *file_part = L'\0';
+ }
+
+ // Remove trailing backslash
+ size_t sender_dir_len = wcslen(sender_dir);
+ if (sender_dir_len > 0 && sender_dir[sender_dir_len - 1] == L'\\')
+ {
+ sender_dir[sender_dir_len - 1] = L'\0';
+ sender_dir_len--;
+ }
+
+ // Remove "\current" suffix from sender's path if present
+ const std::wstring current_suffix = L"\\current";
+ std::wstring sender_normalized_str(sender_dir);
+ if (sender_normalized_str.length() >= current_suffix.length() &&
+ _wcsicmp(sender_normalized_str.c_str() + sender_normalized_str.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ sender_normalized_str.resize(sender_normalized_str.length() - current_suffix.length());
+ }
+
+ // Get our executable directory for comparison
+ std::wstring our_wide = ll_convert<std::wstring>(gDirUtilp->getExecutableDir());
+
+ // Normalize our path
+ wchar_t our_normalized[MAX_PATH];
+ file_part = nullptr;
+
+ DWORD result2 = GetFullPathNameW(our_wide.c_str(), MAX_PATH, our_normalized, &file_part);
+
+ if (result2 == 0 || result2 >= MAX_PATH)
+ {
+ LL_WARNS("Window") << "Failed to normalize our executable path" << LL_ENDL;
+ break;
+ }
+
+ // Remove trailing backslash
+ size_t our_len = wcslen(our_normalized);
+ if (our_len > 0 && our_normalized[our_len - 1] == L'\\')
+ {
+ our_normalized[our_len - 1] = L'\0';
+ our_len--;
+ }
+
+ // Remove "\current" suffix from our path if present
+ std::wstring our_normalized_str(our_normalized);
+ if (our_normalized_str.length() >= current_suffix.length() &&
+ _wcsicmp(our_normalized_str.c_str() + our_normalized_str.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ our_normalized_str.resize(our_normalized_str.length() - current_suffix.length());
+ }
+
+ // Compare the normalized base installation paths (case-insensitive)
+ if (_wcsicmp(sender_normalized_str.c_str(), our_normalized_str.c_str()) == 0)
+ {
+ window_imp->post([=]()
+ {
+ LL_INFOS("Window") << "Received valid shutdown request from verified same installation directory" << LL_ENDL;
+ // Check if app needs cleanup or can be closed immediately.
+ if (window_imp->mCallbacks->handleCloseRequest(window_imp, false))
+ {
+ // Get the app to initiate cleanup.
+ window_imp->mCallbacks->handleQuit(window_imp);
+ }
+ });
+ }
+ else
+ {
+ LL_WARNS("Window") << "Rejected shutdown request - sender not from our installation directory. "
+ << "Sender: " << ll_convert_wide_to_string(sender_normalized_str)
+ << " Our: " << ll_convert_wide_to_string(our_normalized_str) << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Window") << "Received invalid WM_POST_UNINSTALL_ message" << LL_ENDL;
+ }
+ break;
+ }
case WM_COMMAND:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_COMMAND");
@@ -3115,6 +3417,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_DPICHANGED:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DPICHANGED");
+ window_imp->mWindowThread->pingWindowTimeout("WM_DPICHANGED");
LPRECT lprc_new_scale;
F32 new_scale = F32(LOWORD(w_param)) / F32(USER_DEFAULT_SCREEN_DPI);
lprc_new_scale = (LPRECT)l_param;
@@ -3135,7 +3438,19 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_DISPLAYCHANGE:
{
- WINDOW_IMP_POST(window_imp->mCallbacks->handleDisplayChanged());
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DISPLAYCHANGE");
+ window_imp->mWindowThread->pingWindowTimeout("WM_DISPLAYCHANGE");
+ window_imp->post([=]() {
+ window_imp->mCallbacks->handleDisplayChanged();
+ // Note: WM_DISPLAYCHANGE was passing to WM_SETFOCUS
+ // which might have been unintended and was messing with zones.
+ // handleFocus was copied over and return 0 added, but
+ // handleFocus might be not needed here.
+ // handleFocus resets mouse, closes popups and keys, which
+ // we probablt should do on 'display change'.
+ window_imp->mCallbacks->handleFocus(window_imp);
+ });
+ return 0;
}
case WM_SETFOCUS:
@@ -3173,6 +3488,9 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_SETTINGCHANGE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETTINGCHANGE");
+ // Can be called on OS user switching
+ LL_INFOS("Window") << "WM_SETTINGCHANGE, with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL;
+ window_imp->mWindowThread->pingWindowTimeout("WM_SETTINGCHANGE");
if (w_param == SPI_SETMOUSEVANISH)
{
if (!SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &window_imp->mMouseVanish, 0))
@@ -5035,6 +5353,13 @@ inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread()
: LL::ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE, false)
{
LL::ThreadPool::start();
+
+ // Set thread name for the window thread
+ // This will make it distinguishable in Visual Studio debugger
+ post([this]()
+ {
+ SetThreadDescription(GetCurrentThread(), L"LLWindowWin32 Thread");
+ });
}
/**
@@ -5216,7 +5541,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
}
// Normally won't exist yet, but in case of re-init, make sure it's cleaned up
- resumeTimeout("WindowThread");
+ resumeTimeout("Window:WindowThread");
while (! getQueue().done())
{
@@ -5227,23 +5552,25 @@ void LLWindowWin32::LLWindowWin32Thread::run()
if (mWindowHandleThrd != 0)
{
- pingTimeout("messages");
MSG msg;
BOOL status;
if (mhDCThrd == 0)
{
+ pingTimeout("Window:PeekMessage");
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - PeekMessage");
logger.onChange("PeekMessage(", std::hex, mWindowHandleThrd, ")");
status = PeekMessage(&msg, mWindowHandleThrd, 0, 0, PM_REMOVE);
}
else
{
+ pingTimeout("Window:GetMessage");
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - GetMessage");
logger.always("GetMessage(", std::hex, mWindowHandleThrd, ")");
status = GetMessage(&msg, NULL, 0, 0);
}
if (status > 0)
{
+ pingTimeout("Window:TranslateMessage");
logger.always("got MSG (", std::hex, msg.hwnd, ", ", msg.message,
", ", msg.wParam, ")");
TranslateMessage(&msg);
@@ -5255,7 +5582,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue");
- pingTimeout("queue");
+ pingTimeout("Window:Queue");
logger.onChange("runPending()");
//process any pending functions
getQueue().runPending();
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index c0a1816cf3..0b104c6da8 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -40,6 +40,12 @@
// Hack for async host by name
#define LL_WM_HOST_RESOLVED (WM_APP + 1)
+// For requesting shutdown on uninstall,
+// make sure it does not conflict with messages like WM_DUMMY_
+inline constexpr UINT WM_POST_UNINSTALL_ = WM_USER + 0x0019;
+inline constexpr DWORD WM_POST_UNINSTALL_MSG_SHUTDOWN = 1;
+inline constexpr DWORD WM_POST_UNINSTALL_MSG_UPDATE = 2;
+
typedef void (*LLW32MsgCallback)(const MSG &msg);
class LLWindowWin32 : public LLWindow
@@ -234,6 +240,7 @@ protected:
LPWSTR mIconResource;
LPWSTR mIconSmallResource;
bool mInputProcessingPaused;
+ bool mReceivedSCClose; // received SC_CLOSE and expecting WM_CLOSE
// The following variables are for Language Text Input control.
// They are all static, since one context is shared by all LLWindowWin32
diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp
index df6be2546d..84d03a6d0c 100644
--- a/indra/llxml/llcontrol.cpp
+++ b/indra/llxml/llcontrol.cpp
@@ -399,6 +399,7 @@ static bool compareRoutine(settings_pair_t lhs, settings_pair_t rhs)
void LLControlGroup::cleanup()
{
+ LL_PROFILE_ZONE_SCOPED;
if(mSettingsProfile && getCount.size() != 0)
{
std::string file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, SETTINGS_PROFILE);
@@ -959,6 +960,7 @@ U32 LLControlGroup::loadFromFileLegacy(const std::string& filename, bool require
U32 LLControlGroup::saveToFile(const std::string& filename, bool nondefault_only)
{
+ LL_PROFILE_ZONE_SCOPED;
LLSD settings;
int num_saved = 0;
for (ctrl_name_table_t::iterator iter = mNameTable.begin();
diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h
index 5aa2b9715e..ad283c89b1 100644
--- a/indra/llxml/llcontrol.h
+++ b/indra/llxml/llcontrol.h
@@ -321,7 +321,7 @@ public:
{
if(!group.controlExists(name))
{
- LL_ERRS() << "Control named " << name << "not found." << LL_ENDL;
+ LL_ERRS() << "Control named " << name << " not found." << LL_ENDL;
}
bindToControl(group, name);
diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp
index 5693420bb6..3f143cb9ac 100644
--- a/indra/media_plugins/cef/media_plugin_cef.cpp
+++ b/indra/media_plugins/cef/media_plugin_cef.cpp
@@ -116,6 +116,7 @@ private:
std::string mRootCachePath;
std::string mCefLogFile;
bool mCefLogVerbose;
+ U32 mCefRemoteDebuggingPort;
std::vector<std::string> mPickedFiles;
VolumeCatcher mVolumeCatcher;
F32 mCurVolume;
@@ -157,6 +158,7 @@ MediaPluginBase(host_send_func, host_user_data)
mCanSelectAll = false;
mCefLogFile = "";
mCefLogVerbose = false;
+ mCefRemoteDebuggingPort = 0;
mPickedFiles.clear();
mCurVolume = 0.0;
@@ -709,6 +711,8 @@ void MediaPluginCEF::receiveMessage(const char* message_string)
settings.webgl_enabled = true;
settings.log_file = mCefLogFile;
settings.log_verbose = mCefLogVerbose;
+ settings.enable_remote_debug = (mCefRemoteDebuggingPort != 0);
+ settings.remote_debugging_port = mCefRemoteDebuggingPort;
settings.autoplay_without_gesture = true;
std::vector<std::string> custom_schemes(1, "secondlife");
@@ -773,6 +777,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string)
mCefLogFile = message_in.getValue("cef_log_file");
mCefLogVerbose = message_in.getValueBoolean("cef_verbose_log");
+ mCefRemoteDebuggingPort = message_in.getValueU32("cef_remote_debugging_port");
}
else if (message_name == "size_change")
{
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 362199b1ca..7aa255c5da 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -233,6 +233,7 @@ set(viewer_SOURCE_FILES
llfloaterfonttest.cpp
llfloaterforgetuser.cpp
llfloatergesture.cpp
+ llfloatergestureautocompletepicker.cpp
llfloatergltfasseteditor.cpp
llfloatergodtools.cpp
llfloatergotoline.cpp
@@ -243,7 +244,6 @@ set(viewer_SOURCE_FILES
llfloaterhandler.cpp
llfloaterhelpbrowser.cpp
llfloaterhoverheight.cpp
- llfloaterhowto.cpp
llfloaterhud.cpp
llfloaterimagepreview.cpp
llfloaterimsessiontab.cpp
@@ -917,6 +917,7 @@ set(viewer_HEADER_FILES
llfloaterfonttest.h
llfloaterforgetuser.h
llfloatergesture.h
+ llfloatergestureautocompletepicker.h
llfloatergltfasseteditor.h
llfloatergodtools.h
llfloatergotoline.h
@@ -927,7 +928,6 @@ set(viewer_HEADER_FILES
llfloaterhandler.h
llfloaterhelpbrowser.h
llfloaterhoverheight.h
- llfloaterhowto.h
llfloaterhud.h
llfloaterimagepreview.h
llfloaterimnearbychat.h
@@ -1498,6 +1498,37 @@ if (DARWIN)
list(APPEND viewer_SOURCE_FILES ${viewer_RESOURCE_FILES})
endif (DARWIN)
+# Platform-specific files for IDE visibility (excluded from build on other platforms)
+set(macosx_viewer_SOURCE_FILES
+ llappviewermacosx.cpp
+ llappviewermacosx-objc.mm
+ llfilepicker_mac.mm
+ llappdelegate-objc.mm
+ )
+
+set(macosx_viewer_HEADER_FILES
+ llappviewermacosx.h
+ llappviewermacosx-objc.h
+ llfilepicker_mac.h
+ )
+
+set(linux_viewer_SOURCE_FILES
+ llappviewerlinux.cpp
+ )
+
+set(linux_viewer_HEADER_FILES
+ )
+
+set(windows_viewer_SOURCE_FILES
+ llappviewerwin32.cpp
+ llwindebug.cpp
+ )
+
+set(windows_viewer_HEADER_FILES
+ llappviewerwin32.h
+ llwindebug.h
+ )
+
if (LINUX)
LIST(APPEND viewer_SOURCE_FILES llappviewerlinux.cpp)
endif (LINUX)
@@ -1646,6 +1677,45 @@ if (WINDOWS)
endif (WINDOWS)
+# Add all platform-specific files to the target for IDE visibility
+# Collect non-platform files for IDE visibility only
+set(ide_visibility_files "")
+if (NOT DARWIN)
+ list(APPEND ide_visibility_files ${macosx_viewer_SOURCE_FILES} ${macosx_viewer_HEADER_FILES})
+endif()
+if (NOT LINUX)
+ list(APPEND ide_visibility_files ${linux_viewer_SOURCE_FILES} ${linux_viewer_HEADER_FILES})
+endif()
+if (NOT WINDOWS)
+ list(APPEND ide_visibility_files ${windows_viewer_SOURCE_FILES} ${windows_viewer_HEADER_FILES})
+endif()
+
+list(APPEND viewer_SOURCE_FILES ${ide_visibility_files})
+
+# Mark non-platform files as excluded from build
+if (WINDOWS)
+ set_source_files_properties(
+ ${macosx_viewer_SOURCE_FILES}
+ ${linux_viewer_SOURCE_FILES}
+ PROPERTIES
+ HEADER_FILE_ONLY TRUE
+ )
+elseif (DARWIN)
+ set_source_files_properties(
+ ${windows_viewer_SOURCE_FILES}
+ ${linux_viewer_SOURCE_FILES}
+ PROPERTIES
+ HEADER_FILE_ONLY TRUE
+ )
+elseif (LINUX)
+ set_source_files_properties(
+ ${macosx_viewer_SOURCE_FILES}
+ ${windows_viewer_SOURCE_FILES}
+ PROPERTIES
+ HEADER_FILE_ONLY TRUE
+ )
+endif()
+
# Add the xui files. This is handy for searching for xui elements
# from within the IDE.
file(GLOB_RECURSE viewer_XUI_FILES LIST_DIRECTORIES FALSE
@@ -2463,4 +2533,3 @@ if (LL_TESTS)
endif (LL_TESTS)
check_message_template(${VIEWER_BINARY_NAME})
-
diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist
index 09b0e1ec1b..506b2b8a60 100644
--- a/indra/newview/Info-SecondLife.plist
+++ b/indra/newview/Info-SecondLife.plist
@@ -34,6 +34,8 @@
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSMicrophoneUsageDescription</key>
<string>For voice chat, you must grant permission for Second Life to use the microphone.</string>
+ <key>NSLocalNetworkUsageDescription</key>
+ <string>Second Life uses WebRTC for voice chat, which may require local network access while establishing the voice connection.</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml
index 7bcfecf9fa..952818144d 100644
--- a/indra/newview/app_settings/commands.xml
+++ b/indra/newview/app_settings/commands.xml
@@ -83,16 +83,6 @@
is_running_function="Floater.IsOpen"
is_running_parameters="gestures"
/>
- <command name="howto"
- available_in_toybox="true"
- icon="Command_HowTo_Icon"
- label_ref="Command_HowTo_Label"
- tooltip_ref="Command_HowTo_Tooltip"
- execute_function="Floater.ToggleOrBringToFront"
- execute_parameters="guidebook"
- is_running_function="Floater.IsOpen"
- is_running_parameters="guidebook"
- />
<command name="inventory"
available_in_toybox="true"
icon="Command_Inventory_Icon"
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 4f7826adb8..b23acc6bd1 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -492,7 +492,7 @@
<key>RecentJumpThresholdSecs</key>
<map>
<key>Comment</key>
- <string>Seconds after a jump input during which finish-anim is suppressed to avoid interrupting rapid successive jumps.</string>
+ <string>Seconds after jump input during which landing finish-anim is suppressed to avoid interrupting rapid successive jumps.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
@@ -3445,17 +3445,6 @@
<key>Value</key>
<string>https://viewer-help.secondlife.com/[LANGUAGE]/[CHANNEL]/[VERSION]/[TOPIC][DEBUG_MODE]</string>
</map>
- <key>HowToHelpURL</key>
- <map>
- <key>Comment</key>
- <string>URL for How To help content</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>String</string>
- <key>Value</key>
- <string>https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/howto/index.html</string>
- </map>
<key>HomeSidePanelURL</key>
<map>
<key>Comment</key>
@@ -3478,17 +3467,6 @@
<key>Value</key>
<string>https://search.[GRID]/viewer/?query_term=[QUERY]&amp;search_type=[TYPE][COLLECTION]&amp;maturity=[MATURITY]&amp;lang=[LANGUAGE]&amp;g=[GODLIKE]&amp;sid=[SESSION_ID]&amp;rid=[REGION_ID]&amp;pid=[PARCEL_ID]&amp;channel=[CHANNEL]&amp;version=[VERSION]&amp;major=[VERSION_MAJOR]&amp;minor=[VERSION_MINOR]&amp;patch=[VERSION_PATCH]&amp;build=[VERSION_BUILD]</string>
</map>
- <key>GuidebookURL</key>
- <map>
- <key>Comment</key>
- <string>URL for Guidebook content</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>String</string>
- <key>Value</key>
- <string>http://guidebooks.secondlife.io/welcome/index.html</string>
- </map>
<key>HighResSnapshot</key>
<map>
<key>Comment</key>
@@ -9096,6 +9074,17 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>NametagOverWater</key>
+ <map>
+ <key>Comment</key>
+ <string>Render name tag over the transparent water while camera is above the water</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
<key>RenderInitError</key>
<map>
<key>Comment</key>
@@ -9937,6 +9926,17 @@
<key>Value</key>
<real>1</real>
</map>
+ <key>CollectUIImageVertexBuffers</key>
+ <map>
+ <key>Comment</key>
+ <string>When enabled, images will cache vertex buffers and reuse them. When disabled, the general cache will be used with significant hash-lookup overhead, but vertices are regenerated each frame so they are always up to date.</string>
+ <key>Persist</key>
+ <integer>0</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <real>1</real>
+ </map>
<key>ShowMyComplexityChanges</key>
<map>
<key>Comment</key>
@@ -13328,6 +13328,17 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>SwitchToSharedEnvAfterTeleport</key>
+ <map>
+ <key>Comment</key>
+ <string>Switch to Shared Environment after teleport</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
<key>PreferredBrowserBehavior</key>
<map>
<key>Comment</key>
@@ -14499,7 +14510,7 @@
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
- <integer>0</integer>
+ <integer>1</integer>
</map>
<key>OutfitOperationsTimeout</key>
<map>
@@ -14512,6 +14523,17 @@
<key>Value</key>
<integer>180</integer>
</map>
+ <key>OSHibernationMode</key>
+ <map>
+ <key>Comment</key>
+ <string>Whether to prevent OS from hibernating. 0 - can hibernate; 1 - can't hibernate, can turn screen off; 2 - can't hibernate, can't turn screen off </string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>S32</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>HeightUnits</key>
<map>
<key>Comment</key>
@@ -16231,6 +16253,17 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>CEFRemoteDebuggingPort</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable CEF remote debugging on the specified base port (1024 to 65535, 0 to disable). Each new SLPlugin gets base+N.</string>
+ <key>Persist</key>
+ <integer>0</integer>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>360CaptureJPEGEncodeQuality</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl b/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl
index f7505fbfe5..1738cbf9a2 100644
--- a/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl
+++ b/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl
@@ -262,7 +262,7 @@ void alphaMask(float alpha)
#endif
}
-void waterClip()
+void applyWaterClip()
{
#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND)
waterClip(vary_position.xyz);
@@ -284,17 +284,17 @@ float getShadow(vec3 pos, vec3 norm)
#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND)
return sampleDirectionalShadow(pos, norm, vary_texcoord0.xy);
#else
- return 1;
+ return 1.0;
#endif
#else
- return 1;
+ return 1.0;
#endif
}
void main()
{
mirrorClip(vary_position);
- waterClip();
+ applyWaterClip();
// diffcol == diffuse map combined with vertex color
vec4 diffcol = texture(diffuseMap, vary_texcoord0.xy);
@@ -441,5 +441,3 @@ void main()
#endif
}
-
-
diff --git a/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl b/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl
index e8901c7ba2..c9a19f4278 100644
--- a/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl
+++ b/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl
@@ -323,7 +323,7 @@ float tapScreenSpaceReflection(int totalSamples, vec2 tc, vec3 viewPos, vec3 n,
{
#ifdef TRANSPARENT_SURFACE
collectedColor = vec4(1, 0, 1, 1);
- return 0;
+ return 0.0;
#endif
collectedColor = vec4(0);
int hits = 0;
diff --git a/indra/newview/app_settings/toolbars.xml b/indra/newview/app_settings/toolbars.xml
index a1c9d6d9ee..f22c25f3a2 100644
--- a/indra/newview/app_settings/toolbars.xml
+++ b/indra/newview/app_settings/toolbars.xml
@@ -10,7 +10,6 @@
<command name="map"/>
<command name="move"/>
<command name="view"/>
- <command name="howto"/>
</bottom_toolbar>
<left_toolbar
button_display_mode="icons_only">
diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp
index 01c9f63b15..1bdc915bd5 100644
--- a/indra/newview/gltf/llgltfloader.cpp
+++ b/indra/newview/gltf/llgltfloader.cpp
@@ -1398,6 +1398,24 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident);
}
+ // Precompute the source bind pose in viewer coordinate space so each
+ // joint can later be remapped against its own GLTF rest matrix. This
+ // keeps bind-pose deltas local to the joint hierarchy. Without this,
+ // a source skeleton whose parent joints carry bind-pose rotations that
+ // the viewer skeleton does not have can leak those rotations into
+ // child joints with different local bases.
+ joint_node_mat4_map_t rotated_bind_matrices;
+ joint_node_mat4_map_t converted_bind_matrices;
+ if (inverse_count > 0)
+ {
+ for (S32 i = 0; i < joint_count && i < inverse_count; i++)
+ {
+ S32 joint = skin.mJoints[i];
+ glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]);
+ rotated_bind_matrices[joint] = rotateGltfMatrixToViewerSpace(original_bind_matrix);
+ }
+ }
+
for (S32 i = 0; i < joint_count; i++)
{
S32 joint = skin.mJoints[i];
@@ -1430,12 +1448,19 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
}
else if (inverse_count > i)
{
- // Transalte existing bind matrix to viewer's overriden skeleton
- glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]);
- glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix;
- glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name);
- glm::mat4 tranlated_original = skeleton_transform * rotated_original;
- glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original);
+ // Translate existing bind matrices to the viewer skeleton by
+ // preserving each joint's bind-pose delta from its own GLTF rest
+ // matrix. The previous world-space remap applied one skeleton
+ // transform directly to each bind matrix; when a source parent has
+ // bind-pose rotation that the viewer parent does not, that rotation
+ // could get baked into child inverse binds. Keeping the delta local
+ // fixes the resulting child joint twists.
+ glm::mat4 translated_original = computeViewerBindMatrix(
+ joints_data,
+ rotated_bind_matrices,
+ joint,
+ converted_bind_matrices);
+ glm::mat4 final_inverse_bind_matrix = glm::inverse(translated_original);
LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix));
LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL;
@@ -1551,6 +1576,12 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map
glm::quat rotation;
glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective);
+ glm::mat4 viewer_rotation_scale(1.0f);
+ viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[0]), glm::vec3(1, 0, 0));
+ viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[1]), glm::vec3(0, 1, 0));
+ viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[2]), glm::vec3(0, 0, 1));
+ viewer_rotation_scale = glm::scale(viewer_rotation_scale, viewer_data.mScale);
+
// Viewer allows overrides, which are base joint with applied translation override.
// fortunately normal bones use only translation, without rotation or scale
node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity<glm::quat>(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1));
@@ -1566,13 +1597,14 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map
}
else
{
- // This is likely incomplete or even wrong.
- // Viewer Collision bones specify rotation and scale.
- // Importer should apply rotation and scale to this matrix and save as needed
- // then subsctruct them from bind matrix
- // Todo: get models that use collision bones, made by different programs
-
- overriden_joint = glm::scale(overriden_joint, viewer_data.mScale);
+ // Collision volumes need the imported translation override, but
+ // their local rotation-scale basis must come from the raw viewer
+ // skeleton XML values. For non-uniform torso volumes, matching DAE
+ // requires viewer rotation followed by viewer scale.
+ overriden_joint = viewer_rotation_scale;
+ overriden_joint[3][0] = translation_override.x;
+ overriden_joint[3][1] = translation_override.y;
+ overriden_joint[3][2] = translation_override.z;
node.mOverrideRestMatrix = parent_support_rest * overriden_joint;
}
}
@@ -1651,6 +1683,69 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_d
return data.mGltfMatrix;
}
+// Convert a GLTF-space transform into the viewer coordinate space used by
+// skeleton override and bind-pose calculations. Keeping this conversion in
+// one helper ensures bind and rest matrices use the same rotation path,
+// including the optional XY rotation applied for compatible uploads.
+glm::mat4 LLGLTFLoader::rotateGltfMatrixToViewerSpace(const glm::mat4& gltf_matrix) const
+{
+ glm::mat4 rotated = coord_system_rotation * gltf_matrix;
+ if (mApplyXYRotation)
+ {
+ rotated = coord_system_rotationxy * rotated;
+ }
+ return rotated;
+}
+
+// Convert one GLTF bind matrix to the matching viewer bind matrix. The
+// function derives the joint-local bind delta from the GLTF bind and rest
+// matrices, then applies that same delta to the viewer override rest matrix.
+// Results are cached because child calculations can request the same joint
+// more than once while preserving local hierarchy behavior.
+glm::mat4 LLGLTFLoader::computeViewerBindMatrix(
+ const joints_data_map_t& joints_data_map,
+ const joint_node_mat4_map_t& rotated_bind_matrices,
+ S32 gltf_node_index,
+ joint_node_mat4_map_t& converted_bind_matrices) const
+{
+ auto cached = converted_bind_matrices.find(gltf_node_index);
+ if (cached != converted_bind_matrices.end())
+ {
+ return cached->second;
+ }
+
+ auto node_iter = joints_data_map.find(gltf_node_index);
+ if (node_iter == joints_data_map.end())
+ {
+ return glm::mat4(1.f);
+ }
+
+ const JointNodeData& node_data = node_iter->second;
+ if (!node_data.mIsOverrideValid)
+ {
+ // No valid override, falling back to rest matrix.
+ glm::mat4 fallback_bind = rotateGltfMatrixToViewerSpace(node_data.mGltfRestMatrix);
+ converted_bind_matrices[gltf_node_index] = fallback_bind;
+ return fallback_bind;
+ }
+
+ auto bind_iter = rotated_bind_matrices.find(gltf_node_index);
+ if (bind_iter == rotated_bind_matrices.end())
+ {
+ converted_bind_matrices[gltf_node_index] = node_data.mOverrideRestMatrix;
+ return node_data.mOverrideRestMatrix;
+ }
+
+ glm::mat4 gltf_joint_node = rotateGltfMatrixToViewerSpace(node_data.mGltfRestMatrix);
+ glm::mat4 gltf_joint_bind = bind_iter->second;
+ glm::mat4 viewer_joint_node = node_data.mOverrideRestMatrix;
+ glm::mat4 bind_delta = gltf_joint_bind * glm::inverse(gltf_joint_node);
+ glm::mat4 viewer_joint_bind = bind_delta * viewer_joint_node;
+
+ converted_bind_matrices[gltf_node_index] = viewer_joint_bind;
+ return viewer_joint_bind;
+}
+
// This function computes the transformation matrix needed to convert from GLTF skeleton space
// to viewer skeleton space for a specific joint
@@ -1903,4 +1998,3 @@ std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Node& node)
}
return node.mName;
}
-
diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h
index a847e567a6..d8aad79883 100644
--- a/indra/newview/gltf/llgltfloader.h
+++ b/indra/newview/gltf/llgltfloader.h
@@ -160,6 +160,8 @@ private:
void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const;
glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const;
glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const;
+ glm::mat4 rotateGltfMatrixToViewerSpace(const glm::mat4& gltf_matrix) const;
+ glm::mat4 computeViewerBindMatrix(const joints_data_map_t& joints_data_map, const joint_node_mat4_map_t& rotated_bind_matrices, S32 gltf_node_index, joint_node_mat4_map_t& converted_bind_matrices) const;
glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const;
bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx);
void checkForXYrotation(const LL::GLTF::Skin& gltf_skin);
diff --git a/indra/newview/licenses-linux.txt b/indra/newview/licenses-linux.txt
index a199ed410c..167341fc19 100644
--- a/indra/newview/licenses-linux.txt
+++ b/indra/newview/licenses-linux.txt
@@ -632,3 +632,196 @@ supporting the PNG file format in commercial products. If you use this
source code in a product, acknowledgment is not required but would be
appreciated.
+DejaVu Fonts
+MIT License
+
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+@@ -41,17 +42,17 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+@@ -91,9 +92,96 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+Twemoji Fonts
+MIT License
+
+Applies to "EmojiOne SVGinOT Font" code only
+Copyright (c) 2022 Brad Erickson
+
+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.
+
+WebRTC
+
+Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Google fonts
+SIL OPEN FONT LICENSE Version 1.1
+
+Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/indra/newview/licenses-mac.txt b/indra/newview/licenses-mac.txt
index 0bd9ebc5f1..c2f52e3b90 100644
--- a/indra/newview/licenses-mac.txt
+++ b/indra/newview/licenses-mac.txt
@@ -593,3 +593,205 @@ 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.
+==============
+DejaVu Fonts
+==============
+MIT License
+
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+@@ -41,17 +42,17 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+@@ -91,9 +92,96 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+==============
+Twemoji Fonts
+==============
+MIT License
+
+Applies to "EmojiOne SVGinOT Font" code only
+Copyright (c) 2022 Brad Erickson
+
+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.
+
+==============
+WebRTC
+==============
+
+Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+==============
+Google fonts
+==============
+SIL OPEN FONT LICENSE Version 1.1
+
+Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
diff --git a/indra/newview/licenses-win32.txt b/indra/newview/licenses-win32.txt
index b56f1bf2eb..2d4bd8a4b6 100644
--- a/indra/newview/licenses-win32.txt
+++ b/indra/newview/licenses-win32.txt
@@ -667,3 +667,205 @@ 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.
+
+==============
+DejaVu Fonts
+==============
+MIT License
+
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+@@ -41,17 +42,17 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+@@ -91,9 +92,96 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+==============
+Twemoji Fonts
+==============
+MIT License
+
+Applies to "EmojiOne SVGinOT Font" code only
+Copyright (c) 2022 Brad Erickson
+
+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.
+
+==============
+WebRTC
+==============
+
+Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+==============
+Google fonts
+==============
+SIL OPEN FONT LICENSE Version 1.1
+
+Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 3ab87cac13..2f0ad15d18 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -44,6 +44,7 @@
#include "llchicletbar.h"
#include "llconsole.h"
#include "lldonotdisturbnotificationstorage.h"
+#include "llenvironment.h"
#include "llfirstuse.h"
#include "llfloatercamera.h"
#include "llfloaterimcontainer.h"
@@ -1551,6 +1552,8 @@ void LLAgent::setAFK()
setControlFlags(AGENT_CONTROL_AWAY | AGENT_CONTROL_STOP);
gAwayTimer.start();
}
+
+ LLAppViewer::instance()->setPermitOSHibernation(true);
}
//-----------------------------------------------------------------------------
@@ -1569,6 +1572,13 @@ void LLAgent::clearAFK()
sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_STOP);
clearControlFlags(AGENT_CONTROL_AWAY);
}
+
+ if (isAgentAvatarValid())
+ {
+ // Only set this if agent is inworld, login screen
+ // shouldn't prevent hibernation.
+ LLAppViewer::instance()->setPermitOSHibernation(false);
+ }
}
//-----------------------------------------------------------------------------
@@ -2669,17 +2679,20 @@ void LLAgent::onAnimStop(const LLUUID& id)
else if (id == ANIM_AGENT_PRE_JUMP || id == ANIM_AGENT_LAND || id == ANIM_AGENT_MEDIUM_LAND)
{
// FIRE-34049/FIRE-34273/https://github.com/secondlife/viewer/issues/4218
- // Avoid forcing AGENT_CONTROL_FINISH_ANIM, which can short-circuit the next pre-jump
- // during rapid successive jumps.
+ // Avoid forcing AGENT_CONTROL_FINISH_ANIM on landing, which can short-circuit the
+ // next pre-jump during rapid successive jumps.
+ // Do not suppress pre-jump finish, otherwise a quick tap from standing can stall.
// TODO: a more robust fix would require knowing which specific animation finished,
// information that is not currently provided by the simulator.
+ const bool is_landing_anim = (id == ANIM_AGENT_LAND || id == ANIM_AGENT_MEDIUM_LAND);
const bool up_pos = (mControlFlags & AGENT_CONTROL_UP_POS) != 0;
const F64 now = LLTimer::getTotalSeconds();
const F64 elapsed = now - mLastJumpInputTime;
- static LLCachedControl<F32> recent_jump_threshold_secs(gSavedSettings, "RecentJumpThresholdSecs");
+ static LLCachedControl<F32> recent_jump_threshold_secs(gSavedSettings, "RecentJumpThresholdSecs", 1.0);
const bool recent_jump = (mLastJumpInputTime > 0.0) && (elapsed < recent_jump_threshold_secs);
+ const bool suppress_finish = is_landing_anim && recent_jump;
- if (!up_pos && !recent_jump)
+ if (!up_pos && !suppress_finish)
{
setControlFlags(AGENT_CONTROL_FINISH_ANIM);
}
@@ -4138,6 +4151,7 @@ void LLAgent::handleTeleportFinished()
}
clearTeleportRequest();
mTeleportCanceled.reset();
+ LLVOAvatar::resetEarlyAppearanceList();
if (mIsMaturityRatingChangingDuringTeleport)
{
// notify user that the maturity preference has been changed
@@ -4169,6 +4183,11 @@ void LLAgent::handleTeleportFinished()
mRegionp->setCapabilitiesReceivedCallback(boost::bind(&LLAgent::onCapabilitiesReceivedAfterTeleport));
}
}
+ static LLCachedControl<bool> shared_env_on_teleport(gSavedSettings, "SwitchToSharedEnvAfterTeleport", true);
+ if (shared_env_on_teleport)
+ {
+ LLEnvironment::instance().setSharedEnvironment();
+ }
LLPerfStats::tunables.autoTuneTimeout = true;
}
diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp
index a075b6f004..edd08c19e5 100644
--- a/indra/newview/llagentwearables.cpp
+++ b/indra/newview/llagentwearables.cpp
@@ -547,14 +547,23 @@ const S32 LLAgentWearables::getWearableIdxFromItem(const LLViewerInventoryItem*
U32 wearable_count = getWearableCount(type);
if (0 == wearable_count) return -1;
- const LLUUID& asset_id = item->getAssetUUID();
+ // Match by linked inventory item id so the correct copy is found when several
+ // worn wearables of this type share the same asset.
+ const LLUUID item_id = gInventory.getLinkedItemID(item->getUUID());
+ for (U32 i = 0; i < wearable_count; ++i)
+ {
+ const LLViewerWearable* wearable = getViewerWearable(type, i);
+ if (!wearable) continue;
+ if (wearable->getItemID() == item_id) return i;
+ }
+ // Fall back to asset id for items whose inventory id can't be resolved.
+ const LLUUID& asset_id = item->getAssetUUID();
for (U32 i = 0; i < wearable_count; ++i)
{
const LLViewerWearable* wearable = getViewerWearable(type, i);
if (!wearable) continue;
- if (wearable->getAssetID() != asset_id) continue;
- return i;
+ if (wearable->getAssetID() == asset_id) return i;
}
return -1;
@@ -1529,6 +1538,37 @@ bool LLAgentWearables::moveWearable(const LLViewerInventoryItem* item, bool clos
return false;
}
+bool LLAgentWearables::moveWearableToIndex(const LLViewerInventoryItem* item, U32 new_index)
+{
+ if (!item) return false;
+ if (!item->isWearableType()) return false;
+
+ LLWearableType::EType type = item->getWearableType();
+ U32 wearable_count = getWearableCount(type);
+ if (wearable_count < 2) return false;
+
+ if (new_index >= wearable_count) new_index = wearable_count - 1;
+
+ S32 cur = getWearableIdxFromItem(item);
+ if (cur < 0) return false;
+ if ((U32)cur == new_index) return true; // already in place
+
+ // step the wearable to its new slot one swap at a time
+ U32 pos = (U32)cur;
+ while (pos < new_index)
+ {
+ if (!swapWearables(type, pos, pos + 1)) return false;
+ ++pos;
+ }
+ while (pos > new_index)
+ {
+ if (!swapWearables(type, pos, pos - 1)) return false;
+ --pos;
+ }
+
+ return true;
+}
+
// static
void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, const LLUUID& parent_id, std::function<void(const LLUUID&)> created_cb)
{
diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h
index 70da86805c..3c9ffa7443 100644
--- a/indra/newview/llagentwearables.h
+++ b/indra/newview/llagentwearables.h
@@ -129,6 +129,7 @@ public:
static void createWearable(LLWearableType::EType type, bool wear = false, const LLUUID& parent_id = LLUUID::null, std::function<void(const LLUUID&)> created_cb = nullptr);
static void editWearable(const LLUUID& item_id);
bool moveWearable(const LLViewerInventoryItem* item, bool closer_to_body);
+ bool moveWearableToIndex(const LLViewerInventoryItem* item, U32 new_index);
void requestEditingWearable(const LLUUID& item_id);
void editWearableIfRequested(const LLUUID& item_id);
diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp
index f67f2688a1..f0b7457fa2 100644
--- a/indra/newview/llaisapi.cpp
+++ b/indra/newview/llaisapi.cpp
@@ -1039,6 +1039,19 @@ AISUpdate::AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD&
mFetchDepth = request_body["depth"].asInteger();
}
+ // Some tasks are time sensitive, don't wait for them.
+ // Like FETCHCOF which happens on load and is needed for outfit
+ // loading.
+ // FETCHCATEGORYLINKS is used for wearing outfits and fetching
+ // a user selected outfit or for following an outfit link in COF.
+ // Other tasks, like FETCHCATEGORYSUBSET are general background
+ // fetches and can safely wait.
+ // Do count 'time sensitive' tasks in batch timer.
+ mUseTimeout =
+ type != AISAPI::UPDATECATEGORY
+ && type != AISAPI::UPDATEITEM
+ && type != AISAPI::FETCHCOF
+ && type != AISAPI::FETCHCATEGORYLINKS;
mTaskTimer.setTimerExpirySec(AIS_TASK_EXPIRY_SECONDS);
mTaskTimer.start();
@@ -1069,6 +1082,11 @@ void AISUpdate::clearParseResults()
void AISUpdate::checkTimeout()
{
+ if (!mUseTimeout)
+ {
+ // Priority task, don't wait.
+ return;
+ }
if (mTaskTimer.hasExpired() || sBatchTimer.hasExpired())
{
// If we are taking too long, don't starve other tasks,
@@ -1758,35 +1776,41 @@ void AISUpdate::doUpdate()
const LLUUID id = ucv_it->first;
S32 version = static_cast<S32>(ucv_it->second);
LLViewerInventoryCategory *cat = gInventory.getCategory(id);
- LL_DEBUGS("Inventory") << "cat version update " << cat->getName() << " to version " << cat->getVersion() << LL_ENDL;
- if (cat->getVersion() != version)
+ // Update can be rather large and take time to process.
+ // By the time update gets to the category, it could
+ // could have been removed by the user
+ if (cat)
{
- // the AIS version should be considered the true version. Adjust
- // our local category model to reflect this version number. Otherwise
- // it becomes possible to get stuck with the viewer being out of
- // sync with the inventory system. Under normal circumstances
- // inventory COF is maintained on the viewer through calls to
- // LLInventoryModel::accountForUpdate when a changing operation
- // is performed. This occasionally gets out of sync however.
- if (version != LLViewerInventoryCategory::VERSION_UNKNOWN)
- {
- LL_WARNS() << "Possible version mismatch for category " << cat->getName()
- << ", viewer version " << cat->getVersion()
- << " AIS version " << version << " !!!Adjusting local version!!!" << LL_ENDL;
- cat->setVersion(version);
- }
- else
+ LL_DEBUGS("Inventory") << "cat " << cat->getName() << " version update from " << cat->getVersion() << " to AIS version " << version << LL_ENDL;
+ if (cat->getVersion() != version)
{
- // We do not account for update if version is UNKNOWN, so we shouldn't rise version
- // either or viewer will get stuck on descendants count -1, try to refetch folder instead
- //
- // Todo: proper backoff?
+ // the AIS version should be considered the true version. Adjust
+ // our local category model to reflect this version number. Otherwise
+ // it becomes possible to get stuck with the viewer being out of
+ // sync with the inventory system. Under normal circumstances
+ // inventory COF is maintained on the viewer through calls to
+ // LLInventoryModel::accountForUpdate when a changing operation
+ // is performed. This occasionally gets out of sync however.
+ if (version != LLViewerInventoryCategory::VERSION_UNKNOWN)
+ {
+ LL_WARNS() << "Possible version mismatch for category " << cat->getName()
+ << ", viewer version " << cat->getVersion()
+ << " AIS version " << version << " !!!Adjusting local version!!!" << LL_ENDL;
+ cat->setVersion(version);
+ }
+ else
+ {
+ // We do not account for update if version is UNKNOWN, so we shouldn't riase version
+ // either or viewer will get stuck on descendants count -1, try to refetch folder instead
+ //
+ // Todo: proper backoff?
- LL_WARNS() << "Possible version mismatch for category " << cat->getName()
- << ", viewer version " << cat->getVersion()
- << " AIS version " << version << " !!!Rerequesting category!!!" << LL_ENDL;
- const S32 LONG_EXPIRY = 360;
- cat->fetch(LONG_EXPIRY);
+ LL_WARNS() << "Possible version mismatch for category " << cat->getName()
+ << ", viewer version " << cat->getVersion()
+ << " AIS version " << version << " !!!Rerequesting category!!!" << LL_ENDL;
+ const S32 LONG_EXPIRY = 360;
+ cat->fetch(LONG_EXPIRY);
+ }
}
}
}
diff --git a/indra/newview/llaisapi.h b/indra/newview/llaisapi.h
index 1dab0dd1f9..b31768d426 100644
--- a/indra/newview/llaisapi.h
+++ b/indra/newview/llaisapi.h
@@ -109,6 +109,7 @@ private:
class AISUpdate
{
+ LOG_CLASS(AISUpdate);
public:
AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body);
void parseUpdate(const LLSD& update);
@@ -158,6 +159,7 @@ private:
uuid_list_t mCategoryIds;
bool mFetch;
S32 mFetchDepth;
+ bool mUseTimeout;
LLTimer mTaskTimer;
static LLTimer sBatchTimer;
static U32 sBatchFrameCount;
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index 8aa28e1b09..80a3344979 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -980,7 +980,9 @@ void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, L
if (!holder->isMostRecent())
{
LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
- // runway skip here?
+
+ // If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves
+ return;
}
LL_INFOS("Avatar") << "HP " << holder->index() << " recovered item link for type " << type << LL_ENDL;
@@ -1020,7 +1022,6 @@ void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLView
{
if (!holder->isMostRecent())
{
- // runway skip here?
LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
// If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves
@@ -4268,77 +4269,110 @@ bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_b
{
if (!item || !item->isWearableType()) return false;
if (item->getType() != LLAssetType::AT_CLOTHING) return false;
- if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false;
S32 pos = gAgentWearables.getWearableIdxFromItem(item);
if (pos < 0) return false; // Not found
+ if (closer_to_body && pos == 0) return false; // already closest to the body
- U32 count = gAgentWearables.getWearableCount(item->getWearableType());
- if (count < 2) return false; // Nothing to swap with
- if (closer_to_body)
+ return reorderWearable(item, closer_to_body ? (U32)(pos - 1) : (U32)(pos + 1));
+}
+
+bool LLAppearanceMgr::reorderWearable(LLViewerInventoryItem* item, U32 new_index)
+{
+ if (!item || !item->isWearableType()) return false;
+ if (item->getType() != LLAssetType::AT_CLOTHING) return false;
+ if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false;
+
+ LLWearableType::EType type = item->getWearableType();
+ U32 count = gAgentWearables.getWearableCount(type);
+ if (count < 2) return false; // nothing to reorder against
+
+ if (new_index >= count) new_index = count - 1;
+
+ S32 cur = gAgentWearables.getWearableIdxFromItem(item);
+ if (cur < 0) return false;
+ if ((U32)cur == new_index) return false; // already in place
+
+ // Update the live layer order first; bail before touching inventory if it fails.
+ if (!gAgentWearables.moveWearableToIndex(item, new_index)) return false;
+
+ persistWearableOrder(type);
+ return true;
+}
+
+bool LLAppearanceMgr::reorderWearableGroup(LLWearableType::EType type, const uuid_vec_t& ordered_link_ids)
+{
+ U32 count = gAgentWearables.getWearableCount(type);
+ if (count < 2) return false;
+ if (ordered_link_ids.size() != count) return false; // order must cover the whole group
+
+ // Validate everything before mutating, so a bad link can't leave it half-reordered.
+ for (const LLUUID& link_id : ordered_link_ids)
{
- if (pos == 0) return false; // already first
+ LLViewerInventoryItem* link = gInventory.getItem(link_id);
+ if (!link || link->getWearableType() != type) return false;
}
- else
+
+ // ordered_link_ids runs furthest-to-closest; body index 0 is closest to the body.
+ // Place each target at its body index, leaving already-placed lower indices untouched.
+ for (U32 body_index = 0; body_index < count; ++body_index)
{
- if (pos == count - 1) return false; // already last
+ const LLUUID& link_id = ordered_link_ids[count - 1 - body_index];
+ LLViewerInventoryItem* link = gInventory.getItem(link_id);
+ if (!link || link->getWearableType() != type) return false;
+ if (!gAgentWearables.moveWearableToIndex(link, body_index)) return false;
}
- U32 old_pos = (U32)pos;
- U32 swap_with = closer_to_body ? old_pos - 1 : old_pos + 1;
- LLUUID swap_item_id = gAgentWearables.getWearableItemID(item->getWearableType(), swap_with);
+ persistWearableOrder(type);
+ return true;
+}
+
+void LLAppearanceMgr::persistWearableOrder(LLWearableType::EType type)
+{
+ U32 count = gAgentWearables.getWearableCount(type);
- // Find link item from item id.
+ // Rewrite the sort-index descriptions for the whole type group in one pass so
+ // the order survives relog, trusting gAgentWearables over existing descriptions.
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
- LLFindWearablesOfType filter_wearables_of_type(item->getWearableType());
+ LLFindWearablesOfType filter_wearables_of_type(type);
gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type);
- if (items.empty()) return false;
- LLViewerInventoryItem* swap_item = nullptr;
- for (auto iter : items)
+ for (U32 i = 0; i < count; ++i)
{
- if (iter->getLinkedUUID() == swap_item_id)
+ LLUUID linked_id = gAgentWearables.getWearableItemID(type, i);
+ if (linked_id.isNull()) continue;
+
+ LLViewerInventoryItem* link = nullptr;
+ for (auto iter : items)
{
- swap_item = iter.get();
- break;
+ if (iter->getLinkedUUID() == linked_id)
+ {
+ link = iter.get();
+ break;
+ }
}
- }
- if (!swap_item)
- {
- return false;
- }
-
- // Description is supposed to hold sort index, but user could have changed
- // order rapidly and there might be a state mismatch between description
- // and gAgentWearables, trust gAgentWearables over description.
- // Generate new description.
- std::string new_desc = build_order_string(item->getWearableType(), old_pos);
- swap_item->setDescription(new_desc);
- new_desc = build_order_string(item->getWearableType(), swap_with);
- item->setDescription(new_desc);
+ if (!link) continue;
- item->setComplete(true);
- item->updateServer(false);
- gInventory.updateItem(item);
+ std::string new_desc = build_order_string(type, i);
+ if (new_desc == link->getActualDescription()) continue;
- swap_item->setComplete(true);
- swap_item->updateServer(false);
- gInventory.updateItem(swap_item);
+ // Keep the local cache consistent immediately (so the COF list does not
+ // flicker back on a refresh), and persist durably via AISv3, matching
+ // updateClothingOrderingInfo() rather than the legacy UDP updateServer().
+ link->setDescription(new_desc);
+ LLSD updates;
+ updates["desc"] = new_desc;
+ update_inventory_item(link->getUUID(), updates, NULL);
+ }
- //to cause appearance of the agent to be updated
- bool result = false;
- if ((result = gAgentWearables.moveWearable(item, closer_to_body)))
+ if (isAgentAvatarValid())
{
- gAgentAvatarp->wearableUpdated(item->getWearableType());
+ gAgentAvatarp->wearableUpdated(type);
}
setOutfitDirty(true);
-
- //*TODO do we need to notify observers here in such a way?
gInventory.notifyObservers();
-
- return result;
}
//static
diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h
index 9d90be0004..77146ba0be 100644
--- a/indra/newview/llappearancemgr.h
+++ b/indra/newview/llappearancemgr.h
@@ -210,6 +210,15 @@ public:
bool moveWearable(LLViewerInventoryItem* item, bool closer_to_body);
+ // Move a clothing item to an absolute layer index within its wearable type
+ // (0 == closest to the body). Persists the new order to the COF link descriptions.
+ bool reorderWearable(LLViewerInventoryItem* item, U32 new_index);
+
+ // Apply a complete layer order for one wearable type, persisting it to the
+ // COF link descriptions. ordered_link_ids lists the type's COF link items
+ // furthest-to-closest.
+ bool reorderWearableGroup(LLWearableType::EType type, const uuid_vec_t& ordered_link_ids);
+
static void sortItemsByActualDescription(LLInventoryModel::item_array_t& items);
//Divvy items into arrays by wearable type
@@ -250,6 +259,10 @@ private:
private:
+ // Rewrite COF sort-index descriptions for one wearable type to match the
+ // current in-memory layer order, then trigger a single appearance update.
+ void persistWearableOrder(LLWearableType::EType type);
+
void filterWearableItems(LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total, bool skip_bodyparts = false);
void getDescendentsOfAssetType(const LLUUID& category,
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 2519fbffbb..03c8d51168 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -66,6 +66,7 @@
#include "llconversationlog.h"
#if LL_WINDOWS
#include "lldxhardware.h"
+#include <shellapi.h>
#endif
#include "lltexturestats.h"
#include "lltrace.h"
@@ -382,6 +383,8 @@ const std::string START_MARKER_FILE_NAME("SecondLife.start_marker");
const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker");
const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker");
const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker");
+const std::string INITED_MARKER_FILE_NAME("SecondLife.inited_marker");
+const std::string CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker");
static std::string gLaunchFileOnQuit;
//----------------------------------------------------------------------------
@@ -632,6 +635,12 @@ bool LLAppViewer::sendURLToOtherInstance(const std::string& url)
return false;
}
+//virtual
+void LLAppViewer::setOSHibernationMode(eHibernationMode mode)
+{
+ // See OS specific files
+}
+
//----------------------------------------------------------------------------
// LLAppViewer definition
@@ -690,6 +699,12 @@ LLAppViewer::LLAppViewer()
gLoggedInTime.stop();
+ // Locking this early is needed to prevent multiple instances and
+ // to log, but it also means that early paths such as SLURL handling
+ // can invoke processMarkerFiles() and potentially clear markers
+ // from previous runs before those stats are reported.
+ // Todo: improve this. Perhaps store stats 'permanently' to be reported
+ // on next login and only login cleans stats up?
processMarkerFiles();
//
// OK to write stuff to logs now, we've now crash reported if necessary
@@ -842,6 +857,8 @@ bool LLAppViewer::init()
// that use findSkinnedFilenames(), will include the localized files.
gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), LLUI::getLanguage());
+ loadLocalizedSettingsComments();
+
// Setup LLTrans after LLUI::initClass has been called.
initStrings();
@@ -940,6 +957,16 @@ bool LLAppViewer::init()
LL_WARNS("InitInfo") << "initHardwareTest() failed." << LL_ENDL;
// quit immediately
LL_PROFILER_FRAME_END;
+ LLSingletonBase::deleteAll();
+ cleanupConsole();
+ delete mSettingsLocationList;
+ if (!mSecondInstance)
+ {
+ // Stats from previous session will likely be lost, but this should
+ // be fine as this is likely first run for this version.
+ // Todo: Might be smarter to have an exit code for a cleaner shutdown
+ removeMarkerFiles();
+ }
return false;
}
LL_INFOS("InitInfo") << "Hardware test initialization done." << LL_ENDL ;
@@ -1120,17 +1147,17 @@ bool LLAppViewer::init()
gGLActive = false;
-//#if LL_RELEASE_FOR_DOWNLOAD
- // Launch VVM update check
+#if !LL_LINUX
+ // Launch VVM update check (Linux updater is future work: Velopack port)
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive)
{
initVVMUpdateCheck();
}
else
+#endif
{
LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL;
}
-//#endif //LL_RELEASE_FOR_DOWNLOAD
{
// Iterate over --leap command-line options. But this is a bit tricky: if
@@ -1315,6 +1342,8 @@ bool LLAppViewer::doFrame()
}
#endif
+ LL_RECORD_BLOCK_TIME(FTM_FRAME);
+ LL_PROFILE_GPU_ZONE("Frame");
{
// and now adjust the visuals from previous frame.
if(LLPerfStats::tunables.userAutoTuneEnabled && LLPerfStats::tunables.tuningFlag != LLPerfStats::Tunables::Nothing)
@@ -1726,7 +1755,15 @@ bool LLAppViewer::cleanup()
// Give any remaining SLPlugin instances a chance to exit cleanly.
LLPluginProcessParent::shutdown();
+ if (LLWatchdog::instanceExists())
+ {
+ // Signal a stop early, so that it will be out
+ // of 1s sleep loop by the time we get to clean it.
+ LLWatchdog::getInstance()->shutdown();
+ }
+
disconnectViewer();
+
LLViewerCamera::deleteSingleton();
LL_INFOS() << "Viewer disconnected" << LL_ENDL;
@@ -2028,7 +2065,7 @@ bool LLAppViewer::cleanup()
}
LLSplashScreen::show();
- LLSplashScreen::update(LLTrans::getString("ShuttingDown"));
+ LLSplashScreen::update(LLTrans::getString("ShutdownCleanup"));
LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL;
@@ -2057,7 +2094,7 @@ bool LLAppViewer::cleanup()
if (sTextureFetch)
{
sTextureFetch->shutdown();
- sTextureFetch->waitOnPending();
+ sTextureFetch->waitOnPending(10.f);
delete sTextureFetch;
sTextureFetch = NULL;
}
@@ -2102,7 +2139,10 @@ bool LLAppViewer::cleanup()
gSavedSettings.cleanup();
LLUIColorTable::instance().clear();
- LLWatchdog::getInstance()->cleanup();
+ if (LLWatchdog::instanceExists())
+ {
+ LLWatchdog::getInstance()->cleanup();
+ }
LLViewerAssetStatsFF::cleanup();
@@ -2127,6 +2167,10 @@ bool LLAppViewer::cleanup()
SUBSYSTEM_CLEANUP(LLProxy);
LLCore::LLHttp::cleanup();
+ LLSplashScreen::update(LLTrans::getString("CompressingInventoryCache"));
+ LLInventoryModel::waitForPendingCacheWrites();
+ LLSplashScreen::update(LLTrans::getString("ShuttingDown"));
+
ll_close_fail_log();
LLError::LLCallStacks::cleanup();
@@ -2138,6 +2182,7 @@ bool LLAppViewer::cleanup()
LLWorld::deleteSingleton();
LLVoiceClient::deleteSingleton();
LLUI::deleteSingleton();
+ LLWatchdog::deleteSingleton();
// It's not at first obvious where, in this long sequence, a generic cleanup
// call OUGHT to go. So let's say this: as we migrate cleanup from
@@ -2282,6 +2327,9 @@ void errorHandler(const std::string& title_string, const std::string& message_st
case LLError::LLUserWarningMsg::ERROR_MISSING_FILES:
LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES);
break;
+ case LLError::LLUserWarningMsg::ERROR_INIT_FAILED:
+ LLAppViewer::instance()->createErrorMarker(LAST_EXEC_INIT);
+ break;
default:
break;
}
@@ -2306,6 +2354,12 @@ void errorHandler(const std::string& title_string, const std::string& message_st
}
}
+namespace
+{
+ std::string getStartupLogFileName();
+ std::string getOldLogFileName(const std::string& log_file);
+}
+
void LLAppViewer::initLoggingAndGetLastDuration()
{
//
@@ -2331,13 +2385,10 @@ void LLAppViewer::initLoggingAndGetLastDuration()
else
{
// Remove the last ".old" log file.
- std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
- "SecondLife.old");
+ std::string log_file = getStartupLogFileName();
+ std::string old_log_file = getOldLogFileName(log_file);
LLFile::remove(old_log_file);
- // Get name of the log file
- std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
- "SecondLife.log");
/*
* Before touching any log files, compute the duration of the last run
* by comparing the ctime of the previous start marker file with the ctime
@@ -2384,7 +2435,7 @@ void LLAppViewer::initLoggingAndGetLastDuration()
// Rename current log file to ".old"
LLFile::rename(log_file, old_log_file);
- // Set the log file to SecondLife.log
+ // Set the log file.
LLError::logToFile(log_file);
LL_INFOS() << "Started logging to " << log_file << LL_ENDL;
if (!duration_log_msg.empty())
@@ -2523,6 +2574,76 @@ namespace
LLStringUtil::null,
OSMB_OK);
}
+
+ std::string getStartupLogFileName()
+ {
+ if (LLControlVariable* user_log_file = gSavedSettings.getControl("UserLogFile"))
+ {
+ std::string log_file = user_log_file->getValue().asString();
+ if (!log_file.empty())
+ {
+ return log_file;
+ }
+ }
+
+#if LL_WINDOWS
+ int argc = 0;
+ LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (argv)
+ {
+ std::string log_file;
+ for (int i = 1; i < argc; ++i)
+ {
+ std::string option = ll_convert_wide_to_string(argv[i]);
+ if ((option == "--logfile" || option == "-logfile" || option == "/logfile") &&
+ i + 1 < argc)
+ {
+ log_file = ll_convert_wide_to_string(argv[i + 1]);
+ }
+ else if (option.compare(0, 10, "--logfile=") == 0)
+ {
+ log_file = option.substr(10);
+ }
+ else if (option.compare(0, 9, "-logfile=") == 0)
+ {
+ log_file = option.substr(9);
+ }
+ else if (option.compare(0, 9, "/logfile:") == 0)
+ {
+ log_file = option.substr(9);
+ }
+ }
+ LocalFree(argv);
+
+ if (!log_file.empty())
+ {
+ return log_file;
+ }
+ }
+#endif
+
+ return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.log");
+ }
+
+ std::string getOldLogFileName(const std::string& log_file)
+ {
+ std::string old_log_file = log_file;
+ size_t separator = old_log_file.find_last_of("/\\");
+ size_t basename_start = (separator == std::string::npos) ? 0 : separator + 1;
+ size_t extension = old_log_file.find_last_of('.');
+
+ if (extension != std::string::npos &&
+ extension > basename_start)
+ {
+ old_log_file.replace(extension, std::string::npos, ".old");
+ }
+ else
+ {
+ old_log_file += ".old";
+ }
+
+ return old_log_file;
+ }
} // anonymous namespace
// Set a named control temporarily for this session, as when set via the command line --set option.
@@ -2937,7 +3058,23 @@ bool LLAppViewer::initConfiguration()
{
if (sendURLToOtherInstance(start_slurl.getSLURLString()))
{
- // successfully handed off URL to existing instance, exit
+ // Successfully handed off URL to existing instance.
+ // Returning 'false' gets treated as a failure to init,
+ // without cleanup, so instead clear markers and app here.
+ // Do not save settings.
+ // Might be smarter to have an exit code for a more reliable
+ // "early exit, needs cleanup" case.
+ LLSingletonBase::deleteAll();
+ cleanupConsole();
+ delete mSettingsLocationList;
+ if (!mSecondInstance)
+ {
+ // Todo: Unfortunately, if we are doing this, stats and
+ // markers from previous session were already processed,
+ // cleared yet haven't been reported and will be lost.
+ // Consider a way to save those.
+ removeMarkerFiles();
+ }
return false;
}
}
@@ -2984,18 +3121,30 @@ bool LLAppViewer::initConfiguration()
LLTrans::getString("MBAlreadyRunning"),
LLStringUtil::null,
OSMB_OK);
+
+ // Since returning 'false' is basically an error without cleanup,
+ // do cleanup here. No need to worry about marker files here.
+ LLSingletonBase::deleteAll();
+ cleanupConsole();
return false;
}
if (mSecondInstance)
{
- // This is the second instance of SL. Mute voice,
- // but make sure the setting is *not* persisted.
+ // This is the second concurrent instance of SL.
+ // Disable voice for this session only, user should
+ // be able to enable voice manually, after that it
+ // works the same way as on primary instance.
LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat");
- if (enable_voice)
+ if (enable_voice && enable_voice->getValue().asBoolean())
{
+ LL_DEBUGS("AppInit") << "Disabling voice for this session only" << LL_ENDL;
+ // Will be saved as mValues[2] which does not get written to the file.
+ // This feels like a hack, but otherwise way too many controls have to
+ // be tracked manually instead of using xmls' control_name.
const bool DO_NOT_PERSIST = false;
- enable_voice->setValue(LLSD(false), DO_NOT_PERSIST);
+ LLSD::Boolean new_value = false;
+ enable_voice->setValue(new_value, DO_NOT_PERSIST);
}
}
@@ -3017,6 +3166,57 @@ bool LLAppViewer::initConfiguration()
return true; // Config was successful.
}
+static void apply_localized_comments(LLControlGroup& group, const LLSD& comments)
+{
+ for (LLSD::map_const_iterator it = comments.beginMap(); it != comments.endMap(); ++it)
+ {
+ LLControlVariablePtr control = group.getControl(it->first);
+ if (control.notNull())
+ {
+ const std::string comment = it->second.asString();
+ if (!comment.empty())
+ {
+ control->setComment(comment);
+ }
+ }
+ }
+}
+
+void LLAppViewer::loadLocalizedSettingsComments()
+{
+ std::string lang = LLUI::getLanguage();
+ if (lang.empty() || lang == "en")
+ {
+ return;
+ }
+
+ std::string path = gDirUtilp->findSkinnedFilename(
+ LLDir::XUI, "settings_comments.xml", LLDir::CURRENT_SKIN);
+ if (path.empty())
+ {
+ return;
+ }
+
+ llifstream infile;
+ infile.open(path.c_str());
+ if (!infile.is_open())
+ {
+ return;
+ }
+
+ LLSD comments;
+ if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(comments, infile))
+ {
+ infile.close();
+ LL_WARNS("Settings") << "Failed to parse localized settings comments file: " << path << LL_ENDL;
+ return;
+ }
+ infile.close();
+
+ apply_localized_comments(gSavedSettings, comments);
+ apply_localized_comments(gSavedPerAccountSettings, comments);
+}
+
// The following logic is replicated in initConfiguration() (to be able to get
// some initial strings before we've finished initializing enough to know the
// current language) and also in init() (to initialize for real). Somehow it
@@ -3493,10 +3693,11 @@ LLSD LLAppViewer::getViewerInfo() const
vlc_ver_codec << LIBVLC_VERSION_REVISION;
info["LIBVLC_VERSION"] = vlc_ver_codec.str();
- S32 packets_in = (S32)LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN);
+ LLTrace::Recording& recording = LLViewerStats::instance().getRecording();
+ S32 packets_in = (S32)recording.getSum(LLStatViewer::PACKETS_IN);
if (packets_in > 0)
{
- info["PACKETS_LOST"] = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST);
+ info["PACKETS_LOST"] = recording.getSum(LLStatViewer::PACKETS_LOST);
info["PACKETS_IN"] = packets_in;
info["PACKETS_PCT"] = 100.f*info["PACKETS_LOST"].asReal() / info["PACKETS_IN"].asReal();
}
@@ -3549,6 +3750,13 @@ std::string LLAppViewer::getViewerInfoString(bool default_string) const
{
args[ii->first] = LLTrans::getString("none_text", default_string);
}
+ else if (ii->second.isBoolean())
+ {
+ // LLSD intentionally renders false as an empty string to
+ // preserve string-to-boolean round trips. Viewer info is
+ // human-readable, so show both boolean states explicitly.
+ args[ii->first] = ii->second.asBoolean() ? "true" : "false";
+ }
else
{
// don't forget to render value asString()
@@ -3931,7 +4139,12 @@ void LLAppViewer::processMarkerFiles()
// - Freeze (SecondLife.exec_marker present, not locked)
// - LLError Crash (SecondLife.llerror_marker present)
// - Other Crash (SecondLife.error_marker present)
- // These checks should also remove these files for the last 2 cases if they currently exist
+ // - Watchdog freeze (SecondLife.watchdog_marker present)
+ // - Failed to initialize (SecondLife.inited_marker not present)
+ // - Potentially killed by task manager or computer
+ // didn't recover from hibernation (SecondLife.close_marker present)
+ // These checks should also remove these files for the last 2 cases
+ // if they currently exist
std::ostringstream marker_log_stream;
bool marker_is_same_version = true;
@@ -4043,6 +4256,8 @@ void LLAppViewer::processMarkerFiles()
// Bugsplat will set correct state in bugsplatSendLog.
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME);
std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME);
+ std::string inited_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME);
+ std::string close_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME);
if(LLFile::isfile(error_marker_file))
{
S32 marker_code = getMarkerErrorCode(error_marker_file);
@@ -4081,11 +4296,13 @@ void LLAppViewer::processMarkerFiles()
}
else
{
- // so only check watchdog marker if there is no error marker.
- if (LLFile::isfile(watchdog_marker_file))
+ if (LAST_EXEC_UNKNOWN == gLastExecEvent
+ || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent)
{
- if (LAST_EXEC_UNKNOWN == gLastExecEvent
- || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent)
+ // If viewer crashed after a freeze was detected,
+ // crash still takes precendence.
+ // So only check watchdog marker if there is no error marker.
+ if (LLFile::isfile(watchdog_marker_file))
{
// watchdog marker gets created if we detect a freeze,
// so if viwer did not stop gracefully, and we know it wasn't a crash,
@@ -4097,9 +4314,49 @@ void LLAppViewer::processMarkerFiles()
<< LL_ENDL;
}
}
- removeWatchdogMarker();
+ // If 'close' marker is found, viewer either started shutdown but
+ // failed, OS did not recover from hibernation or viewer got
+ // killed by task manager.
+ // Marker does not indicate that viewer was closed or is closing,
+ // just that 'close' was requested before viewer died.
+ else if (LLFile::isfile(close_marker_file))
+ {
+ // Unfortunately we can't reliably distinguish
+ // task manager's case from genuine shutdown, so we
+ // have to report all of them as the same thing.
+ // Todo: but we can distinguish hibernation, might want
+ // to simply not report it as an issue.
+ if (markerIsSameVersion(close_marker_file))
+ {
+ gLastExecEvent = LAST_EXEC_OS_EVENT;
+ LL_INFOS("MarkerFile") << "'Close' marker '" << close_marker_file << "' found, setting LastExecEvent to OS_EVENT"
+ << LL_ENDL;
+ }
+ }
+ else if ((LAST_EXEC_UNKNOWN == gLastExecEvent)
+ && !LLFile::isfile(inited_marker_file))
+ {
+ // Viewer didn't get to a login screen.
+ gLastExecEvent = LAST_EXEC_INIT;
+ LL_INFOS("MarkerFile") << "'Inited' marker '"
+ << inited_marker_file
+ << "' not found, assuming that init crashed."
+ << LL_ENDL;
+ }
}
}
+ if (LLFile::isfile(watchdog_marker_file))
+ {
+ removeWatchdogMarker();
+ }
+ if (LLFile::isfile(inited_marker_file))
+ {
+ removeInitedMarker();
+ }
+ if (LLFile::isfile(close_marker_file))
+ {
+ removeCloseRequestMarker();
+ }
#if LL_DARWIN
if (!mSecondInstance && gLastExecEvent != LAST_EXEC_NORMAL)
@@ -4143,6 +4400,7 @@ void LLAppViewer::removeMarkerFiles()
{
LL_WARNS("MarkerFile") << "logout marker '"<<mLogoutMarkerFileName<<"' not open"<< LL_ENDL;
}
+ removeCloseRequestMarker();
removeWatchdogMarker();
}
else
@@ -4202,6 +4460,13 @@ void LLAppViewer::requestQuit()
// This prevents the halfway-logged-in avatar from hanging around inworld for a couple minutes.
if (region)
{
+ // We probably don't have caps to do it, and it might
+ // arrive after logout finishes, but attempt to send stats
+ if (region->capabilitiesReceived())
+ {
+ constexpr bool include_preferences = true;
+ send_viewer_stats(include_preferences);
+ }
sendLogoutRequest();
}
@@ -4302,6 +4567,14 @@ void LLAppViewer::abortQuit()
mClosingFloaters = false;
}
+void LLAppViewer::sendViewerStatistics(bool include_preferences)
+{
+ if (!gDisconnected)
+ {
+ send_viewer_stats(include_preferences);
+ }
+}
+
void LLAppViewer::migrateCacheDirectory()
{
#if LL_WINDOWS || LL_DARWIN
@@ -4937,6 +5210,7 @@ void LLAppViewer::idle()
static LLCachedControl<U32> downscale_method(gSavedSettings, "RenderDownScaleMethod");
gGLManager.mDownScaleMethod = downscale_method;
LLImageGL::updateClass();
+ LLUIImage::updateClass();
// Service the WorkQueue we use for replies from worker threads.
// Use function statics for the timeslice setting so we only have to fetch
@@ -5589,6 +5863,59 @@ bool LLAppViewer::errorMarkerExists() const
return LLFile::isfile(error_marker_file);
}
+void LLAppViewer::createCloseRequestMarker() const
+{
+ // WINDOW THREAD! since we need this to act fast.
+ // This does not indicate that viewer was closed or is closing,
+ // but that 'close' was requested.
+ if (!mSecondInstance)
+ {
+ std::string close_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME);
+
+ LLAPRFile file;
+ file.open(close_marker, LL_APR_WB);
+ if (file.getFileHandle())
+ {
+ recordMarkerVersion(file);
+ file.close();
+ }
+ }
+}
+
+void LLAppViewer::removeCloseRequestMarker() const
+{
+ if (!mSecondInstance)
+ {
+ std::string close_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME);
+ LLFile::remove(close_marker, ENOENT);
+ }
+}
+
+void LLAppViewer::createInitedMarker() const
+{
+ if (!mSecondInstance)
+ {
+ std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME);
+
+ LLAPRFile file;
+ file.open(inited_marker, LL_APR_WB);
+ if (file.getFileHandle())
+ {
+ recordMarkerVersion(file);
+ file.close();
+ }
+ }
+}
+
+void LLAppViewer::removeInitedMarker() const
+{
+ if (!mSecondInstance)
+ {
+ std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME);
+ LLFile::remove(inited_marker, ENOENT);
+ }
+}
+
void LLAppViewer::createWatchdogMarker() const
{
if (!mSecondInstance)
@@ -5604,12 +5931,13 @@ void LLAppViewer::createWatchdogMarker() const
}
}
}
+
void LLAppViewer::removeWatchdogMarker() const
{
if (!mSecondInstance)
{
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME);
- LLFile::remove(error_marker_file);
+ LLFile::remove(error_marker_file, ENOENT);
}
}
@@ -5631,6 +5959,29 @@ void LLAppViewer::outOfMemorySoftQuit()
}
}
+void LLAppViewer::setPermitOSHibernation(bool permit)
+{
+ if (permit)
+ {
+ if (mCurrentHibernationMode != LL_HIBERNATE_MODE_DEFAULT)
+ {
+ // Will call OS specific code to let OS hibernate when idle
+ setOSHibernationMode(LL_HIBERNATE_MODE_DEFAULT);
+ mCurrentHibernationMode = LL_HIBERNATE_MODE_DEFAULT;
+ }
+ }
+ else
+ {
+ static LLCachedControl<S32> os_hibernation_mode(gSavedSettings, "OSHibernationMode", 0);
+ eHibernationMode mode = static_cast<eHibernationMode>(os_hibernation_mode());
+ if (mode != LL_HIBERNATE_MODE_DEFAULT && mCurrentHibernationMode != mode)
+ {
+ setOSHibernationMode(mode);
+ mCurrentHibernationMode = mode;
+ }
+ }
+}
+
void LLAppViewer::idleNameCache()
{
// Neither old nor new name cache can function before agent has a region
@@ -5675,7 +6026,6 @@ void LLAppViewer::idleNetwork()
pingMainloopTimeout("idleNetwork");
gObjectList.mNumNewObjects = 0;
- S32 total_decoded = 0;
static LLCachedControl<bool> speed_test(gSavedSettings, "SpeedTest", false);
if (!speed_test())
@@ -5683,64 +6033,56 @@ void LLAppViewer::idleNetwork()
LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("idle network"); //LL_RECORD_BLOCK_TIME(FTM_IDLE_NETWORK); // decode
LLTimer check_message_timer;
- // Read all available packets from network
const S64 frame_count = gFrameCount; // U32->S64
- F32 total_time = 0.0f;
+ S32 total_decoded = 0;
+ // Process packets from network
+ LockMessageChecker lmc(gMessageSystem);
+ while (lmc.checkAllMessages(frame_count, gServicePump))
{
- bool needs_drain = false;
- LockMessageChecker lmc(gMessageSystem);
- while (lmc.checkAllMessages(frame_count, gServicePump))
- {
- if (gDoDisconnect)
- {
- // We're disconnecting, don't process any more messages from the server
- // We're usually disconnecting due to either network corruption or a
- // server going down, so this is OK.
- break;
- }
+ ++total_decoded;
- total_decoded++;
-
- if (total_decoded > MESSAGE_MAX_PER_FRAME)
+ // Time-box processing of network packets to prevent framerate catastrophe
+ if (check_message_timer.getElapsedTimeF32() >= CheckMessagesMaxTime)
+ {
+ // Drain the socket buffer so we know how many messages remain to process
+ S32 num_buffered_packets = gMessageSystem->drainUdpSocket();
+ if (num_buffered_packets > total_decoded)
{
- needs_drain = true;
- break;
+ // Grow CheckMessagesMaxTime until we process more packets each frame than arrive.
+ // This might spiral out of control on very slow computers on fast networks when
+ // the bandwidth settings are too high. There is a mechanism for providing backpressure
+ // to network bandwidth but it may be inadequate for the task
+ // (see LLViewerThrottle::updateDynamicThrottle() for more details).
+ CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames
}
-
- // Prevent slow packets from completely destroying the frame rate.
- // This usually happens due to clumps of avatars taking huge amount
- // of network processing time (which needs to be fixed, but this is
- // a good limit anyway).
- total_time = check_message_timer.getElapsedTimeF32();
- if (total_time >= CheckMessagesMaxTime)
+ else if (num_buffered_packets == 0)
{
- needs_drain = true;
- break;
+ // Reset CheckMessagesMaxTime to default value
+ CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME;
}
+ break;
}
- if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0)
+
+ if (total_decoded > MESSAGE_MAX_PER_FRAME)
{
- // Rather than allow packets to silently backup on the socket
- // we drain them into our own buffer so we know how many exist.
- S32 num_buffered_packets = gMessageSystem->drainUdpSocket();
- if (num_buffered_packets > 0)
- {
- // Increase CheckMessagesMaxTime so that we will eventually catch up
- CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames
- }
+ // MESSAGE_MAX_PER_FRAME is very high (400)
+ // We expect to run out of time before reaching here, but just in case...
+ gMessageSystem->drainUdpSocket();
+ break;
}
- else
+
+ if (gDoDisconnect)
{
- // Reset CheckMessagesMaxTime to default value
- CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME;
+ // We're disconnecting so no need to process packets.
+ break;
}
+ }
- // Handle per-frame message system processing.
+ // Handle per-frame message system processing.
- static LLCachedControl<F32> ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f);
- lmc.processAcks(ack_collection_time());
- }
+ static LLCachedControl<F32> ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f);
+ lmc.processAcks(ack_collection_time());
}
add(LLStatViewer::NUM_NEW_OBJECTS, gObjectList.mNumNewObjects);
@@ -5847,6 +6189,9 @@ void LLAppViewer::disconnectViewer()
// Pass the connection state to LLUrlEntryParcel not to attempt
// parcel info requests while disconnected.
LLUrlEntryParcel::setDisconnected(gDisconnected);
+
+ // Restore default OS hibernation mode
+ setPermitOSHibernation(true);
}
void LLAppViewer::forceErrorLLError()
@@ -6061,6 +6406,32 @@ F32 LLAppViewer::getMainloopTimeoutSec() const
}
}
+std::string LLAppViewer::getMainloopWatchdogState() const
+{
+ if (!mMainloopTimeout)
+ {
+ return std::string();
+ }
+ std::string state = mMainloopTimeout->getState();
+
+ if (mMainloopTimeout->hasExpired())
+ {
+ return "Expired at " + state;
+ }
+
+ // Check if the watchdog is currently active (timer started)
+ if (!mMainloopTimeout->isAlive())
+ {
+ // Timer is not running, meaning watchdog is paused/stopped
+ if (state.empty())
+ {
+ return "Paused";
+ }
+ return "Paused at " + state;
+ }
+ return state;
+}
+
void LLAppViewer::handleLoginComplete()
{
gLoggedInTime.start();
@@ -6112,6 +6483,15 @@ void LLAppViewer::handleLoginComplete()
// we logged in successfully, so save settings on logout
LL_INFOS() << "Login successful, per account settings will be saved on log out." << LL_ENDL;
mSavePerAccountSettings=true;
+
+ // Don't allow hibernation while we're running
+ setPermitOSHibernation(false);
+ // Track 'hibernation' mode changes
+ mOSHibernationModeChangeConnection = gSavedSettings.getControl("OSHibernationMode")->getSignal()->connect([](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val)
+ {
+ // setPermitOSHibernation will sort itself out based on new mode.
+ LLAppViewer::instance()->setPermitOSHibernation(false);
+ });
}
//virtual
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index d76e5015e9..837b598ae5 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -17,7 +17,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -76,9 +76,10 @@ typedef enum
LAST_EXEC_LOGOUT_CRASH,
LAST_EXEC_BAD_ALLOC,
LAST_EXEC_MISSING_FILES,
- LAST_EXEC_GRAPHICS_INIT,
+ LAST_EXEC_INIT,
LAST_EXEC_UNKNOWN,
LAST_EXEC_LOGOUT_UNKNOWN,
+ LAST_EXEC_OS_EVENT,
LAST_EXEC_COUNT
} eLastExecEvent;
@@ -113,6 +114,7 @@ public:
const LLSD& substitutions = LLSD()); // Display an error dialog and forcibly quit.
void earlyExitNoNotify(); // Do not display error dialog then forcibly quit.
void abortQuit(); // Called to abort a quit request.
+ void sendViewerStatistics(bool include_preferences);
bool quitRequested() { return mQuitRequested; }
bool logoutRequestSent() { return mLogoutRequestSent; }
@@ -210,6 +212,7 @@ public:
void pingMainloopTimeout(std::string_view state);
F32 getMainloopTimeoutSec() const;
+ std::string getMainloopWatchdogState() const;
// Handle the 'login completed' event.
// *NOTE:Mani Fix this for login abstraction!!
@@ -254,6 +257,10 @@ public:
void createErrorMarker(eLastExecEvent error_code) const;
bool errorMarkerExists() const;
+ void createCloseRequestMarker() const;
+ void removeCloseRequestMarker() const;
+ void createInitedMarker() const;
+ void removeInitedMarker() const;
void createWatchdogMarker() const;
void removeWatchdogMarker() const;
@@ -263,6 +270,8 @@ public:
// Note: mQuitRequested can be aborted by user.
void outOfMemorySoftQuit();
+ virtual void setPermitOSHibernation(bool permit);
+
#ifdef LL_DISCORD
static void initDiscordSocial();
static void updateDiscordActivity();
@@ -274,10 +283,19 @@ protected:
virtual bool initWindow(); // Initialize the viewer's window.
virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system
virtual void initConsole() {}; // Initialize OS level debugging console.
+ virtual void cleanupConsole() {}; // Cleanup OS level debugging console.
virtual bool initHardwareTest() { return true; } // A false result indicates the app should quit.
virtual bool initSLURLHandler();
virtual bool sendURLToOtherInstance(const std::string& url);
+ typedef enum
+ {
+ LL_HIBERNATE_MODE_DEFAULT = 0, // Use the platform's default behavior.
+ LL_HIBERNATE_MODE_PREVENT = 1,
+ LL_HIBERNATE_MODE_PREVENT_SCREEN = 2,
+ } eHibernationMode;
+ virtual void setOSHibernationMode(eHibernationMode mode);
+
virtual bool initParseCommandLine(LLCommandLineParser& clp)
{ return true; } // Allow platforms to specify the command line args.
@@ -303,6 +321,7 @@ private:
bool initThreads(); // Initialize viewer threads, return false on failure.
bool initConfiguration(); // Initialize settings from the command line/config file.
void initStrings(); // Initialize LLTrans machinery
+ void loadLocalizedSettingsComments(); // Override Debug Settings comments for current locale
bool initCache(); // Initialize local client cache.
// We have switched locations of both Mac and Windows cache, make sure
@@ -384,6 +403,9 @@ private:
LLAppCoreHttp mAppCoreHttp;
bool mIsFirstRun;
+
+ eHibernationMode mCurrentHibernationMode = LL_HIBERNATE_MODE_DEFAULT;
+ boost::signals2::scoped_connection mOSHibernationModeChangeConnection;
};
// Globals with external linkage. From viewer.h
diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp
index c924771816..2fd0eebd44 100644
--- a/indra/newview/llappviewerlinux.cpp
+++ b/indra/newview/llappviewerlinux.cpp
@@ -79,6 +79,10 @@ namespace
void (*gOldTerminateHandler)() = NULL;
}
+// Initialize static members
+guint32 LLAppViewerLinux::sPowerInhibitCookie = 0;
+bool LLAppViewerLinux::sPowerInhibitActive = false;
+
void check_vm_bloat()
{
#if LL_LINUX
@@ -276,6 +280,19 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result)
gViewerAppPtr = nullptr;
}
+LLAppViewerLinux::LLAppViewerLinux()
+{
+}
+
+LLAppViewerLinux::~LLAppViewerLinux()
+{
+ // Clean up any power management inhibition on exit
+ if (sPowerInhibitActive)
+ {
+ uninhibitPowerManagement();
+ }
+}
+
bool LLAppViewerLinux::init()
{
bool success = LLAppViewer::init();
@@ -449,6 +466,178 @@ bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url)
}
#endif // LL_GLIB
+
+#if LL_GLIB
+
+namespace
+{
+ // Session-bus sleep inhibitors, tried in order; both hand back a uint cookie.
+ struct PowerInhibitor
+ {
+ const char* service;
+ const char* path;
+ const char* iface;
+ const char* uninhibit;
+ bool gnome_args;
+ };
+
+ const PowerInhibitor POWER_INHIBITORS[] =
+ {
+ { "org.freedesktop.PowerManagement", "/org/freedesktop/PowerManagement/Inhibit",
+ "org.freedesktop.PowerManagement.Inhibit", "UnInhibit", false },
+ { "org.gnome.SessionManager", "/org/gnome/SessionManager",
+ "org.gnome.SessionManager", "Uninhibit", true }
+ };
+
+ const PowerInhibitor* sActiveInhibitor = nullptr;
+
+ // Returns the reply, or null on failure. Takes ownership of args.
+ GVariant* call_power_manager(const PowerInhibitor& inhibitor, const char* method, GVariant* args,
+ const GVariantType* reply_type)
+ {
+ g_variant_ref_sink(args);
+
+ GDBusConnection* bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
+ if (!bus)
+ {
+ LL_WARNS("OS") << "Getting dbus failed." << LL_ENDL;
+ g_variant_unref(args);
+ return nullptr;
+ }
+
+ GError* error = nullptr;
+ GVariant* result = g_dbus_connection_call_sync(bus, inhibitor.service, inhibitor.path,
+ inhibitor.iface, method, args, reply_type,
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
+ if (!result)
+ {
+ LL_DEBUGS("OS") << inhibitor.service << "." << method << " failed: "
+ << (error ? error->message : "unknown error") << LL_ENDL;
+ }
+ if (error)
+ {
+ g_error_free(error);
+ }
+
+ g_variant_unref(args);
+ g_object_unref(bus);
+ return result;
+ }
+}
+
+#endif // LL_GLIB
+
+void LLAppViewerLinux::setOSHibernationMode(eHibernationMode mode)
+{
+ if (mode == LL_HIBERNATE_MODE_DEFAULT)
+ {
+ // Allow OS to sleep/hibernate - remove any inhibition
+ if (sPowerInhibitActive)
+ {
+ uninhibitPowerManagement();
+ LL_INFOS("OS") << "Permitted OS hibernation/sleep" << LL_ENDL;
+ }
+ }
+ else if (mode == LL_HIBERNATE_MODE_PREVENT)
+ {
+ // Prevent system sleep, but allow display to turn off
+ // Release any existing inhibition first to allow mode switching
+ if (sPowerInhibitActive)
+ {
+ uninhibitPowerManagement();
+ }
+
+ if (inhibitPowerManagement(false))
+ {
+ LL_INFOS("OS") << "Prevented OS hibernation/sleep, display sleep allowed" << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("OS") << "Failed to prevent OS hibernation/sleep" << LL_ENDL;
+ }
+ }
+ else if (mode == LL_HIBERNATE_MODE_PREVENT_SCREEN)
+ {
+ // Prevent both system and display sleep
+ // Release any existing inhibition first to allow mode switching
+ if (sPowerInhibitActive)
+ {
+ uninhibitPowerManagement();
+ }
+
+ if (inhibitPowerManagement(true))
+ {
+ LL_INFOS("OS") << "Prevented OS hibernation/sleep and display sleep" << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("OS") << "Failed to prevent OS hibernation/sleep and display sleep" << LL_ENDL;
+ }
+ }
+}
+
+// TODO: This is AI Generated!!!, needs review and testing.
+bool LLAppViewerLinux::inhibitPowerManagement(bool inhibit_display)
+{
+#if LL_GLIB
+ const char* reason = inhibit_display ?
+ "Viewer active - preventing system and display sleep" :
+ "Viewer active - preventing system sleep";
+
+ for (const PowerInhibitor& inhibitor : POWER_INHIBITORS)
+ {
+ // Inhibit(app: s, reason: s) -> u, or GNOME's Inhibit(app: s, xid: u, reason: s, flags: u) -> u
+ // GNOME flags: 4 = suspend, 8 = idle (display), 12 = both
+ GVariant* args = inhibitor.gnome_args ?
+ g_variant_new("(susu)", "SecondLifeViewer", (guint32)0, reason,
+ (guint32)(inhibit_display ? 12 : 4)) :
+ g_variant_new("(ss)", "Second Life Viewer", reason);
+
+ GVariant* result = call_power_manager(inhibitor, "Inhibit", args, G_VARIANT_TYPE("(u)"));
+ if (!result)
+ {
+ continue;
+ }
+
+ guint32 cookie = 0;
+ g_variant_get(result, "(u)", &cookie);
+ g_variant_unref(result);
+
+ sPowerInhibitCookie = cookie;
+ sPowerInhibitActive = true;
+ sActiveInhibitor = &inhibitor;
+ LL_INFOS("OS") << "Successfully inhibited power management using "
+ << inhibitor.service << LL_ENDL;
+ return true;
+ }
+#endif // LL_GLIB
+
+ return false;
+}
+
+void LLAppViewerLinux::uninhibitPowerManagement()
+{
+#if LL_GLIB
+ if (sPowerInhibitActive && sActiveInhibitor)
+ {
+ GVariant* result = call_power_manager(*sActiveInhibitor, sActiveInhibitor->uninhibit,
+ g_variant_new("(u)", (guint32)sPowerInhibitCookie),
+ nullptr);
+ if (result)
+ {
+ g_variant_unref(result);
+ LL_INFOS("OS") << "Successfully uninhibited power management using "
+ << sActiveInhibitor->service << LL_ENDL;
+ }
+
+ sActiveInhibitor = nullptr;
+ }
+#endif // LL_GLIB
+
+ sPowerInhibitCookie = 0;
+ sPowerInhibitActive = false;
+}
+
void LLAppViewerLinux::initCrashReporting(bool reportFreeze)
{
}
diff --git a/indra/newview/llappviewerlinux.h b/indra/newview/llappviewerlinux.h
index 594bb64243..faac3c0a05 100644
--- a/indra/newview/llappviewerlinux.h
+++ b/indra/newview/llappviewerlinux.h
@@ -27,6 +27,10 @@
#ifndef LL_LLAPPVIEWERLINUX_H
#define LL_LLAPPVIEWERLINUX_H
+extern "C" {
+# include <glib.h>
+}
+
#ifndef LL_LLAPPVIEWER_H
#include "llappviewer.h"
#endif
@@ -36,8 +40,8 @@ class LLCommandLineParser;
class LLAppViewerLinux final : public LLAppViewer
{
public:
- LLAppViewerLinux() = default;
- ~LLAppViewerLinux() override = default;
+ LLAppViewerLinux();
+ ~LLAppViewerLinux() override;
//
// Main application logic
@@ -56,6 +60,16 @@ protected:
bool initSLURLHandler() override;
bool sendURLToOtherInstance(const std::string& url) override;
+ void setOSHibernationMode(eHibernationMode mode) override;
+
+private:
+ // Power management state tracking
+ static guint32 sPowerInhibitCookie;
+ static bool sPowerInhibitActive;
+
+ // Helper methods for power management
+ bool inhibitPowerManagement(bool inhibit_display);
+ void uninhibitPowerManagement();
};
#endif // LL_LLAPPVIEWERLINUX_H
diff --git a/indra/newview/llappviewermacosx-objc.h b/indra/newview/llappviewermacosx-objc.h
index 3fbf4202f1..13b8056193 100644
--- a/indra/newview/llappviewermacosx-objc.h
+++ b/indra/newview/llappviewermacosx-objc.h
@@ -31,5 +31,7 @@
#include <vector>
void force_ns_sxeption();
+void register_url_schemes();
+void set_os_hibernation_mode(int mode);
#endif // LL_LLAPPVIEWERMACOSX_OBJC_H
diff --git a/indra/newview/llappviewermacosx-objc.mm b/indra/newview/llappviewermacosx-objc.mm
index 96a6bc6edc..ab4ae428b9 100644
--- a/indra/newview/llappviewermacosx-objc.mm
+++ b/indra/newview/llappviewermacosx-objc.mm
@@ -29,6 +29,7 @@
#endif
#import <Cocoa/Cocoa.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
#include <iostream>
#include "llappviewermacosx-objc.h"
@@ -38,3 +39,93 @@ void force_ns_sxeption()
NSException *exception = [NSException exceptionWithName:@"Forced NSException" reason:nullptr userInfo:nullptr];
@throw exception;
}
+
+void register_url_schemes()
+{
+ @autoreleasepool // Objective-C automatic memory tracking and release.
+ {
+ NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
+ NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
+
+ // Force Launch Services to re-register this app bundle
+ OSStatus status = LSRegisterURL((__bridge CFURLRef)bundleURL, true);
+
+ if (status == noErr)
+ {
+ // Explicitly set this app as the default handler for our URL schemes
+ NSArray *schemes = @[@"secondlife", @"x-grid-location-info"];
+ NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
+
+ for (NSString *scheme in schemes)
+ {
+ LSSetDefaultHandlerForURLScheme((__bridge CFStringRef)scheme,
+ (__bridge CFStringRef)bundleID);
+ }
+ }
+ }
+}
+
+// Add these as static variables at file scope
+static IOPMAssertionID gPowerAssertionID = kIOPMNullAssertionID;
+
+void set_os_hibernation_mode(int mode)
+{
+ // Release existing assertion
+ if (gPowerAssertionID != kIOPMNullAssertionID)
+ {
+ IOReturn result = IOPMAssertionRelease(gPowerAssertionID);
+ if (result == kIOReturnSuccess)
+ {
+ gPowerAssertionID = kIOPMNullAssertionID;
+ NSLog(@"Permitted OS hibernation/sleep");
+ }
+ else
+ {
+ NSLog(@"Failed to release power assertion: %d", result);
+ }
+ }
+
+ if (mode == 1)
+ {
+ // Prevent OS from sleeping/hibernating
+ CFStringRef assertionName = CFSTR("Second Life Viewer");
+ // kIOPMAssertionTypeNoIdleSleep prevents idle sleep
+ IOReturn result = IOPMAssertionCreateWithName(
+ kIOPMAssertionTypeNoIdleSleep,
+ kIOPMAssertionLevelOn,
+ assertionName,
+ &gPowerAssertionID
+ );
+
+ if (result == kIOReturnSuccess)
+ {
+ NSLog(@"Prevented OS hibernation/sleep, allow display sleep");
+ }
+ else
+ {
+ NSLog(@"Failed to create power assertion: %d", result);
+ }
+ }
+ else if (mode == 2)
+ {
+ // Prevent OS from sleeping/hibernating, prevent screen from going off
+ CFStringRef assertionName = CFSTR("Second Life Viewer");
+ // kIOPMAssertionTypeNoIdleSleep prevents idle sleep
+ // kIOPMAssertionTypeNoDisplaySleep prevents display sleep
+ IOReturn result = IOPMAssertionCreateWithName(
+ kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn,
+ assertionName,
+ &gPowerAssertionID
+ );
+
+ if (result == kIOReturnSuccess)
+ {
+ NSLog(@"Prevented OS hibernation/sleep or screen from turning off");
+ }
+ else
+ {
+ NSLog(@"Failed to create power assertion: %d", result);
+ }
+ }
+}
diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp
index 2bd9c8661a..c188bb459f 100644
--- a/indra/newview/llappviewermacosx.cpp
+++ b/indra/newview/llappviewermacosx.cpp
@@ -49,6 +49,7 @@
#include "llerrorcontrol.h"
#include "llvoavatarself.h" // for gAgentAvatarp->getFullname()
#include <ApplicationServices/ApplicationServices.h>
+#include "llwindowmacosx_iokit.h"
#ifdef LL_CARBON_CRASH_HANDLER
#include <Carbon/Carbon.h>
#endif
@@ -125,6 +126,7 @@ bool pumpMainLoop()
void cleanupViewer()
{
+ set_os_hibernation_mode(0); // restore default OS hibernation behavior
if(!LLApp::isError())
{
if (gViewerAppPtr)
@@ -412,6 +414,27 @@ bool LLAppViewerMacOSX::restoreErrorTrap()
return reset_count == 0;
}
+bool LLAppViewerMacOSX::initSLURLHandler()
+{
+ if (isSecondInstance())
+ {
+ return false;
+ }
+ // Main secondlife:// registration is in info.plist, but macOS
+ // Launch Services caches URL scheme handlers, and a different
+ // viewer might still be registered.
+ // Register URL schemes with Launch Services on every launch
+ register_url_schemes();
+
+ return true;
+}
+
+void LLAppViewerMacOSX::setOSHibernationMode(eHibernationMode mode)
+{
+ // pass to objective-c++
+ set_os_hibernation_mode((int)mode);
+}
+
std::string LLAppViewerMacOSX::generateSerialNumber()
{
char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore
@@ -419,7 +442,7 @@ std::string LLAppViewerMacOSX::generateSerialNumber()
// JC: Sample code from http://developer.apple.com/technotes/tn/tn1103.html
CFStringRef serialNumber = NULL;
- io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault,
+ io_service_t platformExpert = IOServiceGetMatchingService(kLLIOMainPort,
IOServiceMatching("IOPlatformExpertDevice"));
if (platformExpert)
{
diff --git a/indra/newview/llappviewermacosx.h b/indra/newview/llappviewermacosx.h
index d50812a35e..1153bd4191 100644
--- a/indra/newview/llappviewermacosx.h
+++ b/indra/newview/llappviewermacosx.h
@@ -46,6 +46,8 @@ public:
protected:
virtual bool restoreErrorTrap();
+ virtual bool initSLURLHandler();
+ virtual void setOSHibernationMode(eHibernationMode mode);
std::string generateSerialNumber();
virtual bool initParseCommandLine(LLCommandLineParser& clp);
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index e869c6c85e..159c3af8a0 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -72,12 +72,11 @@
#include <fstream>
#include <exception>
-#include "llversioninfovars.h"
-
// Velopack installer and update framework
#if LL_VELOPACK
#include "llvelopack.h"
#endif
+#include "llversioninfovars.h"
// Bugsplat (http://bugsplat.com) crash reporting tool
#ifdef LL_BUGSPLAT
@@ -207,6 +206,13 @@ namespace
LLAppViewer* app = LLAppViewer::instance();
+ // Include mainloop watchdog state if available
+ std::string watchdog_state = app->getMainloopWatchdogState();
+ if (!watchdog_state.empty())
+ {
+ sBugSplatSender->setAttribute(WCSTR(L"WatchdogState"), WCSTR(watchdog_state));
+ }
+
if (!app->isSecondInstance() && !app->errorMarkerExists())
{
// If marker doesn't exist, create a marker with 'other' or 'logout' code for next launch
@@ -854,12 +860,7 @@ bool LLAppViewerWin32::cleanup()
bool result = LLAppViewer::cleanup();
gDXHardware.cleanup();
-
- if (mIsConsoleAllocated)
- {
- FreeConsole();
- mIsConsoleAllocated = false;
- }
+ cleanupConsole();
return result;
}
@@ -943,6 +944,15 @@ void LLAppViewerWin32::initConsole()
return LLAppViewer::initConsole();
}
+void LLAppViewerWin32::cleanupConsole()
+{
+ if (mIsConsoleAllocated)
+ {
+ FreeConsole();
+ mIsConsoleAllocated = false;
+ }
+}
+
void write_debug_dx(const char* str)
{
std::string value = gDebugInfo["DXInfo"].asString();
@@ -1015,7 +1025,7 @@ bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
if (other_window != NULL)
{
- LL_DEBUGS() << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL;
+ LL_DEBUGS("AppInit") << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL;
COPYDATASTRUCT cds;
const S32 SLURL_MESSAGE_TYPE = 0;
cds.dwData = SLURL_MESSAGE_TYPE;
@@ -1023,13 +1033,281 @@ bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
cds.lpData = (void*)url.c_str();
LRESULT msg_result = SendMessage(other_window, WM_COPYDATA, NULL, (LPARAM)&cds);
- LL_DEBUGS() << "SendMessage(WM_COPYDATA) to other window '"
+ LL_DEBUGS("AppInit") << "SendMessage(WM_COPYDATA) to other window '"
<< getWindowTitle() << "' returned " << msg_result << LL_ENDL;
return true;
}
return false;
}
+void LLAppViewerWin32::setOSHibernationMode(eHibernationMode mode)
+{
+ // ES_CONTINUOUS tells Windows to reset the idle timer
+ // and restore normal operation
+ // ES_SYSTEM_REQUIRED prevents system sleep/hibernation
+ // ES_DISPLAY_REQUIRED prevents display sleep
+
+ if (mode == LL_HIBERNATE_MODE_DEFAULT)
+ {
+ // Allow OS to hibernate - clear the previous execution state flags
+ // ES_CONTINUOUS without other flags allows the system to idle normally
+ SetThreadExecutionState(ES_CONTINUOUS);
+ LL_INFOS("OS") << "Permitted OS hibernation/sleep" << LL_ENDL;
+ }
+ else if (mode == LL_HIBERNATE_MODE_PREVENT)
+ {
+ // Prevent OS from hibernating while viewer is running
+ // ES_CONTINUOUS | ES_SYSTEM_REQUIRED keeps the system awake
+ EXECUTION_STATE result = SetThreadExecutionState(
+ ES_CONTINUOUS | ES_SYSTEM_REQUIRED
+ );
+ if (result == NULL)
+ {
+ LL_WARNS("OS") << "Failed to prevent OS hibernation, error: " << GetLastError() << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("OS") << "Prevented OS hibernation, but allowed display sleep" << LL_ENDL;
+ }
+ }
+ else if (mode == LL_HIBERNATE_MODE_PREVENT_SCREEN)
+ {
+ // Prevent OS from hibernating or turning screen off while viewer is running
+ // ES_CONTINUOUS | ES_SYSTEM_REQUIRED keeps the system awake
+ // ES_DISPLAY_REQUIRED keeps the display on
+ EXECUTION_STATE result = SetThreadExecutionState(
+ ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
+ );
+
+ if (result == NULL)
+ {
+ LL_WARNS("OS") << "Failed to prevent OS hibernation and display sleep, error: " << GetLastError() << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("OS") << "Prevented OS hibernation/sleep" << LL_ENDL;
+ }
+ }
+}
+
+bool LLAppViewerWin32::sendShutdownToOtherInstances(const std::wstring& install_dir)
+{
+ // Velopack installs viewer like this:
+ // %appdata%\Local\ChannelNameViewer\Update.exe // which is our uninstaller
+ // %appdata%\Local\ChannelNameViewer\SecondLifeViewer.exe // wrapper, redirects to main executable
+ // %appdata%\Local\ChannelNameViewer\current\SecondLifeViewer.exe // main executable
+ // For reliability don't expect install_dir to be actually in the base path, strip 'current'
+
+ const std::wstring current_suffix = L"\\current";
+ std::wstring normalized_path(install_dir);
+ if (normalized_path.length() >= current_suffix.length() &&
+ _wcsicmp(normalized_path.c_str() + normalized_path.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ normalized_path.resize(normalized_path.length() - current_suffix.length());
+ }
+
+ wchar_t window_class[256]; // Assume max length < 255 chars.
+ mbstowcs(window_class, sWindowClass, 255);
+ window_class[255] = 0;
+
+ // Normalize the directory path
+ wchar_t our_dir_normalized[MAX_PATH];
+ wchar_t* file_part = nullptr;
+ DWORD result = GetFullPathNameW(normalized_path.c_str(), MAX_PATH, our_dir_normalized, &file_part);
+ if (result == 0 || result >= MAX_PATH)
+ {
+ LL_WARNS() << "Failed to normalize our executable path" << LL_ENDL;
+ return false;
+ }
+
+ // Remove trailing backslash if present
+ size_t dir_len = wcslen(our_dir_normalized);
+ if (dir_len > 0 && our_dir_normalized[dir_len - 1] == L'\\')
+ {
+ our_dir_normalized[dir_len - 1] = L'\0';
+ dir_len--;
+ }
+
+ // This message is meant for velopack, so we don't expect to have
+ // a window of our own, store any matching windows.
+ struct EnumData
+ {
+ const wchar_t* target_class;
+ const wchar_t* our_dir_normalized;
+ std::vector<HWND> found_windows;
+ };
+
+ EnumData enum_data;
+ enum_data.target_class = window_class;
+ enum_data.our_dir_normalized = our_dir_normalized;
+
+ // Callback function to find all matching windows
+ auto find_windows_callback = [](HWND hwnd, LPARAM lParam) -> BOOL
+ {
+ EnumData* data = reinterpret_cast<EnumData*>(lParam);
+ wchar_t class_name[256];
+
+ if (GetClassName(hwnd, class_name, 256) > 0)
+ {
+ if (wcscmp(class_name, data->target_class) == 0)
+ {
+ // Get the process ID for this window
+ DWORD process_id = 0;
+ GetWindowThreadProcessId(hwnd, &process_id);
+
+ // Open the process to query its executable path
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
+ if (hProcess)
+ {
+ wchar_t exe_path[MAX_PATH];
+ DWORD size = MAX_PATH;
+ if (QueryFullProcessImageNameW(hProcess, 0, exe_path, &size))
+ {
+ // Normalize the other process's path
+ wchar_t other_dir_normalized[MAX_PATH];
+ wchar_t* other_file_part = nullptr;
+ DWORD result = GetFullPathNameW(exe_path, MAX_PATH, other_dir_normalized, &other_file_part);
+
+ if (result > 0 && result < MAX_PATH)
+ {
+ // Remove the filename part to get just the directory
+ // We are doing this to avoid incidents, like having
+ // multiple viewer version exes in the same folder.
+ if (other_file_part)
+ {
+ *other_file_part = L'\0';
+ }
+
+ // Remove trailing backslash if present
+ size_t other_dir_len = wcslen(other_dir_normalized);
+ if (other_dir_len > 0 && other_dir_normalized[other_dir_len - 1] == L'\\')
+ {
+ other_dir_normalized[other_dir_len - 1] = L'\0';
+ other_dir_len--;
+ }
+
+ // Strip "\current" suffix if present to normalize comparison
+ // This handles both release (with \current) and debug builds (without)
+ const std::wstring current_suffix = L"\\current";
+ if (other_dir_len >= current_suffix.length())
+ {
+ size_t offset = other_dir_len - current_suffix.length();
+ if (_wcsicmp(other_dir_normalized + offset, current_suffix.c_str()) == 0)
+ {
+ other_dir_normalized[offset] = L'\0';
+ }
+ }
+
+ // Compare directories (case-insensitive)
+ if (_wcsicmp(other_dir_normalized, data->our_dir_normalized) == 0)
+ {
+ data->found_windows.push_back(hwnd);
+ }
+ }
+ }
+ CloseHandle(hProcess);
+ }
+ }
+ }
+
+ return TRUE; // Continue enumeration
+ };
+
+ // Find all matching windows and send shutdown messages
+ EnumWindows(find_windows_callback, reinterpret_cast<LPARAM>(&enum_data));
+
+ if (enum_data.found_windows.empty())
+ {
+ LL_DEBUGS("AppInit") << "No other instances found" << LL_ENDL;
+ return false;
+ }
+
+ LL_INFOS("AppInit") << "Found " << (S32)(enum_data.found_windows.size()) << " other instance(s), sending shutdown messages" << LL_ENDL;
+
+ // Get our own process ID to include in the message
+ DWORD our_process_id = GetCurrentProcessId();
+
+ constexpr UINT timeout_ms = 2000; // 2s. Viewer's message thread is supposed to be fast.
+ for (HWND other_window : enum_data.found_windows)
+ {
+ if (IsWindow(other_window))
+ {
+ DWORD_PTR result = 0;
+ LRESULT send_result = SendMessageTimeout(
+ other_window,
+ WM_POST_UNINSTALL_,
+ static_cast<WPARAM>(our_process_id),
+ static_cast<LPARAM>(WM_POST_UNINSTALL_MSG_SHUTDOWN),
+ SMTO_ABORTIFHUNG | SMTO_BLOCK,
+ timeout_ms,
+ &result
+ );
+
+ if (send_result == 0)
+ {
+ DWORD error = GetLastError();
+ if (error == ERROR_TIMEOUT)
+ {
+ LL_WARNS("AppInit") << "Shutdown message timed out for window " << std::hex << other_window << std::dec << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("AppInit") << "Failed to send shutdown message to window " << std::hex << other_window
+ << ", error: " << error << std::dec << LL_ENDL;
+ }
+
+ PostMessage(other_window, WM_CLOSE, 0, 0);
+ }
+ else
+ {
+ LL_DEBUGS("AppInit") << "Shutdown message sent successfully to window " << std::hex << other_window << std::dec << LL_ENDL;
+ }
+ }
+ }
+
+ // Poll for up to 30 seconds, checking every 5 seconds
+ const S32 MAX_WAIT_TIME_MS = 60000; // 30 seconds
+ const S32 POLL_INTERVAL_MS = 5000; // 5 seconds
+ S32 elapsed_time_ms = 0;
+ size_t still_open_count = enum_data.found_windows.size();
+
+ while (elapsed_time_ms < MAX_WAIT_TIME_MS)
+ {
+ LL_INFOS("AppInit") << "Waiting for " << (S32)still_open_count << " instance(s) to close... ("
+ << (S32)(elapsed_time_ms / 1000) << "s elapsed)" << LL_ENDL;
+
+ ms_sleep(POLL_INTERVAL_MS);
+ elapsed_time_ms += POLL_INTERVAL_MS;
+
+ // Check if the specific windows we found still exist
+ // Don't enumerate all windows for new ones, assume that
+ // no instances were reused and assume user won't open
+ // the app again. For now just check our list.
+ still_open_count = 0;
+ for (HWND hwnd : enum_data.found_windows)
+ {
+ if (IsWindow(hwnd))
+ {
+ still_open_count++;
+ }
+ }
+
+ if (still_open_count == 0)
+ {
+ LL_INFOS("AppInit") << "All other instances have closed after " << (S32)(elapsed_time_ms / 1000) << " seconds" << LL_ENDL;
+ return false;
+ }
+ }
+
+ if (still_open_count != 0)
+ {
+ LL_WARNS("AppInit") << "Proceeding with uninstall with " << (S32)still_open_count << " instance(s) still open." << LL_ENDL;
+ }
+
+ return true;
+}
+
std::string LLAppViewerWin32::generateSerialNumber()
{
diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h
index 53177f7f95..7c3c7a475a 100644
--- a/indra/newview/llappviewerwin32.h
+++ b/indra/newview/llappviewerwin32.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -46,10 +46,15 @@ public:
bool reportCrashToBugsplat(void* pExcepInfo) override;
bool reportCustomToBugsplat(const std::string& description) override;
+ // returns true if other windows were found and are still running.
+ static bool sendShutdownToOtherInstances(const std::wstring& install_dir);
+
protected:
bool initWindow() override; // Override to initialize the viewer's window.
void initLoggingAndGetLastDuration() override; // Override to clean stack_trace info.
void initConsole() override; // Initialize OS level debugging console.
+ void cleanupConsole() override;
+
bool initHardwareTest() override; // Win32 uses DX9 to test hardware.
bool initParseCommandLine(LLCommandLineParser& clp) override;
@@ -57,6 +62,7 @@ protected:
bool restoreErrorTrap() override;
bool sendURLToOtherInstance(const std::string& url) override;
+ void setOSHibernationMode(eHibernationMode mode) override;
std::string generateSerialNumber();
diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp
index fb1426a235..c95e98982d 100644
--- a/indra/newview/llavataractions.cpp
+++ b/indra/newview/llavataractions.cpp
@@ -853,6 +853,13 @@ namespace action_give_inventory
break;
}
LLViewerInventoryItem* inv_item = gInventory.getItem(*it);
+ if (!inv_item)
+ {
+ shared = false;
+ LL_WARNS() << "Failed to share an item " << *it
+ << ". Item was not found in inventory." << LL_ENDL;
+ continue;
+ }
if (!inv_item->getPermissions().allowCopyBy(gAgentID))
{
if (!noncopy_item_names.empty())
diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp
index c1af09ebc7..af94c6d443 100644
--- a/indra/newview/llchathistory.cpp
+++ b/indra/newview/llchathistory.cpp
@@ -175,6 +175,49 @@ public:
LLFloaterSidePanelContainer::showPanel("people", "panel_people",
LLSD().with("people_panel_tab_name", "blocked_panel").with("blocked_to_select", getAvatarId()));
}
+ else if (level == "report_abuse")
+ {
+ std::string time_string;
+ if (mTime > 0) // have frame time
+ {
+ time_t current_time = time_corrected();
+ time_t message_time = (time_t)(current_time - LLFrameTimer::getElapsedSeconds() + mTime);
+
+ // Report abuse shouldn't use AM/PM, use 24-hour time
+ time_string = "[" + LLTrans::getString("TimeMonth") + "]/["
+ + LLTrans::getString("TimeDay") + "]/["
+ + LLTrans::getString("TimeYear") + "] ["
+ + LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "]";
+
+ LLSD substitution;
+
+ substitution["datetime"] = (S32)message_time;
+ LLStringUtil::format(time_string, substitution);
+ }
+ else
+ {
+ // From history. This might be empty or not full.
+ // See LLChatLogParser::parse
+ time_string = getChild<LLTextBox>("time_box")->getValue().asString();
+
+ // Just add current date if not full.
+ // Should be fine since both times are supposed to be SLT.
+ if (!time_string.empty() && time_string.size() < 7)
+ {
+ time_string = "[" + LLTrans::getString("TimeMonth") + "]/["
+ + LLTrans::getString("TimeDay") + "]/["
+ + LLTrans::getString("TimeYear") + "] " + time_string;
+
+ LLSD substitution;
+ // To avoid adding today's date to yesterday's timestamp,
+ // use creation time instead of current time
+ substitution["datetime"] = (S32)mCreationTime;
+ LLStringUtil::format(time_string, substitution);
+ }
+ }
+ LLFloaterReporter::showFromChatObj(getAvatarId(), time_string, mText);
+ }
else if (level == "unblock")
{
LLMuteList::getInstance()->remove(LLMute(getAvatarId(), mFrom, LLMute::OBJECT));
@@ -461,7 +504,7 @@ public:
time_string = getChild<LLTextBox>("time_box")->getValue().asString();
// Just add current date if not full.
- // Should be fine since both times are supposed to be stl
+ // Should be fine since both times are supposed to be SLT.
if (!time_string.empty() && time_string.size() < 7)
{
time_string = "[" + LLTrans::getString("TimeMonth") + "]/["
@@ -475,7 +518,7 @@ public:
LLStringUtil::format(time_string, substitution);
}
}
- LLFloaterReporter::showFromChat(mAvatarID, mFrom, time_string, mText);
+ LLFloaterReporter::showFromChatAv(mAvatarID, mFrom, time_string, mText);
}
else if(level == "block_unblock")
{
diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp
index 5ac4ce0d52..2e8fb752b8 100644
--- a/indra/newview/llchatitemscontainerctrl.cpp
+++ b/indra/newview/llchatitemscontainerctrl.cpp
@@ -35,8 +35,10 @@
#include "llcommandhandler.h"
#include "llfloaterreg.h"
#include "lllocalcliprect.h"
+#include "llpanelblockedlist.h"
#include "lltrans.h"
#include "llfloaterimnearbychat.h"
+#include "llfloaterreporter.h"
#include "llfloaterworldmap.h"
#include "llviewermenu.h"
@@ -93,6 +95,32 @@ public:
}
return true;
}
+ if (verb == "block")
+ {
+ if (params.size() > 2)
+ {
+ const std::string object_name = LLURI::unescape(params[2].asString());
+ LLMute mute(object_id, object_name, LLMute::OBJECT);
+ LLMuteList::getInstance()->add(mute);
+ LLPanelBlockedList::showPanelAndSelect(mute.mID);
+ }
+ return true;
+ }
+ if (verb == "unblock")
+ {
+ if (params.size() > 2)
+ {
+ const std::string object_name = params[2].asString();
+ LLMute mute(object_id, object_name, LLMute::OBJECT);
+ LLMuteList::getInstance()->remove(mute);
+ }
+ return true;
+ }
+ if (verb == "reportAbuse" && web == NULL)
+ {
+ LLFloaterReporter::showFromObject(object_id, LLUUID::null);
+ return true;
+ }
return false;
}
diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp
index 6bd15d4b61..f5d144dcc6 100644
--- a/indra/newview/llcofwearables.cpp
+++ b/indra/newview/llcofwearables.cpp
@@ -292,7 +292,8 @@ LLCOFWearables::LLCOFWearables() : LLPanel(),
mBodyPartsTab(NULL),
mLastSelectedTab(NULL),
mAccordionCtrl(NULL),
- mCOFVersion(-1)
+ mCOFVersion(-1),
+ mRefreshPending(false)
{
mClothingMenu = new CofClothingContextMenu(this);
mAttachmentMenu = new CofAttachmentContextMenu(this);
@@ -326,6 +327,10 @@ bool LLCOFWearables::postBuild()
mClothing->setCommitOnSelectionChange(true);
mBodyParts->setCommitOnSelectionChange(true);
+ mClothing->setReorderValidateCallback(boost::bind(&LLCOFWearables::canReorderClothing, this, _1, _2));
+ mClothing->setReorderCallback(boost::bind(&LLCOFWearables::onClothingReordered, this, _1, _2));
+ mClothing->setReorderEndedCallback(boost::bind(&LLCOFWearables::onReorderEnded, this));
+
//clothing is sorted according to its position relatively to the body
mAttachments->setComparator(&WEARABLE_NAME_COMPARATOR);
mBodyParts->setComparator(&WEARABLE_NAME_COMPARATOR);
@@ -377,6 +382,50 @@ void LLCOFWearables::onSelectionChange(LLFlatListView* selected_list)
onCommit();
}
+bool LLCOFWearables::canReorderClothing(const LLSD& dragged_value, const LLSD& neighbour_value)
+{
+ LLViewerInventoryItem* dragged = gInventory.getItem(dragged_value.asUUID());
+ LLViewerInventoryItem* neighbour = gInventory.getItem(neighbour_value.asUUID());
+ if (!dragged || !neighbour) return false; // reject placeholder/dummy rows
+ if (!dragged->isWearableType() || !neighbour->isWearableType()) return false;
+
+ return dragged->getWearableType() == neighbour->getWearableType();
+}
+
+void LLCOFWearables::onClothingReordered(const LLSD& dragged_value, S32 /*new_index*/)
+{
+ LLViewerInventoryItem* item = gInventory.getItem(dragged_value.asUUID());
+ if (!item || !item->isWearableType()) return;
+
+ LLWearableType::EType type = item->getWearableType();
+
+ // Persist the affected type group's full order from the list's current order
+ // (furthest-to-closest), which covers both single- and multi-row drags.
+ uuid_vec_t ordered_link_ids;
+ std::vector<LLSD> values;
+ mClothing->getValues(values);
+ for (const LLSD& value : values)
+ {
+ LLViewerInventoryItem* other = gInventory.getItem(value.asUUID());
+ if (!other || !other->isWearableType() || other->getWearableType() != type) continue;
+
+ ordered_link_ids.push_back(value.asUUID());
+ }
+
+ if (ordered_link_ids.size() < 2) return;
+
+ LLAppearanceMgr::getInstance()->reorderWearableGroup(type, ordered_link_ids);
+}
+
+void LLCOFWearables::onReorderEnded()
+{
+ // Run a refresh that arrived (and was skipped) while the drag was active.
+ if (mRefreshPending)
+ {
+ refresh();
+ }
+}
+
void LLCOFWearables::onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded)
{
bool had_selected_items = mClothing->numSelected() || mAttachments->numSelected() || mBodyParts->numSelected();
@@ -401,6 +450,15 @@ void LLCOFWearables::onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expa
void LLCOFWearables::refresh()
{
+ // Don't rebuild mid-drag, clearing the list would cancel the active reorder.
+ // Remember it so onReorderEnded() can re-run the skipped refresh on release.
+ if (mClothing && mClothing->isReorderActive())
+ {
+ mRefreshPending = true;
+ return;
+ }
+ mRefreshPending = false;
+
const LLUUID cof_id = LLAppearanceMgr::instance().getCOF();
if (cof_id.isNull())
{
@@ -560,6 +618,12 @@ LLPanelClothingListItem* LLCOFWearables::buildClothingListItem(LLViewerInventory
item_panel->setShowMoveUpButton(!first);
item_panel->setShowMoveDownButton(!last);
+ // hint that rows in a multi-layer group can be dragged to reorder
+ if (!(first && last))
+ {
+ item_panel->setToolTip(LLTrans::getString("ReorderClothingTooltip"));
+ }
+
//setting callbacks
//*TODO move that item panel's inner structure disclosing stuff into the panels
item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable));
diff --git a/indra/newview/llcofwearables.h b/indra/newview/llcofwearables.h
index b349f35921..7d4d8917b4 100644
--- a/indra/newview/llcofwearables.h
+++ b/indra/newview/llcofwearables.h
@@ -102,6 +102,12 @@ protected:
void onSelectionChange(LLFlatListView* selected_list);
void onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded);
+ // Clothing drag-to-reorder: only allow moves within the same wearable type,
+ // and translate a drop into a layer-order change.
+ bool canReorderClothing(const LLSD& dragged_value, const LLSD& neighbour_value);
+ void onClothingReordered(const LLSD& dragged_value, S32 new_index);
+ void onReorderEnded();
+
LLPanelClothingListItem* buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last);
LLPanelBodyPartsListItem* buildBodypartListItem(LLViewerInventoryItem* item);
LLPanelDeletableWearableListItem* buildAttachemntListItem(LLViewerInventoryItem* item);
@@ -132,6 +138,9 @@ protected:
/* COF category version since last refresh */
S32 mCOFVersion;
+
+ /* a refresh arrived mid-drag and was skipped; re-run it when the drag ends */
+ bool mRefreshPending;
};
diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp
index da9378ad12..18123e07a8 100644
--- a/indra/newview/lldrawable.cpp
+++ b/indra/newview/lldrawable.cpp
@@ -664,10 +664,15 @@ F32 LLDrawable::updateXform(bool undamped)
// snap to final position (only if no target omega is applied)
dist_squared = 0.0f;
//set target scale here, because of dist_squared = 0.0f remove object from move list
- mCurrentScale = target_scale;
-
- if (getVOVolume() && !isRoot())
- { //child prim snapping to some position, needs a rebuild
+ if (mCurrentScale != target_scale)
+ {
+ mCurrentScale = target_scale;
+ // Final scale change needs a rebuild
+ gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION);
+ }
+ else if (getVOVolume() && !isRoot())
+ {
+ //child prim snapping to some position, needs a rebuild
gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION);
}
}
diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp
index 3eca6059ed..f5a93a4804 100644
--- a/indra/newview/lldrawpool.cpp
+++ b/indra/newview/lldrawpool.cpp
@@ -440,7 +440,11 @@ void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures)
LLDrawInfo* pparams = *i;
LLCullResult::increment_iterator(i, end);
- pushBatch(*pparams, texture, batch_textures);
+ llassert(pparams); // figure out how null got here, it shouldn't be happening
+ if (pparams)
+ {
+ pushBatch(*pparams, texture, batch_textures);
+ }
}
}
else
@@ -459,7 +463,10 @@ void LLRenderPass::pushUntexturedBatches(U32 type)
LLDrawInfo* pparams = *i;
LLCullResult::increment_iterator(i, end);
- pushUntexturedBatch(*pparams);
+ if (pparams)
+ {
+ pushUntexturedBatch(*pparams);
+ }
}
}
@@ -479,7 +486,7 @@ void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures
LLDrawInfo* pparams = *i;
LLCullResult::increment_iterator(i, end);
- if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin))
+ if (pparams && uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin))
{
pushBatch(*pparams, texture, batch_textures);
}
@@ -504,7 +511,7 @@ void LLRenderPass::pushUntexturedRiggedBatches(U32 type)
LLDrawInfo* pparams = *i;
LLCullResult::increment_iterator(i, end);
- if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin))
+ if (pparams && uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin))
{
pushUntexturedBatch(*pparams);
}
@@ -520,8 +527,11 @@ void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures)
{
LLDrawInfo* pparams = *i;
LLCullResult::increment_iterator(i, end);
- LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff);
- pushBatch(*pparams, texture, batch_textures);
+ if (pparams)
+ {
+ LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff);
+ pushBatch(*pparams, texture, batch_textures);
+ }
}
}
@@ -539,13 +549,16 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text
LLCullResult::increment_iterator(i, end);
- llassert(pparams);
-
- LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff);
+ llassert(pparams); // figure out how null got here, it shouldn't be happening
- if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin))
+ if (pparams)
{
- pushBatch(*pparams, texture, batch_textures);
+ LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff);
+
+ if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin))
+ {
+ pushBatch(*pparams, texture, batch_textures);
+ }
}
}
}
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index 9d1b11880b..8755120920 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -267,8 +267,8 @@ void LLDrawPoolAlpha::forwardRender(bool rigged)
gGL.setColorMask(true, false);
- if (!rigged && getType() == LLDrawPoolAlpha::POOL_ALPHA_POST_WATER)
- { //render "highlight alpha" on final non-rigged pass
+ if (!rigged && (LLPipeline::sRenderingHUDs || getType() == LLDrawPoolAlpha::POOL_ALPHA_POST_WATER))
+ { //render "highlight alpha" on final non-rigged pass for non-HUDs (HUDs only run pre-water alpha pass)
// NOTE -- hacky call here protected by !rigged instead of alongside "forwardRender"
// so renderDebugAlpha is executed while gls_pipeline_alpha and depth GL state
// variables above are still in scope
@@ -278,7 +278,7 @@ void LLDrawPoolAlpha::forwardRender(bool rigged)
void LLDrawPoolAlpha::renderDebugAlpha()
{
- if (sShowDebugAlpha && !gCubeSnapshot)
+ if (sShowDebugAlpha && !gCubeSnapshot && !LLPipeline::sReflectionRender)
{
gHighlightProgram.bind();
gGL.diffuseColor4f(1, 0, 0, 1);
diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp
index f0f589e7f4..9386206e76 100644
--- a/indra/newview/lldrawpoolavatar.cpp
+++ b/indra/newview/lldrawpoolavatar.cpp
@@ -595,7 +595,15 @@ void LLDrawPoolAvatar::beginSkinned()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
- // used for preview only
+ // Used for preview only, like LLVisualParamHint
+ // Uses deprecated logic!!!
+ if (!gAvatarProgram.isComplete())
+ {
+ llassert(false); // Avatar shader shouldn't have failed if deferred shader loaded
+ sVertexProgram = nullptr;
+ LLGLSLShader::unbind();
+ return;
+ }
sVertexProgram = &gAvatarProgram;
@@ -603,31 +611,26 @@ void LLDrawPoolAvatar::beginSkinned()
sVertexProgram->bind();
sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha);
+ sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
}
void LLDrawPoolAvatar::endSkinned()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
- // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done
- if (sShaderLevel > 0)
- {
- sRenderingSkinned = false;
- sVertexProgram->disableTexture(LLViewerShaderMgr::BUMP_MAP);
- gGL.getTexUnit(0)->activate();
- sVertexProgram->unbind();
- sShaderLevel = mShaderLevel;
- }
- else
+ if (sVertexProgram == nullptr)
{
- if(gPipeline.shadersLoaded())
- {
- // software skinning, use a basic shader for windlight.
- // TODO: find a better fallback method for software skinning.
- sVertexProgram->unbind();
- }
+ return;
}
+ // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done
+ sRenderingSkinned = false;
+ sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
+ sVertexProgram->disableTexture(LLViewerShaderMgr::BUMP_MAP);
+ gGL.getTexUnit(0)->activate();
+ sVertexProgram->unbind();
+ sShaderLevel = mShaderLevel;
+
gGL.getTexUnit(0)->activate();
}
diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp
index 9eddec55c5..836ff63429 100644
--- a/indra/newview/lleventpoll.cpp
+++ b/indra/newview/lleventpoll.cpp
@@ -331,26 +331,51 @@ namespace Details
{
// LLSD is too smart for it's own good and may act like a smart
// pointer for the content of (*i), so instead of passing (*i)
- // pass a prepared name and move ownership of "body",
- // as we are not going to need "body" anywhere else.
+ // pass a prepared name and copy the body
std::string msg_name = (*i)["message"].asString();
- // WARNING: This is a shallow copy!
- // If something still retains the data (like in httpAdapter?) this might still
- // result in a crash, if it does appear to be the case, make a deep copy or
- // convert data to string and pass that string.
- const LLSD body = (*i)["body"];
- (*i)["body"].clear();
- work = [this, msg_name, body]()
+ // Create a deep copy using binary serialization
+ try
{
- handleMessage(msg_name, body);
- };
+ std::stringstream body_stream;
+ LLSDSerialize::toBinary((*i)["body"], body_stream);
+ std::string body_str = body_stream.str();
+ (*i)["body"].clear();
+ work = [this, msg_name, body_str]()
+ {
+ try
+ {
+ LLSD body;
+ std::istringstream istr(body_str);
+ LLSDSerialize::fromBinary(body, istr, body_str.size());
+ handleMessage(msg_name, body);
+ }
+ catch (std::bad_alloc&)
+ {
+ LLError::LLUserWarningMsg::showOutOfMemory();
+ LL_ERRS("LLCoros") << "Bad memory allocation in handleMessage() for " << msg_name << LL_ENDL;
+ }
+ };
+ }
+ catch (std::bad_alloc&)
+ {
+ LLError::LLUserWarningMsg::showOutOfMemory();
+ LL_ERRS("LLCoros") << "Bad memory allocation in eventPollCoro for " << msg_name << LL_ENDL;
+ }
}
main_queue->post(work);
}
else
{
- handleMessage(*i);
+ try
+ {
+ handleMessage(*i);
+ }
+ catch (std::bad_alloc&)
+ {
+ LLError::LLUserWarningMsg::showOutOfMemory();
+ LL_ERRS("LLCoros") << "Bad memory allocation in handleMessage() for " << (*i)["message"].asString() << LL_ENDL;
+ }
}
}
}
diff --git a/indra/newview/llfilepicker_mac.mm b/indra/newview/llfilepicker_mac.mm
index 99e93bafbf..3a0bc4005e 100644
--- a/indra/newview/llfilepicker_mac.mm
+++ b/indra/newview/llfilepicker_mac.mm
@@ -26,15 +26,28 @@
#ifdef LL_DARWIN
#import <Cocoa/Cocoa.h>
+#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <iostream>
#include "llfilepicker_mac.h"
+// Convert a file extension or UTI string into a UTType for use with
+// NSOpenPanel/NSSavePanel's allowedContentTypes.
+static UTType *contentTypeForString(NSString *typeString)
+{
+ UTType *type = [UTType typeWithFilenameExtension:typeString];
+ if (!type)
+ {
+ type = [UTType typeWithIdentifier:typeString];
+ }
+ return type;
+}
+
NSOpenPanel *init_panel(const std::vector<std::string>* allowed_types, unsigned int flags)
{
int i;
NSOpenPanel *panel = [NSOpenPanel openPanel];
- NSMutableArray *fileTypes = nil;
+ NSMutableArray<UTType *> *fileTypes = nil;
if ( allowed_types && !allowed_types->empty())
@@ -43,9 +56,13 @@ NSOpenPanel *init_panel(const std::vector<std::string>* allowed_types, unsigned
for (i=0;i<allowed_types->size();++i)
{
- [fileTypes addObject:
- [NSString stringWithCString:(*allowed_types)[i].c_str()
- encoding:[NSString defaultCStringEncoding]]];
+ NSString *typeString = [NSString stringWithCString:(*allowed_types)[i].c_str()
+ encoding:[NSString defaultCStringEncoding]];
+ UTType *type = contentTypeForString(typeString);
+ if (type)
+ {
+ [fileTypes addObject:type];
+ }
}
}
@@ -57,9 +74,9 @@ NSOpenPanel *init_panel(const std::vector<std::string>* allowed_types, unsigned
[panel setCanChooseFiles: ( (flags & F_FILE)?true:false )];
[panel setTreatsFilePackagesAsDirectories: ( flags & F_NAV_SUPPORT ) ];
- if (fileTypes)
+ if (fileTypes && fileTypes.count > 0)
{
- [panel setAllowedFileTypes:fileTypes];
+ [panel setAllowedContentTypes:fileTypes];
}
else
{
@@ -196,12 +213,25 @@ std::unique_ptr<std::string> doSaveDialog(const std::string* file,
NSSavePanel *panel = [NSSavePanel savePanel];
NSString *extensionns = [NSString stringWithCString:extension->c_str() encoding:[NSString defaultCStringEncoding]];
- NSArray *fileType = [extensionns componentsSeparatedByString:@","];
+ NSArray *extensions = [extensionns componentsSeparatedByString:@","];
+
+ NSMutableArray<UTType *> *fileType = [[NSMutableArray alloc] init];
+ for (NSString *ext in extensions)
+ {
+ UTType *type = contentTypeForString(ext);
+ if (type)
+ {
+ [fileType addObject:type];
+ }
+ }
//[panel setMessage:@"Save Image File"];
[panel setTreatsFilePackagesAsDirectories: ( flags & F_NAV_SUPPORT ) ];
[panel setCanSelectHiddenExtension:true];
- [panel setAllowedFileTypes:fileType];
+ if (fileType.count > 0)
+ {
+ [panel setAllowedContentTypes:fileType];
+ }
NSString *fileName = [NSString stringWithCString:file->c_str() encoding:[NSString defaultCStringEncoding]];
NSURL* url = [NSURL fileURLWithPath:fileName];
@@ -231,12 +261,25 @@ void doSaveDialogModeless(const std::string* file,
NSSavePanel *panel = [NSSavePanel savePanel];
NSString *extensionns = [NSString stringWithCString:extension->c_str() encoding:[NSString defaultCStringEncoding]];
- NSArray *fileType = [extensionns componentsSeparatedByString:@","];
+ NSArray *extensions = [extensionns componentsSeparatedByString:@","];
+
+ NSMutableArray<UTType *> *fileType = [[NSMutableArray alloc] init];
+ for (NSString *ext in extensions)
+ {
+ UTType *type = contentTypeForString(ext);
+ if (type)
+ {
+ [fileType addObject:type];
+ }
+ }
//[panel setMessage:@"Save Image File"];
[panel setTreatsFilePackagesAsDirectories: ( flags & F_NAV_SUPPORT ) ];
[panel setCanSelectHiddenExtension:true];
- [panel setAllowedFileTypes:fileType];
+ if (fileType.count > 0)
+ {
+ [panel setAllowedContentTypes:fileType];
+ }
NSString *fileName = [NSString stringWithCString:file->c_str() encoding:[NSString defaultCStringEncoding]];
NSURL* url = [NSURL fileURLWithPath:fileName];
diff --git a/indra/newview/llfloaterbuycurrency.cpp b/indra/newview/llfloaterbuycurrency.cpp
index e41f893c43..506cff31f4 100644
--- a/indra/newview/llfloaterbuycurrency.cpp
+++ b/indra/newview/llfloaterbuycurrency.cpp
@@ -316,16 +316,23 @@ void LLFloaterBuyCurrency::handleBuyCurrency(bool has_piof, bool has_target, con
if (has_piof)
{
LLFloaterBuyCurrencyUI* ui = LLFloaterReg::showTypedInstance<LLFloaterBuyCurrencyUI>("buy_currency");
- if (has_target)
+ if (ui)
{
- ui->target(name, price);
+ if (has_target)
+ {
+ ui->target(name, price);
+ }
+ else
+ {
+ ui->noTarget();
+ }
+ ui->updateUI();
+ ui->collapsePanels(!has_target);
}
else
{
- ui->noTarget();
+ LL_WARNS() << "Cannot instantiate buy_currency floater" << LL_ENDL;
}
- ui->updateUI();
- ui->collapsePanels(!has_target);
}
else
{
diff --git a/indra/newview/llfloatercolorpicker.cpp b/indra/newview/llfloatercolorpicker.cpp
index 56e086502a..520206fd5c 100644
--- a/indra/newview/llfloatercolorpicker.cpp
+++ b/indra/newview/llfloatercolorpicker.cpp
@@ -32,6 +32,7 @@
#include "lltoolmgr.h"
#include "lltoolpipette.h"
#include "llviewercontrol.h"
+#include "llviewerwindow.h"
#include "llworld.h"
// Linden library includes
@@ -45,6 +46,7 @@
#include "lllineeditor.h"
#include "v4coloru.h"
#include "llbutton.h"
+#include "llcombobox.h"
#include "lluictrlfactory.h"
#include "llgl.h"
#include "llpointer.h"
@@ -76,7 +78,7 @@ LLFloaterColorPicker::LLFloaterColorPicker (LLColorSwatchCtrl* swatch, bool show
mMouseDownInSwatch ( false ),
// *TODO: Specify this in XML
mRGBViewerImageLeft ( 140 ),
- mRGBViewerImageTop ( 356 ),
+ mRGBViewerImageTop ( 436 ),
mRGBViewerImageWidth ( 256 ),
mRGBViewerImageHeight ( 256 ),
mLumRegionLeft ( mRGBViewerImageLeft + mRGBViewerImageWidth + 16 ),
@@ -86,18 +88,18 @@ LLFloaterColorPicker::LLFloaterColorPicker (LLColorSwatchCtrl* swatch, bool show
mLumMarkerSize ( 6 ),
// *TODO: Specify this in XML
mSwatchRegionLeft ( 12 ),
- mSwatchRegionTop ( 190 ),
+ mSwatchRegionTop ( 249 ),
mSwatchRegionWidth ( 116 ),
mSwatchRegionHeight ( 60 ),
mSwatchView ( NULL ),
// *TODO: Specify this in XML
numPaletteColumns ( 16 ),
- numPaletteRows ( 2 ),
+ numPaletteRows ( 4 ),
highlightEntry ( -1 ),
mPaletteRegionLeft ( 11 ),
- mPaletteRegionTop ( 100 - 8 ),
+ mPaletteRegionTop ( 180 - 8 ),
mPaletteRegionWidth ( mLumRegionLeft + mLumRegionWidth - 10 ),
- mPaletteRegionHeight ( 40 ),
+ mPaletteRegionHeight ( 120 ),
mSwatch ( swatch ),
mActive ( true ),
mCanApplyImmediately ( show_apply_immediate ),
@@ -154,7 +156,7 @@ void LLFloaterColorPicker::createUI ()
// create palette
for ( S32 each = 0; each < numPaletteColumns * numPaletteRows; ++each )
{
- mPalette.push_back(new LLColor4(LLUIColorTable::instance().getColor(llformat("ColorPaletteEntry%02d", each + 1))));
+ mPalette.push_back(new LLColor4(LLUIColorTable::instance().getColor(llformat("ColorPaletteEntry%02d", each + 1), LLColor4::grey)));
}
}
@@ -232,6 +234,7 @@ bool LLFloaterColorPicker::postBuild()
childSetCommitCallback("hspin", onTextCommit, (void*)this );
childSetCommitCallback("sspin", onTextCommit, (void*)this );
childSetCommitCallback("lspin", onTextCommit, (void*)this );
+ childSetCommitCallback("hex_color", onTextCommit, (void*)this );
mPipetteConnection = LLToolPipette::getInstance()->setToolSelectCallback(
[this](LLPointer<LLViewerObject> object, S32 te_index)
@@ -243,6 +246,9 @@ bool LLFloaterColorPicker::postBuild()
}
});
+ mCopyColorAsCombo = getChild<LLComboBox>("copy_color_combobox");
+ mCopyColorAsCombo->setCommitCallback(boost::bind(&LLFloaterColorPicker::onCopyColor, this));
+
return true;
}
@@ -678,6 +684,61 @@ void LLFloaterColorPicker::drawPalette ()
}
//////////////////////////////////////////////////////////////////////////////
+// Convert input RGB (0..1.0) to a hex color string
+std::string rgbFloatToHex(float r, float g, float b)
+{
+ auto toUint8 = [](float val) -> unsigned char {
+ return static_cast<unsigned char>(std::round(std::clamp(val * 255.0f, 0.0f, 255.0f)));
+ };
+
+ std::ostringstream oss;
+ oss << std::hex << std::uppercase << std::setfill('0');
+ oss << std::setw(2) << static_cast<int>(toUint8(r));
+ oss << std::setw(2) << static_cast<int>(toUint8(g));
+ oss << std::setw(2) << static_cast<int>(toUint8(b));
+
+ return oss.str();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Boolean test if input string is a valid hex color string
+bool LLFloaterColorPicker::isValidHexColor(std::string& hex_color, F32& hr, F32& hg, F32& hb)
+{
+ // Strip any whitespace and a leading # character if present
+ // (Often included in hex color strings from other places)
+ LLStringUtil::trim(hex_color);
+ if (!hex_color.empty() && hex_color.front() == '#')
+ {
+ hex_color = hex_color.substr(1);
+ }
+
+ // Make sure it's a real string and valid hex - we can't use the
+ // hex to dec code because that coerces invalid hex into decimal 0
+ if (hex_color.length() ==0 || hex_color.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos)
+ {
+ return false;
+ }
+
+ // Convert the hex string to a decimal number
+ std::stringstream oss;
+ oss << std::hex << hex_color;
+ unsigned int dec_val;
+ oss >> dec_val;
+
+ // Break out the RGB values in 0..1.0 range and pass back
+ // (They will only be used if this function returns true.)
+ hr = F32(((dec_val >> 16) & 0xff)) / 255.0f;
+ hg = F32(((dec_val >> 8) & 0xff)) / 255.0f;
+ hb = F32(((dec_val >> 0) & 0xff)) / 255.0f;
+
+ // The max chars in the field is more than 6 now so we can paste in
+ // poorly formatted color strings and reformat them on commit.
+ // TODO: there must be a way to to the preformat when the string
+ // is pasted into the line editor control.
+ return dec_val > 0xffffff ? false : true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
// update text entry values for RGB/HSL (can't be done in ::draw () since this overwrites input
void LLFloaterColorPicker::updateTextEntry ()
{
@@ -688,6 +749,8 @@ void LLFloaterColorPicker::updateTextEntry ()
getChild<LLUICtrl>("hspin")->setValue(( getCurH () * 360.0f ) );
getChild<LLUICtrl>("sspin")->setValue(( getCurS () * 100.0f ) );
getChild<LLUICtrl>("lspin")->setValue(( getCurL () * 100.0f ) );
+
+ getChild<LLUICtrl>("hex_color")->setValue(rgbFloatToHex(curR, curG, curB));
}
//////////////////////////////////////////////////////////////////////////////
@@ -745,6 +808,22 @@ void LLFloaterColorPicker::onTextEntryChanged ( LLUICtrl* ctrl )
selectCurHsl ( hVal, sVal, lVal );
updateTextEntry ();
+ } else
+ if (name == "hex_color")
+ {
+ F32 rVal, gVal, bVal;
+ std::string hex = ctrl->getValue().asString();
+ if (isValidHexColor(hex, rVal, gVal, bVal))
+ {
+ // Valid hex color - update the picker with specified value
+ selectCurRgb ( rVal, gVal, bVal );
+ updateTextEntry ();
+ }
+ else
+ {
+ // Invalid hex color provided - update the hex color field with current color
+ updateTextEntry ();
+ }
}
}
@@ -1096,3 +1175,74 @@ void LLFloaterColorPicker::stopUsingPipette()
LLToolMgr::getInstance()->clearTransientTool();
}
}
+
+void LLFloaterColorPicker::onCopyColor()
+{
+ LLComboBox* combo = getChild<LLComboBox>("copy_color_combobox");
+ S32 combo_val = combo->getSelectedValue().asInteger();
+
+ std::ostringstream to_copy;
+
+ // LSL - e.g. <0.571, 0.929, 0.585>
+ if (combo_val == 1)
+ {
+ to_copy << "<";
+ to_copy << std::setfill('0') << std::setprecision(3) << ((getCurR() < 0.001) ? 0.0 : getCurR());
+ to_copy << ", ";
+ to_copy << std::setfill('0') << std::setprecision(3) << ((getCurG() < 0.001) ? 0.0 : getCurG());
+ to_copy << ", ";
+ to_copy << std::setfill('0') << std::setprecision(3) << ((getCurB() < 0.001) ? 0.0 : getCurB());
+ to_copy << ">";
+ } else
+ // HEX e.g. #92ED95
+ if (combo_val == 2)
+ {
+ to_copy << "#";
+ to_copy << rgbFloatToHex(getCurR(), getCurG(), getCurB());
+ } else
+ // RGB e.g. (146, 237, 149)
+ if (combo_val == 3)
+ {
+ to_copy << "(";
+ to_copy << (S32)(getCurR() * 255.0f + 0.5f);
+ to_copy << ", ";
+ to_copy << (S32)(getCurG() * 255.0f + 0.5f);
+ to_copy << ", ";
+ to_copy << (S32)(getCurB() * 255.0f + 0.5f);
+ to_copy << ")";
+ } else
+ // HSL e.g. (122°, 71%, 75%)
+ if (combo_val == 4)
+ {
+ to_copy << "(";
+ to_copy << (S32)(getCurH() * 360.0f + 0.5f);
+ to_copy << "\xC2\xB0, ";
+ to_copy << (S32)(getCurS() * 100.0f + 0.5f);
+ to_copy << "%, ";
+ to_copy << (S32)(getCurL() * 100.0f + 0.5f);
+ to_copy << "%)";
+ }
+ // CMYK e.g. (38%, 0%, 37%, 7%)
+ if (combo_val == 5)
+ {
+ F32 k = 1.0f - std::max({getCurR(), getCurG(), getCurB()});
+ F32 c = (k == 1.0f) ? 0.0f : (1.0f - getCurR() - k) / (1.0f - k);
+ F32 m = (k == 1.0f) ? 0.0f : (1.0f - getCurG() - k) / (1.0f - k);
+ F32 y = (k == 1.0f) ? 0.0f : (1.0f - getCurB() - k) / (1.0f - k);
+ to_copy << "(";
+ to_copy << (S32)(c * 100.0f + 0.5f);
+ to_copy << "%, ";
+ to_copy << (S32)(m * 100.0f + 0.5f);
+ to_copy << "%, ";
+ to_copy << (S32)(y * 100.0f + 0.5f);
+ to_copy << "%, ";
+ to_copy << (S32)(k * 100.0f + 0.5f);
+ to_copy << "%";
+ to_copy << ")";
+ }
+
+ if (! to_copy.str().empty())
+ {
+ gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(to_copy.str()));
+ }
+}
diff --git a/indra/newview/llfloatercolorpicker.h b/indra/newview/llfloatercolorpicker.h
index af199d92a3..93f67f40ea 100644
--- a/indra/newview/llfloatercolorpicker.h
+++ b/indra/newview/llfloatercolorpicker.h
@@ -37,6 +37,7 @@
class LLButton;
class LLLineEditor;
class LLCheckBoxCtrl;
+class LLComboBox;
//////////////////////////////////////////////////////////////////////////////
// floater class
@@ -127,6 +128,10 @@ class LLFloaterColorPicker
// mutators for color values, can raise event to preview changes at object
void selectCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn );
void selectCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn );
+
+ // utility functions for manipulating hex colors
+ bool isValidHexColor(std::string& hex_color, F32& hr, F32& hg, F32& hb);
+
// draws color selection palette
void drawPalette ();
@@ -192,6 +197,9 @@ class LLFloaterColorPicker
LLButton* mPipetteBtn;
+ LLComboBox* mCopyColorAsCombo;
+ void onCopyColor();
+
F32 mContextConeOpacity;
F32 mContextConeInAlpha;
F32 mContextConeOutAlpha;
diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp
index ab76f3473b..d8dd30c9cc 100644
--- a/indra/newview/llfloateremojipicker.cpp
+++ b/indra/newview/llfloateremojipicker.cpp
@@ -1001,6 +1001,9 @@ bool LLFloaterEmojiPicker::moveFocusedIconNext()
if (mHoveredIcon)
return false;
+ if (mFocusedIconRow < 0 || static_cast<size_t>(mFocusedIconRow) >= mEmojiGrid->getPanelList().size())
+ return false;
+
LLScrollingPanel* panel = mEmojiGrid->getPanelList()[mFocusedIconRow];
LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel);
S32 colCount = row ? static_cast<S32>(row->mList->getPanelList().size()) : 0;
diff --git a/indra/newview/llfloatergestureautocompletepicker.cpp b/indra/newview/llfloatergestureautocompletepicker.cpp
new file mode 100644
index 0000000000..c7c4e46459
--- /dev/null
+++ b/indra/newview/llfloatergestureautocompletepicker.cpp
@@ -0,0 +1,154 @@
+/**
+ * @file llfloatergestureautocompletepicker.cpp
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2026, 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$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfloatergestureautocompletepicker.h"
+
+#include "llgestureautocompletehelper.h"
+#include "llscrolllistctrl.h"
+#include "llscrolllistitem.h"
+
+LLFloaterGestureAutocompletePicker::LLFloaterGestureAutocompletePicker(const LLSD& key)
+: LLFloater(key), mGestureList(NULL)
+{
+ setFocusStealsFrontmost(false);
+ setBackgroundVisible(false);
+ setAutoFocus(false);
+}
+
+bool LLFloaterGestureAutocompletePicker::postBuild()
+{
+ mGestureList = getChild<LLScrollListCtrl>("gesture_list");
+ mGestureList->setCommitOnKeyboardMovement(false);
+ mGestureList->setCommitCallback(boost::bind(&LLFloaterGestureAutocompletePicker::commitSelected, this));
+
+ return LLFloater::postBuild();
+}
+
+void LLFloaterGestureAutocompletePicker::onOpen(const LLSD& key)
+{
+ LLGestureAutocompleteHelper& helper = LLGestureAutocompleteHelper::instance();
+ mGestureList->clearRows();
+
+ const std::vector<LLGestureAutocompleteHelper::Row>& rows = helper.rows();
+
+ for (const auto& row : rows)
+ {
+ LLSD element;
+ element["value"] = row.value;
+ element["columns"][0]["column"] = "trigger";
+ element["columns"][0]["value"] = row.trigger;
+ element["columns"][1]["column"] = "name";
+ element["columns"][1]["value"] = row.name;
+ mGestureList->addElement(element);
+ }
+
+ if (helper.total() > rows.size())
+ {
+ LLSD element;
+ element["enabled"] = false;
+ element["columns"][0]["column"] = "trigger";
+ element["columns"][0]["value"] = LLStringUtil::null;
+ element["columns"][1]["column"] = "name";
+
+ LLStringUtil::format_map_t args;
+ args["[COUNT]"] = llformat("%d", (S32)rows.size());
+ args["[TOTAL]"] = llformat("%d", (S32)helper.total());
+ element["columns"][1]["value"] = getString("showing_count", args);
+
+ mGestureList->addElement(element);
+ }
+
+ mGestureList->selectFirstItem();
+ gFloaterView->adjustToFitScreen(this, false);
+}
+
+bool LLFloaterGestureAutocompletePicker::handleKey(KEY key, MASK mask, bool called_from_parent)
+{
+ if (mask == MASK_NONE)
+ {
+ switch (key)
+ {
+ case KEY_UP:
+ mGestureList->selectPrevItem();
+ mGestureList->scrollToShowSelected();
+ return true;
+ case KEY_DOWN:
+ mGestureList->selectNextItem();
+ mGestureList->scrollToShowSelected();
+ return true;
+ case KEY_RETURN:
+ case KEY_TAB:
+ commitSelected();
+ return true;
+ case KEY_ESCAPE:
+ LLGestureAutocompleteHelper::instance().hideHelper();
+ return true;
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ return true;
+ default:
+ break;
+ }
+ }
+
+ return LLFloater::handleKey(key, mask, called_from_parent);
+}
+
+void LLFloaterGestureAutocompletePicker::onClose(bool app_quitting)
+{
+ if (!app_quitting)
+ {
+ LLGestureAutocompleteHelper::instance().hideHelper();
+ }
+}
+
+void LLFloaterGestureAutocompletePicker::goneFromFront()
+{
+ LLGestureAutocompleteHelper::instance().hideHelper();
+}
+
+bool LLFloaterGestureAutocompletePicker::commitSelected()
+{
+ LLScrollListItem* item = mGestureList->getFirstSelected();
+
+ if (!item || !item->getEnabled())
+ {
+ return false;
+ }
+
+ const std::string value = mGestureList->getSelectedValue().asString();
+
+ if (value.empty())
+ {
+ return false;
+ }
+
+ setValue(value);
+ onCommit();
+
+ return true;
+}
diff --git a/indra/newview/llfloaterhowto.h b/indra/newview/llfloatergestureautocompletepicker.h
index 9d7793817a..71f754138d 100644
--- a/indra/newview/llfloaterhowto.h
+++ b/indra/newview/llfloatergestureautocompletepicker.h
@@ -1,10 +1,9 @@
/**
- * @file llfloaterhowto.h
- * @brief A variant of web floater meant to open guidebook
+ * @file llfloatergestureautocompletepicker.h
*
- * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2021, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -24,35 +23,25 @@
* $/LicenseInfo$
*/
-#ifndef LL_LLFLOATERHOWTO_H
-#define LL_LLFLOATERHOWTO_H
+#pragma once
-#include "llfloaterwebcontent.h"
+#include "llfloater.h"
-class LLMediaCtrl;
+class LLScrollListCtrl;
-
-class LLFloaterHowTo :
- public LLFloaterWebContent
+class LLFloaterGestureAutocompletePicker : public LLFloater
{
public:
- LOG_CLASS(LLFloaterHowTo);
-
- typedef LLFloaterWebContent::Params Params;
-
- LLFloaterHowTo(const Params& key);
+ LLFloaterGestureAutocompletePicker(const LLSD& key);
+ bool postBuild() override;
void onOpen(const LLSD& key) override;
-
- bool handleKeyHere(KEY key, MASK mask) override;
-
- static LLFloaterHowTo* getInstance();
-
- bool matchesKey(const LLSD& key) override { return true; /*single instance*/ };
+ bool handleKey(KEY key, MASK mask, bool called_from_parent) override;
+ void onClose(bool app_quitting) override;
+ void goneFromFront() override;
private:
- bool postBuild() override;
-};
-
-#endif // LL_LLFLOATERHOWTO_H
+ bool commitSelected();
+ LLScrollListCtrl* mGestureList;
+};
diff --git a/indra/newview/llfloaterhowto.cpp b/indra/newview/llfloaterhowto.cpp
deleted file mode 100644
index 6a9f113d53..0000000000
--- a/indra/newview/llfloaterhowto.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * @file llfloaterhowto.cpp
- * @brief A variant of web floater meant to open guidebook
- *
- * $LicenseInfo:firstyear=2021&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2021, 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$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "llfloaterhowto.h"
-
-#include "llfloaterreg.h"
-#include "llviewercontrol.h"
-#include "llweb.h"
-
-
-constexpr S32 STACK_WIDTH = 300;
-constexpr S32 STACK_HEIGHT = 505; // content will be 500
-
-LLFloaterHowTo::LLFloaterHowTo(const Params& key) :
- LLFloaterWebContent(key)
-{
- mShowPageTitle = false;
-}
-
-bool LLFloaterHowTo::postBuild()
-{
- LLFloaterWebContent::postBuild();
-
- return true;
-}
-
-void LLFloaterHowTo::onOpen(const LLSD& key)
-{
- LLFloaterWebContent::Params p(key);
- if (!p.url.isProvided() || p.url.getValue().empty())
- {
- std::string url = gSavedSettings.getString("GuidebookURL");
- p.url = LLWeb::expandURLSubstitutions(url, LLSD());
- }
- p.show_chrome = false;
-
- LLFloaterWebContent::onOpen(p);
-
- if (p.preferred_media_size().isEmpty())
- {
- // Elements from LLFloaterWebContent did not pick up restored size (save_rect) of LLFloaterHowTo
- // set the stack size and position (alternative to preferred_media_size)
- LLLayoutStack *stack = getChild<LLLayoutStack>("stack1");
- LLRect stack_rect = stack->getRect();
- stack->reshape(STACK_WIDTH, STACK_HEIGHT);
- stack->setOrigin(stack_rect.mLeft, stack_rect.mTop - STACK_HEIGHT);
- stack->updateLayout();
- }
-}
-
-LLFloaterHowTo* LLFloaterHowTo::getInstance()
-{
- return LLFloaterReg::getTypedInstance<LLFloaterHowTo>("guidebook");
-}
-
-bool LLFloaterHowTo::handleKeyHere(KEY key, MASK mask)
-{
- bool handled = false;
-
- if (KEY_F1 == key )
- {
- closeFloater();
- handled = true;
- }
-
- return handled;
-}
diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp
index f4a22c6728..c2a1a306fe 100644
--- a/indra/newview/llfloaterimcontainer.cpp
+++ b/indra/newview/llfloaterimcontainer.cpp
@@ -155,18 +155,36 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const
{
// The general strategy when a session id is modified is to delete all related objects and create them anew.
+ // The conversation floater can be active while participant widgets are selected.
+ // Preserve that state separately so the new conversation widget can be selected
+ // after the server replaces an outgoing ad-hoc session id.
+ const bool was_active = old_session_id == getSelectedSession();
+
// Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived()
// and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress
// its related mSessions record as it's indexed with the wrong id.
- // Grabbing the updated LLFloaterIMSession and readding it in mSessions will eventually be done by addConversationListItem().
mSessions.erase(old_session_id);
- // Delete the model and participants related to the old session
- bool change_focus = removeConversationListItem(old_session_id);
+ // Remove the old conversation widget without changing focus - we'll immediately re-add with
+ // the new id and select it, avoiding an unnecessary focus switch to an adjacent conversation.
+ bool was_selected = removeConversationListItem(old_session_id, false);
// Create a new conversation with the new id
- addConversationListItem(new_session_id, change_focus);
+ addConversationListItem(new_session_id, was_selected);
LLFloaterIMSessionTab::addToHost(new_session_id);
+
+ // addToHost is a no-op for already-hosted floaters, so mSessions won't be
+ // updated by addFloater. Re-register manually so message flash works.
+ LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::findConversation(new_session_id);
+ if (conversp && mSessions.find(new_session_id) == mSessions.end())
+ {
+ mSessions[new_session_id] = conversp;
+ }
+
+ if (was_active)
+ {
+ selectConversationPair(new_session_id, true, false, true);
+ }
}
diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp
index f0d696361a..860535a28f 100644
--- a/indra/newview/llfloaterimnearbychat.cpp
+++ b/indra/newview/llfloaterimnearbychat.cpp
@@ -55,6 +55,7 @@
#include "llfloaterimnearbychatlistener.h"
#include "llagent.h" // gAgent
#include "llgesturemgr.h"
+#include "llgestureautocompletehelper.h"
#include "llmultigesture.h"
#include "llkeyboard.h"
#include "llanimationstates.h"
@@ -70,6 +71,8 @@
#include "llautoreplace.h"
#include "lluiusage.h"
+#include <map>
+
S32 LLFloaterIMNearbyChat::sLastSpecialChatChannel = 0;
static LLFloaterIMNearbyChatListener sChatListener;
@@ -77,6 +80,67 @@ static LLFloaterIMNearbyChatListener sChatListener;
constexpr S32 EXPANDED_HEIGHT = 266;
constexpr S32 COLLAPSED_HEIGHT = 60;
constexpr S32 EXPANDED_MIN_HEIGHT = 150;
+constexpr size_t MAX_GESTURE_AUTOCOMPLETE_ROWS = 50;
+
+namespace
+{
+bool buildGestureAutocompleteRows(
+ const std::string& prefix,
+ std::vector<LLGestureAutocompleteHelper::Row>& rows,
+ size_t& total)
+{
+ rows.clear();
+ total = 0;
+
+ // Wait for at least one character after the slash before offering matches.
+ if (prefix.size() < 2 || prefix[0] != '/' || prefix.find_first_of(" \t") != std::string::npos)
+ {
+ return false;
+ }
+
+ std::string lower_prefix = prefix;
+ LLStringUtil::toLower(lower_prefix);
+
+ std::map<std::string, std::string> unique;
+ const LLGestureMgr::item_map_t& active = LLGestureMgr::instance().getActiveGestures();
+
+ for (const auto& entry : active)
+ {
+ LLMultiGesture* gesture = entry.second;
+
+ if (!gesture || gesture->getTrigger().empty() || gesture->getTrigger()[0] != '/')
+ {
+ continue;
+ }
+
+ std::string lower_trigger = gesture->getTrigger();
+ LLStringUtil::toLower(lower_trigger);
+
+ if (lower_trigger.compare(0, lower_prefix.size(), lower_prefix) != 0)
+ {
+ continue;
+ }
+
+ unique.emplace(
+ gesture->getTrigger(),
+ gesture->mName);
+ }
+
+ for (const auto& [trigger, name] : unique)
+ {
+ if (rows.size() >= MAX_GESTURE_AUTOCOMPLETE_ROWS)
+ {
+ break;
+ }
+
+ rows.push_back({ trigger, trigger, name });
+ }
+
+ total = unique.size();
+
+ return total > 0;
+}
+}
// legacy callback glue
void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel);
@@ -501,8 +565,32 @@ void LLFloaterIMNearbyChat::onChatBoxKeystroke()
KEY key = gKeyboard->currentKey();
+ static LLCachedControl<bool> autocomplete_gestures(gSavedSettings, "ChatAutocompleteGestures", true);
+
+ if (autocomplete_gestures)
+ {
+ std::vector<LLGestureAutocompleteHelper::Row> rows;
+ size_t total = 0;
+ const std::string utf8_trigger = wstring_to_utf8str(raw_text);
+
+ if (buildGestureAutocompleteRows(utf8_trigger, rows, total))
+ {
+ LLGestureAutocompleteHelper::instance().showHelper(
+ mInputEditor,
+ rows,
+ total,
+ [this](std::string trigger)
+ {
+ mInputEditor->setText(trigger + " ");
+ mInputEditor->endOfDoc();
+ });
+ return;
+ }
+
+ LLGestureAutocompleteHelper::instance().hideHelper(mInputEditor);
+ }
// Ignore "special" keys, like backspace, arrows, etc.
- if (gSavedSettings.getBOOL("ChatAutocompleteGestures")
+ if (autocomplete_gestures
&& length > 1
&& raw_text[0] == '/'
&& key < KEY_SPECIAL)
@@ -589,6 +677,9 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type )
LLWString text = mInputEditor->getConvertedText();
LLWStringUtil::trim(text);
LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
+
+ LLGestureAutocompleteHelper::instance().hideHelper();
+
if (!text.empty())
{
// Check if this is destined for another channel
diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h
index fdc93d6fb4..d97b0eaa33 100644
--- a/indra/newview/llfloaterimsessiontab.h
+++ b/indra/newview/llfloaterimsessiontab.h
@@ -117,6 +117,8 @@ public:
virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override {}; // Stub
virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override {}; // Stub
+ bool isP2PSessionType() { return mIsP2PChat; }
+
protected:
// callback for click on any items of the visual states menu
diff --git a/indra/newview/llfloaterinspect.cpp b/indra/newview/llfloaterinspect.cpp
index 163edf0426..77a98943ac 100644
--- a/indra/newview/llfloaterinspect.cpp
+++ b/indra/newview/llfloaterinspect.cpp
@@ -78,18 +78,21 @@ LLFloaterInspect::~LLFloaterInspect(void)
{
mCreatorNameCacheConnection.disconnect();
}
- if(!LLFloaterReg::instanceVisible("build"))
+ if (!LLApp::isExiting())
{
- if(LLToolMgr::getInstance()->getBaseTool() == LLToolCompInspect::getInstance())
+ if (!LLFloaterReg::instanceVisible("build"))
{
- LLToolMgr::getInstance()->clearTransientTool();
+ if (LLToolMgr::getInstance()->getBaseTool() == LLToolCompInspect::getInstance())
+ {
+ LLToolMgr::getInstance()->clearTransientTool();
+ }
+ // Switch back to basic toolset
+ LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset);
+ }
+ else
+ {
+ LLFloaterReg::showInstance("build", LLSD(), true);
}
- // Switch back to basic toolset
- LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset);
- }
- else
- {
- LLFloaterReg::showInstance("build", LLSD(), true);
}
}
diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp
index ebc91255d9..3dc4e53250 100644
--- a/indra/newview/llfloaterland.cpp
+++ b/indra/newview/llfloaterland.cpp
@@ -454,9 +454,6 @@ bool LLPanelLandGeneral::postBuild()
mContentRating = getChild<LLTextBox>("ContentRatingText");
mLandType = getChild<LLTextBox>("LandTypeText");
- mBtnProfile = getChild<LLButton>("Profile...");
- mBtnProfile->setClickedCallback(boost::bind(&LLPanelLandGeneral::onClickProfile, this));
-
mTextGroupLabel = getChild<LLTextBox>("Group:");
mTextGroup = getChild<LLTextBox>("GroupText");
@@ -587,8 +584,6 @@ void LLPanelLandGeneral::refresh()
mTextOwner->setText(LLStringUtil::null);
mContentRating->setText(LLStringUtil::null);
mLandType->setText(LLStringUtil::null);
- mBtnProfile->setLabel(getString("profile_text"));
- mBtnProfile->setEnabled(false);
mTextClaimDate->setText(LLStringUtil::null);
mTextGroup->setText(LLStringUtil::null);
@@ -670,7 +665,6 @@ void LLPanelLandGeneral::refresh()
mTextSalePending->setEnabled(false);
mTextOwner->setText(getString("public_text"));
mTextOwner->setEnabled(false);
- mBtnProfile->setEnabled(false);
mTextClaimDate->setText(LLStringUtil::null);
mTextClaimDate->setEnabled(false);
mTextGroup->setText(getString("none_text"));
@@ -699,21 +693,14 @@ void LLPanelLandGeneral::refresh()
//refreshNames();
mTextOwner->setEnabled(true);
- // We support both group and personal profiles
- mBtnProfile->setEnabled(true);
-
if (parcel->getGroupID().isNull())
{
- // Not group owned, so "Profile"
- mBtnProfile->setLabel(getString("profile_text"));
mTextGroup->setText(getString("none_text"));
mTextGroup->setEnabled(false);
}
else
{
- // Group owned, so "Info"
- mBtnProfile->setLabel(getString("info_text"));
//mTextGroup->setText("HIPPOS!");//parcel->getGroupName());
mTextGroup->setEnabled(true);
@@ -947,23 +934,6 @@ void LLPanelLandGeneral::onClickSetGroup()
}
}
-void LLPanelLandGeneral::onClickProfile()
-{
- LLParcel* parcel = mParcel->getParcel();
- if (!parcel) return;
-
- if (parcel->getIsGroupOwned())
- {
- const LLUUID& group_id = parcel->getGroupID();
- LLGroupActions::show(group_id);
- }
- else
- {
- const LLUUID& avatar_id = parcel->getOwnerID();
- LLAvatarActions::showProfile(avatar_id);
- }
-}
-
// public
void LLPanelLandGeneral::setGroup(const LLUUID& group_id)
{
diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h
index 8af0caab33..79e5da042f 100644
--- a/indra/newview/llfloaterland.h
+++ b/indra/newview/llfloaterland.h
@@ -146,7 +146,6 @@ public:
virtual void draw();
void setGroup(const LLUUID& group_id);
- void onClickProfile();
void onClickSetGroup();
static void onClickDeed(void*);
static void onClickBuyLand(void* data);
@@ -193,7 +192,6 @@ protected:
LLTextBox* mTextOwnerLabel;
LLTextBox* mTextOwner;
- LLButton* mBtnProfile;
LLTextBox* mContentRating;
LLTextBox* mLandType;
diff --git a/indra/newview/llfloaterobjectweights.cpp b/indra/newview/llfloaterobjectweights.cpp
index bd8530abd7..cf89518c03 100644
--- a/indra/newview/llfloaterobjectweights.cpp
+++ b/indra/newview/llfloaterobjectweights.cpp
@@ -33,8 +33,10 @@
#include "lltextbox.h"
#include "llagent.h"
+#include "llcallbacklist.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"
+#include "llmeshrepository.h"
static const std::string lod_strings[4] =
{
@@ -74,6 +76,14 @@ bool LLCrossParcelFunctor::apply(LLViewerObject* obj)
LLFloaterObjectWeights::LLFloaterObjectWeights(const LLSD& key)
: LLFloater(key),
+ mWeightsDirty(false),
+ mSelectionDirty(true),
+ mSelectionMeshDirty(true),
+ mSelectionLastLOD(-1),
+ mSelectionLastTris(0),
+ mSelectionLastArea(0),
+ mLastActiveLODRequests(0),
+ mSelectionRefreshTime(0.),
mSelectedObjects(NULL),
mSelectedPrims(NULL),
mSelectedDownloadWeight(NULL),
@@ -88,6 +98,13 @@ LLFloaterObjectWeights::LLFloaterObjectWeights(const LLSD& key)
mTrianglesShown(nullptr),
mPixelArea(nullptr)
{
+ mSelectionConnection = LLSelectMgr::getInstance()->mUpdateSignal.connect(
+ [this]()
+ {
+ mSelectionDirty = true;
+ mSelectionMeshDirty = true; // assume that mesh data will arrive with a delay.
+ }
+ );
}
LLFloaterObjectWeights::~LLFloaterObjectWeights()
@@ -114,6 +131,11 @@ bool LLFloaterObjectWeights::postBuild()
mTrianglesShown = getChild<LLTextBox>("triangles_shown");
mPixelArea = getChild<LLTextBox>("pixel_area");
+ mHighLodTris = getChild<LLTextBox>("high_lod_tris");
+ mMediumLodTris = getChild<LLTextBox>("medium_lod_tris");
+ mLowLodTris = getChild<LLTextBox>("low_lod_tris");
+ mLowestLodTris = getChild<LLTextBox>("lowest_lod_tris");
+
return true;
}
@@ -122,6 +144,14 @@ void LLFloaterObjectWeights::onOpen(const LLSD& key)
{
refresh();
updateLandImpacts(LLViewerParcelMgr::getInstance()->getFloatingParcelSelection()->getParcel());
+
+ onIdleRefresh(this);
+ gIdleCallbacks.addFunction(onIdleRefresh, this);
+}
+
+void LLFloaterObjectWeights::onClose(bool app_quitting)
+{
+ gIdleCallbacks.deleteFunction(onIdleRefresh, this);
}
// virtual
@@ -131,8 +161,9 @@ void LLFloaterObjectWeights::onWeightsUpdate(const SelectionCost& selection_cost
mSelectedPhysicsWeight->setText(llformat("%.1f", selection_cost.mPhysicsCost));
mSelectedServerWeight->setText(llformat("%.1f", selection_cost.mSimulationCost));
- S32 render_cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectRenderCost();
- mSelectedDisplayWeight->setText(llformat("%d", render_cost));
+ // onWeightsUpdate comes from a coroutine.
+ // Postpone LLSelectMgr operations as getSelectedObjectRenderCost is not coroutine-safe
+ mWeightsDirty = true;
toggleWeightsLoadingIndicators(false);
}
@@ -152,20 +183,13 @@ void LLFloaterObjectWeights::setErrorStatus(S32 status, const std::string& reaso
void LLFloaterObjectWeights::draw()
{
- // Normally it's a bad idea to set text and visibility inside draw
- // since it can cause rect updates go to different, already drawn elements,
- // but floater is very simple and these elements are supposed to be isolated
+ // This logic might be a bit too expensive for draw(),
+ // but this floater needs to react fast, even if it's
+ // detrimental to performance. Having callbacks like
+ // 'tris changed' in every selected object might be
+ // more impactful on performance.
LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
- if (selection->isEmpty())
- {
- const std::string text = getString("nothing_selected");
- mLodLevel->setText(text);
- mTrianglesShown->setText(text);
- mPixelArea->setText(text);
-
- toggleRenderLoadingIndicators(false);
- }
- else
+ if (!selection->isEmpty() && !mSelectionDirty)
{
S32 object_lod = -1;
bool multiple_lods = false;
@@ -192,27 +216,71 @@ void LLFloaterObjectWeights::draw()
}
}
- if (multiple_lods)
+ if (mSelectionLastTris != total_tris)
{
- mLodLevel->setText(getString("multiple_lods"));
- toggleRenderLoadingIndicators(false);
+ mSelectionDirty = true;
}
- else if (object_lod < 0)
+ else if (mSelectionLastArea != pixel_area)
{
- // nodes are waiting for data
- toggleRenderLoadingIndicators(true);
+ mSelectionDirty = true;
}
- else
+ else if (multiple_lods)
{
- mLodLevel->setText(getString(lod_strings[object_lod]));
- toggleRenderLoadingIndicators(false);
+ mSelectionDirty |= mSelectionLastLOD != -1;
+ }
+ else if (mSelectionLastLOD != object_lod)
+ {
+ mSelectionDirty = true;
}
- mTrianglesShown->setText(llformat("%d", total_tris));
- mPixelArea->setText(llformat("%ld", (S64)pixel_area)); // value capped at 10M
}
LLFloater::draw();
}
+void LLFloaterObjectWeights::onIdleRefresh(void* user_data)
+{
+ LLFloaterObjectWeights* self = (LLFloaterObjectWeights*)user_data;
+
+ F64 current_time = LLTimer::getTotalSeconds();
+ S32 current_lod_requests = LLMeshRepoThread::sActiveLODRequests.load();
+ // Can't look for a specific mesh (no callback mechanics and we
+ // need multiple ones), so just update periodically if something loads
+ self->mSelectionMeshDirty |= (self->mLastActiveLODRequests > current_lod_requests);
+ bool throttle_elapsed = (current_time - self->mSelectionRefreshTime) >= 2.0;
+
+ if (self->mLastActiveLODRequests < current_lod_requests)
+ {
+ // we are interested in finished downloads,
+ // so don't refresh if more requests are getting added.
+ self->mLastActiveLODRequests = current_lod_requests;
+ }
+
+ if (self->mSelectionDirty)
+ {
+ // Always refresh on selection changes
+ self->refreshDataFromSelection();
+ self->mSelectionDirty = false;
+ self->mSelectionRefreshTime = current_time;
+ // refreshDataFromSelection could have indirectly initiated more requests
+ self->mLastActiveLODRequests = LLMeshRepoThread::sActiveLODRequests.load();
+ }
+ else if (self->mSelectionMeshDirty && throttle_elapsed)
+ {
+ // LOD requests count changed or mesh needs lod data reload
+ self->refreshDataFromSelection();
+ self->mSelectionRefreshTime = current_time;
+ self->mSelectionMeshDirty = false;
+ // refreshDataFromSelection could have indirectly initiated more requests
+ self->mLastActiveLODRequests = LLMeshRepoThread::sActiveLODRequests.load();
+ }
+ else if (self->mWeightsDirty) // also done in refreshDataFromSelection
+ {
+ LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
+ S32 render_cost = selection->getSelectedObjectRenderCost();
+ self->mSelectedDisplayWeight->setText(llformat("%d", render_cost));
+ self->mWeightsDirty = false;
+ }
+}
+
void LLFloaterObjectWeights::updateLandImpacts(const LLParcel* parcel)
{
if (!parcel || LLSelectMgr::getInstance()->getSelection()->isEmpty())
@@ -240,6 +308,101 @@ void LLFloaterObjectWeights::updateLandImpacts(const LLParcel* parcel)
}
}
+void LLFloaterObjectWeights::refreshDataFromSelection()
+{
+ LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
+ if (selection->isEmpty())
+ {
+ const std::string text = getString("nothing_selected");
+ mLodLevel->setText(text);
+ mTrianglesShown->setText(text);
+ mPixelArea->setText(text);
+
+ mHighLodTris->setText(text);
+ mMediumLodTris->setText(text);
+ mLowLodTris->setText(text);
+ mLowestLodTris->setText(text);
+
+ toggleRenderLoadingIndicators(false);
+ toggleLODLoadingIndicators(false);
+ }
+ else
+ {
+ S32 object_lod = -1;
+ bool multiple_lods = false;
+ S32 total_tris = 0;
+ F32 pixel_area = 0;
+ S32 high_tris = 0;
+ S32 medium_tris = 0;
+ S32 low_tris = 0;
+ S32 lowest_tris = 0;
+ for (LLObjectSelection::valid_root_iterator iter = selection->valid_root_begin();
+ iter != selection->valid_root_end(); ++iter)
+ {
+ LLViewerObject* object = (*iter)->getObject();
+ S32 lod = object->getLOD();
+ if (object_lod < 0)
+ {
+ object_lod = lod;
+ }
+ else if (object_lod != lod)
+ {
+ multiple_lods = true;
+ }
+
+ if (object->isRootEdit())
+ {
+ total_tris += object->recursiveGetTriangleCount();
+ pixel_area += object->getPixelArea();
+ object->recursiveGetLODTriangleCount(high_tris, medium_tris, low_tris, lowest_tris);
+ }
+ }
+
+ mSelectionLastTris = total_tris;
+ mSelectionLastArea = pixel_area;
+ if (multiple_lods)
+ {
+ mSelectionLastLOD = -1;
+ }
+ else
+ {
+ mSelectionLastLOD = object_lod;
+ }
+
+ if (multiple_lods)
+ {
+ mLodLevel->setText(getString("multiple_lods"));
+ toggleRenderLoadingIndicators(false);
+ toggleLODLoadingIndicators(false);
+ }
+ else if (object_lod < 0)
+ {
+ // nodes are waiting for data
+ toggleRenderLoadingIndicators(true);
+ toggleLODLoadingIndicators(true);
+ }
+ else
+ {
+ mLodLevel->setText(getString(lod_strings[object_lod]));
+ toggleRenderLoadingIndicators(false);
+ toggleLODLoadingIndicators(false);
+ }
+ mTrianglesShown->setText(llformat("%d", total_tris));
+ mPixelArea->setText(llformat("%ld", (S64)pixel_area)); // value capped at 10M
+ mHighLodTris->setText(llformat("%d", high_tris));
+ mMediumLodTris->setText(llformat("%d", medium_tris));
+ mLowLodTris->setText(llformat("%d", low_tris));
+ mLowestLodTris->setText(llformat("%d", lowest_tris));
+ }
+
+ if (mWeightsDirty)
+ {
+ S32 render_cost = selection->getSelectedObjectRenderCost();
+ mSelectedDisplayWeight->setText(llformat("%d", render_cost));
+ mWeightsDirty = false;
+ }
+}
+
void LLFloaterObjectWeights::refresh()
{
LLSelectMgr* sel_mgr = LLSelectMgr::getInstance();
@@ -341,6 +504,19 @@ void LLFloaterObjectWeights::toggleRenderLoadingIndicators(bool visible)
mPixelArea->setVisible(!visible);
}
+void LLFloaterObjectWeights::toggleLODLoadingIndicators(bool visible)
+{
+ childSetVisible("high_lod_tris_loading_indicator", visible);
+ childSetVisible("medium_lod_tris_loading_indicator", visible);
+ childSetVisible("low_lod_tris_loading_indicator", visible);
+ childSetVisible("lowest_lod_tris_loading_indicator", visible);
+
+ mHighLodTris->setVisible(!visible);
+ mMediumLodTris->setVisible(!visible);
+ mLowLodTris->setVisible(!visible);
+ mLowestLodTris->setVisible(!visible);
+}
+
void LLFloaterObjectWeights::updateIfNothingSelected()
{
const std::string text = getString("nothing_selected");
diff --git a/indra/newview/llfloaterobjectweights.h b/indra/newview/llfloaterobjectweights.h
index bda625564b..dac87d2a10 100644
--- a/indra/newview/llfloaterobjectweights.h
+++ b/indra/newview/llfloaterobjectweights.h
@@ -61,13 +61,17 @@ public:
bool postBuild() override;
void onOpen(const LLSD& key) override;
+ void onClose(bool app_quitting) override;
void onWeightsUpdate(const SelectionCost& selection_cost) override;
void setErrorStatus(S32 status, const std::string& reason) override;
void draw() override;
+ static void onIdleRefresh(void* user_data);
+
void updateLandImpacts(const LLParcel* parcel);
+ void refreshDataFromSelection();
void refresh() override;
private:
@@ -76,9 +80,19 @@ private:
void toggleWeightsLoadingIndicators(bool visible);
void toggleLandImpactsLoadingIndicators(bool visible);
void toggleRenderLoadingIndicators(bool visible);
+ void toggleLODLoadingIndicators(bool visible);
void updateIfNothingSelected();
+ bool mWeightsDirty;
+ bool mSelectionDirty;
+ bool mSelectionMeshDirty;
+ F64 mSelectionRefreshTime;
+ S32 mSelectionLastLOD;
+ S32 mSelectionLastTris;
+ F32 mSelectionLastArea;
+ S32 mLastActiveLODRequests;
+
LLTextBox *mSelectedObjects;
LLTextBox *mSelectedPrims;
@@ -95,6 +109,13 @@ private:
LLTextBox *mLodLevel;
LLTextBox *mTrianglesShown;
LLTextBox *mPixelArea;
+
+ LLTextBox *mHighLodTris;
+ LLTextBox *mMediumLodTris;
+ LLTextBox *mLowLodTris;
+ LLTextBox *mLowestLodTris;
+
+ boost::signals2::scoped_connection mSelectionConnection;
};
#endif //LL_LLFLOATEROBJECTWEIGHTS_H
diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp
index 7e7eb91636..6322dcd3c9 100644
--- a/indra/newview/llfloaterreporter.cpp
+++ b/indra/newview/llfloaterreporter.cpp
@@ -660,9 +660,9 @@ void LLFloaterReporter::showFromAvatar(const LLUUID& avatar_id, const std::strin
}
// static
-void LLFloaterReporter::showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description)
+void LLFloaterReporter::showFromChatObj(const LLUUID& object_id, const std::string& time, const std::string& description)
{
- show(avatar_id, avatar_name);
+ show(object_id, LLStringUtil::null, LLUUID::null);
LLStringUtil::format_map_t args;
args["[MSG_TIME]"] = time;
@@ -671,8 +671,25 @@ void LLFloaterReporter::showFromChat(const LLUUID& avatar_id, const std::string&
LLFloaterReporter *self = LLFloaterReg::findTypedInstance<LLFloaterReporter>("reporter");
if (self)
{
- std::string description = self->getString("chat_report_format", args);
- self->getChild<LLUICtrl>("details_edit")->setValue(description);
+ std::string description_frmt = self->getString("chat_report_format", args);
+ self->getChild<LLUICtrl>("details_edit")->setValue(description_frmt);
+ }
+}
+
+// static
+void LLFloaterReporter::showFromChatAv(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description)
+{
+ show(avatar_id, avatar_name);
+
+ LLStringUtil::format_map_t args;
+ args["[MSG_TIME]"] = time;
+ args["[MSG_DESCRIPTION]"] = description;
+
+ LLFloaterReporter* self = LLFloaterReg::findTypedInstance<LLFloaterReporter>("reporter");
+ if (self)
+ {
+ std::string description_frmt = self->getString("chat_report_format", args);
+ self->getChild<LLUICtrl>("details_edit")->setValue(description_frmt);
}
}
diff --git a/indra/newview/llfloaterreporter.h b/indra/newview/llfloaterreporter.h
index 31b05235a6..446e7d63b6 100644
--- a/indra/newview/llfloaterreporter.h
+++ b/indra/newview/llfloaterreporter.h
@@ -93,7 +93,8 @@ public:
static void showFromObject(const LLUUID& object_id, const LLUUID& experience_id = LLUUID::null);
static void showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name);
- static void showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description);
+ static void showFromChatObj(const LLUUID& object_id, const std::string& time, const std::string& description);
+ static void showFromChatAv(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description);
static void showFromExperience(const LLUUID& experience_id);
static void onClickSend (void *userdata);
diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp
index 83d7a92846..8fa279dace 100644
--- a/indra/newview/llfloatersnapshot.cpp
+++ b/indra/newview/llfloatersnapshot.cpp
@@ -96,13 +96,13 @@ LLSnapshotModel::ESnapshotFormat LLFloaterSnapshot::Impl::getImageFormat(LLFloat
LLSpinCtrl* LLFloaterSnapshot::Impl::getWidthSpinner(LLFloaterSnapshotBase* floater)
{
LLPanelSnapshot* active_panel = getActivePanel(floater);
- return active_panel ? active_panel->getWidthSpinner() : floater->getChild<LLSpinCtrl>("snapshot_width");
+ return active_panel ? active_panel->getWidthSpinner() : floater->findChild<LLSpinCtrl>("snapshot_width");
}
LLSpinCtrl* LLFloaterSnapshot::Impl::getHeightSpinner(LLFloaterSnapshotBase* floater)
{
LLPanelSnapshot* active_panel = getActivePanel(floater);
- return active_panel ? active_panel->getHeightSpinner() : floater->getChild<LLSpinCtrl>("snapshot_height");
+ return active_panel ? active_panel->getHeightSpinner() : floater->findChild<LLSpinCtrl>("snapshot_height");
}
void LLFloaterSnapshot::Impl::enableAspectRatioCheckbox(LLFloaterSnapshotBase* floater, bool enable)
@@ -278,7 +278,7 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater)
LLSpinCtrl* height_ctrl = getHeightSpinner(floater);
// Initialize spinners.
- if (width_ctrl->getValue().asInteger() == 0)
+ if (width_ctrl && width_ctrl->getValue().asInteger() == 0)
{
S32 w = gViewerWindow->getWindowWidthRaw();
LL_DEBUGS() << "Initializing width spinner (" << width_ctrl->getName() << "): " << w << LL_ENDL;
@@ -288,7 +288,7 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater)
width_ctrl->setIncrement((F32)(w >> 1));
}
}
- if (height_ctrl->getValue().asInteger() == 0)
+ if (height_ctrl && height_ctrl->getValue().asInteger() == 0)
{
S32 h = gViewerWindow->getWindowHeightRaw();
LL_DEBUGS() << "Initializing height spinner (" << height_ctrl->getName() << "): " << h << LL_ENDL;
@@ -686,8 +686,8 @@ void LLFloaterSnapshot::Impl::updateResolution(LLUICtrl* ctrl, void* data, bool
LLSnapshotLivePreview* previewp = getPreviewView();
if (previewp && combobox->getCurrentIndex() >= 0)
{
- S32 original_width = 0 , original_height = 0 ;
- previewp->getSize(original_width, original_height) ;
+ S32 original_width = 0, original_height = 0;
+ previewp->getSize(original_width, original_height);
if (gSavedSettings.getBOOL("RenderUIInSnapshot") || gSavedSettings.getBOOL("RenderHUDInSnapshot"))
{ //clamp snapshot resolution to window size when showing UI or HUD in snapshot
@@ -737,24 +737,31 @@ void LLFloaterSnapshot::Impl::updateResolution(LLUICtrl* ctrl, void* data, bool
previewp->setSize(width, height);
}
- checkAspectRatio(view, width) ;
+ checkAspectRatio(view, width);
previewp->getSize(width, height);
- // We use the height spinner here because we come here via the aspect ratio
- // checkbox as well and we want height always changing to width by default.
- // If we use the width spinner we would change width according to height by
- // default, that is not what we want.
- updateSpinners(view, previewp, width, height, !getHeightSpinner(view)->isDirty()); // may change width and height
+ LLSpinCtrl* height_ctrl = getHeightSpinner(view);
+ if (height_ctrl)
+ {
+ // We use the height spinner here because we come here via the aspect ratio
+ // checkbox as well and we want height always changing to width by default.
+ // If we use the width spinner we would change width according to height by
+ // default, that is not what we want.
+ updateSpinners(view, previewp, width, height, !height_ctrl->isDirty()); // may change width and height
+ }
- if(getWidthSpinner(view)->getValue().asInteger() != width || getHeightSpinner(view)->getValue().asInteger() != height)
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ if (width_ctrl
+ && height_ctrl
+ && (width_ctrl->getValue().asInteger() != width || height_ctrl->getValue().asInteger() != height))
{
- getWidthSpinner(view)->setValue(width);
- getHeightSpinner(view)->setValue(height);
+ width_ctrl->setValue(width);
+ height_ctrl->setValue(height);
if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE)
{
- getWidthSpinner(view)->setIncrement((F32)(width >> 1));
- getHeightSpinner(view)->setIncrement((F32)(height >> 1));
+ width_ctrl->setIncrement((F32)(width >> 1));
+ height_ctrl->setIncrement((F32)(height >> 1));
}
}
@@ -824,7 +831,7 @@ void LLFloaterSnapshot::Impl::comboSetCustom(LLFloaterSnapshotBase* floater, con
}
// Update supplied width and height according to the constrain proportions flag; limit them by max_val.
-bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value)
+bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value) const
{
S32 w = width ;
S32 h = height ;
@@ -870,19 +877,31 @@ bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S3
void LLFloaterSnapshot::Impl::setImageSizeSpinnersValues(LLFloaterSnapshotBase* view, S32 width, S32 height)
{
- getWidthSpinner(view)->forceSetValue(width);
- getHeightSpinner(view)->forceSetValue(height);
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ LLSpinCtrl* height_ctrl = getHeightSpinner(view);
+ if (!height_ctrl || !width_ctrl)
+ {
+ return;
+ }
+ width_ctrl->forceSetValue(width);
+ height_ctrl->forceSetValue(height);
if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE)
{
- getWidthSpinner(view)->setIncrement((F32)(width >> 1));
- getHeightSpinner(view)->setIncrement((F32)(height >> 1));
+ width_ctrl->setIncrement((F32)(width >> 1));
+ height_ctrl->setIncrement((F32)(height >> 1));
}
}
void LLFloaterSnapshot::Impl::updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed)
{
- getWidthSpinner(view)->resetDirty();
- getHeightSpinner(view)->resetDirty();
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ LLSpinCtrl* height_ctrl = getHeightSpinner(view);
+ if (!height_ctrl || !width_ctrl)
+ {
+ return;
+ }
+ width_ctrl->resetDirty();
+ height_ctrl->resetDirty();
if (checkImageSize(previewp, width, height, is_width_changed, previewp->getMaxImageSize()))
{
setImageSizeSpinnersValues(view, width, height);
@@ -903,7 +922,15 @@ void LLFloaterSnapshot::Impl::applyCustomResolution(LLFloaterSnapshotBase* view,
if (w != curw || h != curh)
{
//if to upload a snapshot, process spinner input in a special way.
- previewp->setMaxImageSize((S32) getWidthSpinner(view)->getMaxValue()) ;
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ if (width_ctrl)
+ {
+ previewp->setMaxImageSize((S32)width_ctrl->getMaxValue());
+ }
+ else
+ {
+ previewp->setMaxImageSize((S32)2048);
+ }
previewp->setSize(w,h);
checkAutoSnapshot(previewp, false);
diff --git a/indra/newview/llfloatersnapshot.h b/indra/newview/llfloatersnapshot.h
index 186d9c41cf..703ce9e57b 100644
--- a/indra/newview/llfloatersnapshot.h
+++ b/indra/newview/llfloatersnapshot.h
@@ -194,7 +194,7 @@ public:
void onImageFormatChange(LLFloaterSnapshotBase* view);
void applyCustomResolution(LLFloaterSnapshotBase* view, S32 w, S32 h);
static void onSendingPostcardFinished(LLFloaterSnapshotBase* floater, bool status);
- bool checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value);
+ bool checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value) const;
void setImageSizeSpinnersValues(LLFloaterSnapshotBase *view, S32 width, S32 height);
void updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed);
static void onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status);
diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp
index 03979edbc1..87a9b91015 100644
--- a/indra/newview/llfloaterworldmap.cpp
+++ b/indra/newview/llfloaterworldmap.cpp
@@ -419,7 +419,7 @@ bool LLFloaterWorldMap::postBuild()
mEventsMatureCheck = getChild<LLCheckBoxCtrl>("events_mature_chk");
mEventsAdultCheck = getChild<LLCheckBoxCtrl>("events_adult_chk");
- mAvatarIcon = getChild<LLUICtrl>("avatar_icon");
+ mFriendAvatarIcon = getChild<LLUICtrl>("friends_icon");
mLandmarkIcon = getChild<LLUICtrl>("landmark_icon");
mLocationIcon = getChild<LLUICtrl>("location_icon");
@@ -610,11 +610,11 @@ void LLFloaterWorldMap::draw()
LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus();
if (LLTracker::TRACKING_AVATAR == tracking_status)
{
- mAvatarIcon->setColor( map_track_color);
+ mFriendAvatarIcon->setColor( map_track_color);
}
else
{
- mAvatarIcon->setColor( map_track_disabled_color);
+ mFriendAvatarIcon->setColor( map_track_disabled_color);
}
if (LLTracker::TRACKING_LANDMARK == tracking_status)
diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h
index 9558ca2615..00b5837df0 100644
--- a/indra/newview/llfloaterworldmap.h
+++ b/indra/newview/llfloaterworldmap.h
@@ -234,7 +234,7 @@ private:
LLCheckBoxCtrl* mEventsMatureCheck = nullptr;
LLCheckBoxCtrl* mEventsAdultCheck = nullptr;
- LLUICtrl* mAvatarIcon = nullptr;
+ LLUICtrl* mFriendAvatarIcon = nullptr;
LLUICtrl* mLandmarkIcon = nullptr;
LLUICtrl* mLocationIcon = nullptr;
diff --git a/indra/newview/llgltfmateriallist.cpp b/indra/newview/llgltfmateriallist.cpp
index b784419780..4036544343 100644
--- a/indra/newview/llgltfmateriallist.cpp
+++ b/indra/newview/llgltfmateriallist.cpp
@@ -525,7 +525,7 @@ void LLGLTFMaterialList::onAssetLoadComplete(const LLUUID& id, LLAssetType::ETyp
if (status != LL_ERR_NOERR)
{
- LL_WARNS("GLTF") << "Error getting material asset data: " << LLAssetStorage::getErrorString(status) << " (" << status << ")" << LL_ENDL;
+ LL_WARNS("GLTF") << "Error getting material asset data: " << LLAssetStorage::getErrorString(status) << " (" << status << ") for asset " << id << LL_ENDL;
asset_data->mMaterial->materialComplete(false);
delete asset_data;
}
diff --git a/indra/newview/llgroupmgr.cpp b/indra/newview/llgroupmgr.cpp
index e0da762279..6081b36918 100644
--- a/indra/newview/llgroupmgr.cpp
+++ b/indra/newview/llgroupmgr.cpp
@@ -869,19 +869,15 @@ void LLGroupMgr::removeObserver(LLGroupMgrObserver* observer)
{
return;
}
- observer_multimap_t::iterator it;
- it = mObservers.find(observer->getID());
- while (it != mObservers.end())
+ observer_multimap_t::iterator it = mObservers.lower_bound(observer->getID());
+ observer_multimap_t::iterator end = mObservers.upper_bound(observer->getID());
+ for (; it != end; ++it)
{
if (it->second == observer)
{
mObservers.erase(it);
break;
}
- else
- {
- ++it;
- }
}
}
diff --git a/indra/newview/llhudnametag.cpp b/indra/newview/llhudnametag.cpp
index 4327d281e5..5d20e722de 100644
--- a/indra/newview/llhudnametag.cpp
+++ b/indra/newview/llhudnametag.cpp
@@ -41,9 +41,9 @@
#include "llhudrender.h"
#include "llui.h"
#include "llviewercamera.h"
+#include "llviewerregion.h"
#include "llviewertexturelist.h"
#include "llviewerobject.h"
-#include "llvovolume.h"
#include "llviewerwindow.h"
#include "llstatusbar.h"
#include "llmenugl.h"
@@ -291,7 +291,22 @@ void LLHUDNameTag::renderText()
+ (x_pixel_vec * screen_offset.mV[VX])
+ (y_pixel_vec * screen_offset.mV[VY]);
- LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);
+ // Check if an underwater name tag should be rendered over the water (while camera is above the water)
+ bool render_over_water = false;
+ static LLCachedControl<bool> nametag_over_water(gSavedSettings, "NametagOverWater", true);
+ if (nametag_over_water &&
+ mSourceObject &&
+ mSourceObject->getRegion() &&
+ LLPipeline::sRenderTransparentWater &&
+ !LLViewerCamera::getInstance()->cameraUnderWater())
+ {
+ if (mSourceObject->getPositionAgent().mV[VZ] < mSourceObject->getRegion()->getWaterHeight())
+ {
+ render_over_water = true;
+ }
+ }
+ LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, render_over_water ? GL_ALWAYS : GL_LEQUAL);
+
LLRect screen_rect;
screen_rect.setCenterAndSize(0, static_cast<S32>(lltrunc(-mHeight / 2 + mOffsetY)), static_cast<S32>(lltrunc(mWidth)), static_cast<S32>(lltrunc(mHeight)));
mRoundedRectImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, screen_rect, bg_color);
diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp
index 3f60dc5d26..9ba939d35b 100644
--- a/indra/newview/llimprocessing.cpp
+++ b/indra/newview/llimprocessing.cpp
@@ -1582,7 +1582,8 @@ void LLIMProcessing::requestOfflineMessagesCoro(std::string url)
if (!contents.size())
{
- LL_WARNS("Messaging") << "No contents received for offline messages via capability " << url << LL_ENDL;
+ // Received no offline messages on login.
+ LL_INFOS("Messaging") << "No contents received for offline messages via capability " << url << LL_ENDL;
return;
}
diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp
index 252eb32e7c..f8cc6885c4 100644
--- a/indra/newview/llimview.cpp
+++ b/indra/newview/llimview.cpp
@@ -3245,8 +3245,9 @@ void LLIMMgr::addMessage(
return;
}
- // Fetch group chat history, enabled by default.
- if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory"))
+ // Fetch group chat or ad-hoc history, enabled by default.
+ static LLCachedControl<bool> fetch_chat_history(gSavedPerAccountSettings, "FetchGroupChatHistory", true);
+ if (fetch_chat_history && !session->isP2PSessionType())
{
std::string chat_url = gAgent.getRegionCapability("ChatSessionRequest");
if (!chat_url.empty())
@@ -4089,8 +4090,9 @@ public:
{
im_floater->processSessionUpdate(body["session_info"]);
- // Send request for chat history, if enabled.
- if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory"))
+ // Send request for chat history, if enabled. Skip for peer-to-peer IMs.
+ static LLCachedControl<bool> fetch_chat_history(gSavedPerAccountSettings, "FetchGroupChatHistory", true);
+ if (fetch_chat_history && !im_floater->isP2PSessionType())
{
std::string url = gAgent.getRegionCapability("ChatSessionRequest");
if (!url.empty())
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index adeb65bf40..a8e8f122e1 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -249,11 +249,27 @@ const std::string& LLInvFVBridge::getName() const
const std::string& LLInvFVBridge::getDisplayName() const
{
- if(mDisplayName.empty())
+ if(mSearchableName.empty())
{
- buildDisplayName();
+ // first request of display name, build search string and cache it for later use
+ buildSearchableName();
}
- return mDisplayName;
+
+ LLInventoryModel* model = getInventoryModel();
+ if (model)
+ {
+ LLViewerInventoryCategory* cat = model->getCategory(mUUID);
+ if (cat)
+ {
+ return cat->getDisplayName();
+ }
+ LLViewerInventoryItem* item = model->getItem(mUUID);
+ if (item)
+ {
+ return item->getName();
+ }
+ }
+ return LLStringUtil::null;
}
std::string LLInvFVBridge::getSearchableDescription() const
@@ -2035,22 +2051,22 @@ PermissionMask LLItemBridge::getPermissionMask() const
}
// virtual
-void LLItemBridge::buildDisplayName() const
+void LLItemBridge::buildSearchableName() const
{
- if (getItem())
+ LLViewerInventoryItem* item = getItem();
+ if (item)
{
- mDisplayName.assign(getItem()->getName());
+ // for items, display name matches item name
+ mSearchableName.assign(item->getName());
}
else
{
- mDisplayName.assign(LLStringUtil::null);
+ mSearchableName.assign(LLStringUtil::null);
}
-
- mSearchableName.assign(mDisplayName);
mSearchableName.append(getLabelSuffix());
LLStringUtil::toUpper(mSearchableName);
- // Name set, so trigger a sort
+ // Searchable and name set, so trigger a sort
LLInventorySort sorter = static_cast<LLFolderViewModelInventory&>(mRootViewModel).getSorter();
if (mParent && !sorter.isByDate())
{
@@ -2371,39 +2387,17 @@ void LLFolderBridge::selectItem()
}
}
-void LLFolderBridge::buildDisplayName() const
+void LLFolderBridge::buildSearchableName() const
{
- LLFolderType::EType preferred_type = getPreferredType();
-
- // *TODO: to be removed when database supports multi language. This is a
- // temporary attempt to display the inventory folder in the user locale.
- // mantipov: *NOTE: be sure this code is synchronized with LLFriendCardsManager::findChildFolderUUID
- // it uses the same way to find localized string
-
- // HACK: EXT - 6028 ([HARD CODED]? Inventory > Library > "Accessories" folder)
- // Translation of Accessories folder in Library inventory folder
- bool accessories = false;
- if(getName() == "Accessories")
+ LLViewerInventoryCategory* cat = gInventory.getCategory(getUUID());
+ if (cat)
{
- //To ensure that Accessories folder is in Library we have to check its parent folder.
- //Due to parent LLFolderViewFloder is not set to this item yet we have to check its parent via Inventory Model
- LLInventoryCategory* cat = gInventory.getCategory(getUUID());
- if(cat)
- {
- const LLUUID& parent_folder_id = cat->getParentUUID();
- accessories = (parent_folder_id == gInventory.getLibraryRootFolderID());
- }
+ mSearchableName.assign(cat->getDisplayName());
}
-
- //"Accessories" inventory category has folder type FT_NONE. So, this folder
- //can not be detected as protected with LLFolderType::lookupIsProtectedType
- mDisplayName.assign(getName());
- if (accessories || LLFolderType::lookupIsProtectedType(preferred_type))
+ else
{
- LLTrans::findString(mDisplayName, std::string("InvFolder ") + getName(), LLSD());
+ mSearchableName.assign(LLStringUtil::null);
}
-
- mSearchableName.assign(mDisplayName);
mSearchableName.append(getLabelSuffix());
LLStringUtil::toUpper(mSearchableName);
@@ -2417,6 +2411,8 @@ void LLFolderBridge::buildDisplayName() const
std::string LLFolderBridge::getLabelSuffix() const
{
+ // Folders, unlike items, have context dependent suffixes
+ // that may change as the folder is loaded
static LLCachedControl<bool> xui_debug(gSavedSettings, "DebugShowXUINames", 0);
if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= FOLDER_LOADING_MESSAGE_DELAY)
@@ -4140,6 +4136,17 @@ void LLFolderBridge::perform_pasteFromClipboard()
}
else
{
+ // Check that no folder is being pasted into itself or into one of its descendants
+ for (const LLUUID& item_id : objects)
+ {
+ LLInventoryCategory* cat = model->getCategory(item_id);
+ if (cat && (item_id == mUUID || model->isObjectDescendentOf(mUUID, item_id)))
+ {
+ LLNotificationsUtil::add("CannotPasteFolderIntoSelf");
+ return;
+ }
+ }
+
// Check that all items can be moved into that folder : for the moment, only stock folder mismatch is checked
for (std::vector<LLUUID>::const_iterator iter = objects.begin(); iter != objects.end(); ++iter)
{
@@ -6576,18 +6583,29 @@ void LLCallingCardBridge::refreshFolderViewItem()
void LLCallingCardBridge::checkSearchBySuffixChanges()
{
- if (!mDisplayName.empty())
+ if (!mSearchableName.empty())
{
- // changes in mDisplayName are processed by rename function and here it will be always same
+ LLViewerInventoryItem* item = getItem();
+ if (!item)
+ {
+ // checkSearchBySuffixChanges is only used by friend list
+ // so if item is not found, we removed the calling card or
+ // are no longer friends.
+ mSearchableName.clear();
+ return;
+ }
+
+ // changes in display name are processed by rename function and here it will be always same
// suffixes are also of fixed length, and we are processing change of one at a time,
// so it should be safe to use length (note: mSearchableName is capitalized)
- auto old_length = mSearchableName.length();
- auto new_length = mDisplayName.length() + getLabelSuffix().length();
+ size_t old_length = mSearchableName.length();
+ const std::string& display_name = item->getName();
+ size_t new_length = display_name.length() + getLabelSuffix().length();
if (old_length == new_length)
{
return;
}
- mSearchableName.assign(mDisplayName);
+ mSearchableName.assign(display_name);
mSearchableName.append(getLabelSuffix());
LLStringUtil::toUpper(mSearchableName);
if (new_length<old_length)
@@ -7465,7 +7483,7 @@ bool LLObjectBridge::renameItem(const std::string& new_name)
new_item->updateServer(false);
model->updateItem(new_item);
model->notifyObservers();
- buildDisplayName();
+ buildSearchableName();
if (isAgentAvatarValid())
{
diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h
index c62f383160..ae012c3451 100644
--- a/indra/newview/llinventorybridge.h
+++ b/indra/newview/llinventorybridge.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -87,7 +87,7 @@ public:
virtual const LLUUID& getUUID() const { return mUUID; }
virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null; }
virtual bool isFavorite() const { return false; }
- virtual void clearDisplayName() { mDisplayName.clear(); }
+ virtual void clearSearchableName() { mSearchableName.clear(); }
virtual void restoreItem() {}
virtual void restoreToWorld() {}
@@ -202,12 +202,11 @@ protected:
LLInventoryType::EType mInvType;
bool mIsLink;
LLTimer mTimeSinceRequestStart;
- mutable std::string mDisplayName;
mutable std::string mSearchableName;
void purgeItem(LLInventoryModel *model, const LLUUID &uuid);
void removeObject(LLInventoryModel *model, const LLUUID &uuid);
- virtual void buildDisplayName() const {}
+ virtual void buildSearchableName() const {}
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -266,7 +265,7 @@ public:
protected:
bool confirmRemoveItem(const LLSD& notification, const LLSD& response);
virtual bool isItemPermissive() const;
- virtual void buildDisplayName() const;
+ virtual void buildSearchableName() const;
void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb);
private:
@@ -287,7 +286,7 @@ public:
void callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item);
void callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category);
- virtual void buildDisplayName() const;
+ virtual void buildSearchableName() const;
virtual void performAction(LLInventoryModel* model, std::string action);
virtual void openItem();
diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h
index c0164e04e4..6ff1c4839f 100644
--- a/indra/newview/llinventoryfilter.h
+++ b/indra/newview/llinventoryfilter.h
@@ -208,12 +208,14 @@ public:
Optional<FilterOps::Params> filter_ops;
Optional<std::string> substring;
Optional<bool> since_logoff;
+ Optional<S32> order;
Params()
: name("name"),
filter_ops(""),
substring("substring"),
- since_logoff("since_logoff")
+ since_logoff("since_logoff"),
+ order("order")
{}
};
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index af4650dcc8..d0d4d85993 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -28,6 +28,7 @@
#include <typeinfo>
#include <random>
+#include <thread>
#include "llinventorymodel.h"
@@ -82,6 +83,7 @@ const S32 LLInventoryModel::sCurrentInvCacheVersion = 5;
bool LLInventoryModel::sFirstTimeInViewer2 = true;
S32 LLInventoryModel::sPendingSystemFolders = 0;
+static std::vector<std::thread> sPendingCacheThreads;
///----------------------------------------------------------------------------
/// Local function declarations, constants, enums, and typedefs
@@ -100,45 +102,6 @@ struct InventoryIDPtrLess
}
};
-class LLCanCache : public LLInventoryCollectFunctor
-{
-public:
- LLCanCache(LLInventoryModel* model) : mModel(model) {}
- virtual ~LLCanCache() {}
- virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item);
-protected:
- LLInventoryModel* mModel;
- std::set<LLUUID> mCachedCatIDs;
-};
-
-bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
-{
- bool rv = false;
- if(item)
- {
- if(mCachedCatIDs.find(item->getParentUUID()) != mCachedCatIDs.end())
- {
- rv = true;
- }
- }
- else if(cat)
- {
- // HACK: downcast
- LLViewerInventoryCategory* c = (LLViewerInventoryCategory*)cat;
- if(c->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN)
- {
- S32 descendents_server = c->getDescendentCount();
- S32 descendents_actual = c->getViewerDescendentCount();
- if(descendents_server == descendents_actual)
- {
- mCachedCatIDs.insert(c->getUUID());
- rv = true;
- }
- }
- }
- return rv;
-}
-
struct InventoryCallbackInfo
{
InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) :
@@ -467,30 +430,33 @@ LLInventoryModel::~LLInventoryModel()
void LLInventoryModel::cleanupInventory()
{
+ LL_PROFILE_ZONE_SCOPED;
empty();
- // Deleting one observer might erase others from the list, so always pop off the front
- while (!mObservers.empty())
+ // Deleting one observer might trigger removeObserver, so use a local copy
+ if (!mObservers.empty())
{
- observer_list_t::iterator iter = mObservers.begin();
- LLInventoryObserver* observer = *iter;
- mObservers.erase(iter);
- delete observer;
+ observer_list_t observers_to_delete;
+ observers_to_delete.swap(mObservers);
+
+ for (LLInventoryObserver* observer : observers_to_delete)
+ {
+ delete observer;
+ }
}
if (mBulkFecthCallbackSlot.connected())
{
mBulkFecthCallbackSlot.disconnect();
}
- mObservers.clear();
// Run down HTTP transport
mHttpHeaders.reset();
mHttpOptions.reset();
delete mHttpRequestFG;
- mHttpRequestFG = NULL;
+ mHttpRequestFG = nullptr;
delete mHttpRequestBG;
- mHttpRequestBG = NULL;
+ mHttpRequestBG = nullptr;
}
// This is a convenience function to check if one object has a parent
@@ -2369,22 +2335,101 @@ void LLInventoryModel::cache(
const LLUUID& parent_folder_id,
const LLUUID& agent_id)
{
+ LL_PROFILE_ZONE_SCOPED;
LL_DEBUGS(LOG_INV) << "Caching " << parent_folder_id << " for " << agent_id
<< LL_ENDL;
+
LLViewerInventoryCategory* root_cat = getCategory(parent_folder_id);
- if(!root_cat) return;
+ if (!root_cat)
+ {
+ LL_WARNS(LOG_INV) << "Root category not found for " << parent_folder_id << LL_ENDL;
+ return;
+ }
+
cat_array_t categories;
categories.push_back(root_cat);
item_array_t items;
- LLCanCache can_cache(this);
- can_cache(root_cat, NULL);
- collectDescendentsIf(
- parent_folder_id,
- categories,
- items,
- INCLUDE_TRASH,
- can_cache);
+ // Lambda to check if a category should be cached
+ // Only cache if it has known version and matching descendent counts
+ auto should_cache_category = [](LLViewerInventoryCategory* cat) -> bool {
+ if (!cat || cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)
+ {
+ return false;
+ }
+ S32 descendents_server = cat->getDescendentCount();
+ S32 descendents_actual = cat->getViewerDescendentCount();
+ return (descendents_server == descendents_actual);
+ };
+
+ // Track which folders we've verified as cacheable descendants
+ std::unordered_set<LLUUID> processed_folders;
+ processed_folders.insert(parent_folder_id);
+
+ // First pass: identify all cacheable descendant folders
+ // Use pair of (folder_id, should_save_children)
+ std::deque<std::pair<LLUUID, bool>> folders_to_check;
+ folders_to_check.push_back(std::make_pair(parent_folder_id, should_cache_category(root_cat)));
+
+ while (!folders_to_check.empty())
+ {
+ auto [current_id, save_children] = folders_to_check.front();
+ folders_to_check.pop_front();
+
+ if (save_children) // else incorrect count or version
+ {
+ auto item_it = mParentChildItemTree.find(current_id);
+ if (item_it != mParentChildItemTree.end() && item_it->second)
+ {
+ for (LLViewerInventoryItem* item : *(item_it->second))
+ {
+ if (item)
+ {
+ items.push_back(item);
+ }
+ }
+ }
+ }
+
+ // Get child categories directly from the parent-child tree
+ auto cat_it = mParentChildCategoryTree.find(current_id);
+ if (cat_it != mParentChildCategoryTree.end() && cat_it->second)
+ {
+ for (LLViewerInventoryCategory* child_cat : *(cat_it->second))
+ {
+ if (!child_cat)
+ {
+ continue;
+ }
+
+ // Verify ownership matches (library vs agent inventory)
+ if (child_cat->getOwnerID() != root_cat->getOwnerID())
+ {
+ LL_WARNS(LOG_INV) << "Owner mismatch in category tree: expected "
+ << root_cat->getOwnerID() << " got "
+ << child_cat->getOwnerID() << " for category "
+ << child_cat->getName() << LL_ENDL;
+ continue;
+ }
+
+ const LLUUID& child_id = child_cat->getUUID();
+
+ // Only process each folder once
+ if (processed_folders.insert(child_id).second)
+ {
+ if (should_cache_category(child_cat))
+ {
+ categories.push_back(child_cat);
+ folders_to_check.push_back(std::make_pair(child_id, true));
+ }
+ else
+ {
+ folders_to_check.push_back(std::make_pair(child_id, false));
+ }
+ }
+ }
+ }
+ }
if (categories.empty() && items.empty())
{
@@ -2403,17 +2448,70 @@ void LLInventoryModel::cache(
}
std::string gzip_filename = getInvCacheAddres(agent_id);
gzip_filename.append(".gz");
- if(gzip_file(temp_file, gzip_filename))
+
+ if (sPendingCacheThreads.empty())
{
- LL_DEBUGS(LOG_INV) << "Successfully compressed " << temp_file << " to " << gzip_filename << LL_ENDL;
- LLFile::remove(temp_file);
+ LL_INFOS(LOG_INV) << "Inventory cache compression started" << LL_ENDL;
}
- else
+
+ // Launch background packing thread
+ // Main thread is the only one modifying sPendingCacheThreads
+ auto compress_cache = [temp_file, gzip_filename]()
{
- LL_WARNS(LOG_INV) << "Unable to compress " << temp_file << " into " << gzip_filename << LL_ENDL;
+ LL_PROFILE_ZONE_NAMED("inv cache compression");
+ LLTimer gzip_timer;
+
+ if (gzip_file(temp_file, gzip_filename))
+ {
+ F32 gzip_time = gzip_timer.getElapsedTimeF32();
+ LL_DEBUGS(LOG_INV) << "Successfully compressed " << temp_file
+ << " to " << gzip_filename
+ << " in " << gzip_time << "s" << LL_ENDL;
+ LLFile::remove(temp_file);
+ }
+ else
+ {
+ LL_WARNS(LOG_INV) << "Unable to compress " << temp_file
+ << " into " << gzip_filename << LL_ENDL;
+ }
+ };
+
+ try
+ {
+ sPendingCacheThreads.emplace_back(compress_cache);
+ }
+ catch (...)
+ {
+ LL_WARNS(LOG_INV) << "Failed to start inventory cache compression thread; running compression synchronously" << LL_ENDL;
+ compress_cache();
}
}
+void LLInventoryModel::waitForPendingCacheWrites()
+{
+ if (sPendingCacheThreads.empty())
+ {
+ return;
+ }
+ LL_PROFILE_ZONE_SCOPED;
+
+ // By this point all threads should have already been added,
+ // viewer is shutting down, main thread is the only one to
+ // modify sPendingCacheThreads
+ LL_DEBUGS(LOG_INV) << "Waiting for " << sPendingCacheThreads.size()
+ << " inventory cache compression thread(s) to complete..." << LL_ENDL;
+
+ for (auto& thread : sPendingCacheThreads)
+ {
+ if (thread.joinable())
+ {
+ thread.join();
+ }
+ }
+ sPendingCacheThreads.clear();
+
+ LL_INFOS(LOG_INV) << "Inventory cache compression completed" << LL_ENDL;
+}
void LLInventoryModel::addCategory(LLViewerInventoryCategory* category)
{
@@ -2721,10 +2819,11 @@ bool LLInventoryModel::loadSkeleton(
for(LLSD::array_const_iterator it = options.beginArray(),
end = options.endArray(); it != end; ++it)
{
- LLSD name = (*it)["name"];
- LLSD folder_id = (*it)["folder_id"];
- LLSD parent_id = (*it)["parent_id"];
- LLSD version = (*it)["version"];
+ const LLSD &folder = *it;
+ const LLSD &name = folder["name"];
+ const LLSD &folder_id = folder["folder_id"];
+ const LLSD &parent_id = folder["parent_id"];
+ const LLSD &version = folder["version"];
if(name.isDefined()
&& folder_id.isDefined()
&& parent_id.isDefined()
@@ -2738,7 +2837,7 @@ bool LLInventoryModel::loadSkeleton(
cat->setParent(parent_id.asUUID());
LLFolderType::EType preferred_type = LLFolderType::FT_NONE;
- LLSD type_default = (*it)["type_default"];
+ const LLSD &type_default = folder["type_default"];
if(type_default.isDefined())
{
preferred_type = (LLFolderType::EType)type_default.asInteger();
@@ -3466,6 +3565,8 @@ bool LLInventoryModel::loadFromFile(const std::string& filename,
const LLSD& llsd_cats = inventory["categories"];
if (llsd_cats.isArray())
{
+ size_t cats_count = llsd_cats.size();
+ categories.reserve(cats_count);
LLSD::array_const_iterator iter = llsd_cats.beginArray();
LLSD::array_const_iterator end = llsd_cats.endArray();
for (; iter != end; ++iter)
@@ -3484,6 +3585,8 @@ bool LLInventoryModel::loadFromFile(const std::string& filename,
const LLSD& llsd_items = inventory["items"];
if (llsd_items.isArray())
{
+ size_t items_count = llsd_items.size();
+ items.reserve(items_count);
LLSD::array_const_iterator iter = llsd_items.beginArray();
LLSD::array_const_iterator end = llsd_items.endArray();
for (; iter != end; ++iter)
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index 3aaba69a60..6ac307dd68 100644
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -199,6 +199,9 @@ public:
// Call on logout to save a terse representation.
void cache(const LLUUID& parent_folder_id, const LLUUID& agent_id);
+
+ // Wait for any pending async cache operations to complete
+ static void waitForPendingCacheWrites();
private:
// Information for tracking the actual inventory. We index this
// information in a lot of different ways so we can access
diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h
index 99cb9ec811..dbafc6fcb6 100644
--- a/indra/newview/llinventoryobserver.h
+++ b/indra/newview/llinventoryobserver.h
@@ -183,7 +183,7 @@ protected:
item_ref_t mAddedItems;
item_ref_t mWatchedAssets;
-private:
+protected:
bool mIsDirty;
};
diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp
index 06dd830416..301a0a0cc9 100644
--- a/indra/newview/llinventorypanel.cpp
+++ b/indra/newview/llinventorypanel.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -198,7 +198,6 @@ LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id )
p.title = getLabel();
p.rect = LLRect(0, 0, getRect().getWidth(), 0);
p.parent_panel = this;
- p.tool_tip = p.name;
p.listener = mInvFVBridgeBuilder->createBridge( LLAssetType::AT_CATEGORY,
LLAssetType::AT_CATEGORY,
LLInventoryType::IT_CATEGORY,
@@ -581,8 +580,9 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve
LLInvFVBridge* bridge = (LLInvFVBridge*)view_item->getViewModelItem();
if(bridge)
{
- // Clear the display name first, so it gets properly re-built during refresh()
- bridge->clearDisplayName();
+ // Clear the searchable name first, so it gets
+ // properly re-built during refresh()
+ bridge->clearSearchableName();
view_item->refresh();
}
@@ -1063,10 +1063,20 @@ LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * br
{
LLFolderViewFolder::Params params(mParams.folder);
- params.name = bridge->getDisplayName();
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ // Only usable for debug and first call has a large
+ // overhead from search string construction.
+ // As inventory names aren't unique and can change,
+ // there is little we can use them for in release builds.
+ params.name = bridge->getName();
+#else
+ // We don't have a source of unique names and inventory
+ // items can reach millions in quantity, just use
+ // a short descriptor
+ params.name = "fld";
+#endif
params.root = mFolderRoot.get();
params.listener = bridge;
- params.tool_tip = params.name;
params.allow_drop = allow_drop;
params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor);
@@ -1079,12 +1089,23 @@ LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge
{
LLFolderViewItem::Params params(mParams.item);
- params.name = bridge->getDisplayName();
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ // Only usable for debug and first call has a large
+ // overhead from search string construction.
+ // As inventory names aren't unique, are large and can change,
+ // there is little we can use them for in release builds.
+ // Prefer shorter
+ params.name = bridge->getName();
+#else
+ // We don't have a source of unique names and inventory
+ // items can reach millions in quantity, just use
+ // a short descriptor
+ params.name = "itm";
+#endif
params.creation_date = bridge->getCreationDate();
params.root = mFolderRoot.get();
params.listener = bridge;
params.rect = LLRect (0, 0, 0, 0);
- params.tool_tip = params.name;
params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor);
params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor);
@@ -1676,7 +1697,7 @@ void LLInventoryPanel::onSelectionChange(const std::deque<LLFolderViewItem*>& it
LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem();
if(prev_bridge)
{
- prev_bridge->clearDisplayName();
+ prev_bridge->clearSearchableName();
prev_bridge->setShowDescendantsCount(false);
prev_folder_item->refresh();
}
@@ -1685,7 +1706,7 @@ void LLInventoryPanel::onSelectionChange(const std::deque<LLFolderViewItem*>& it
LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem();
if(bridge)
{
- bridge->clearDisplayName();
+ bridge->clearSearchableName();
bridge->setShowDescendantsCount(true);
folder_item->refresh();
mPreviousSelectedFolder = bridge->getUUID();
@@ -1700,7 +1721,7 @@ void LLInventoryPanel::onSelectionChange(const std::deque<LLFolderViewItem*>& it
LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem();
if(prev_bridge)
{
- prev_bridge->clearDisplayName();
+ prev_bridge->clearSearchableName();
prev_bridge->setShowDescendantsCount(false);
prev_folder_item->refresh();
}
@@ -1720,7 +1741,7 @@ void LLInventoryPanel::updateFolderLabel(const LLUUID& folder_id)
LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem();
if(bridge)
{
- bridge->clearDisplayName();
+ bridge->clearSearchableName();
bridge->setShowDescendantsCount(true);
folder_item->refresh();
}
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 02f013ad3c..2cdaf40be8 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -96,6 +96,8 @@ LLLoginInstance::LLLoginInstance() :
mDispatcher.add("connect", "", boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1));
mDispatcher.add("disconnect", "", boost::bind(&LLLoginInstance::handleDisconnect, this, _1));
mDispatcher.add("indeterminate", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
+ // Todo, implement "authenticating"?
+ mDispatcher.add("authenticating", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
}
void LLLoginInstance::setPlatformInfo(const std::string platform,
diff --git a/indra/newview/llmachineid.cpp b/indra/newview/llmachineid.cpp
index 0a90cf0699..0373b25143 100644
--- a/indra/newview/llmachineid.cpp
+++ b/indra/newview/llmachineid.cpp
@@ -34,7 +34,7 @@
#include <Wbemidl.h>
#elif LL_DARWIN
#include <CoreFoundation/CoreFoundation.h>
-#include <IOKit/IOKitLib.h>
+#include "llwindowmacosx_iokit.h"
#endif
unsigned char static_unique_id[] = {0,0,0,0,0,0};
unsigned char static_legacy_id[] = {0,0,0,0,0,0};
@@ -350,7 +350,7 @@ bool LLWMIMethods::getGenericSerialNumber(const BSTR &select, const LPCWSTR &var
bool getSerialNumber(unsigned char *unique_id, size_t len)
{
CFStringRef serial_cf_str = NULL;
- io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault,
+ io_service_t platformExpert = IOServiceGetMatchingService(kLLIOMainPort,
IOServiceMatching("IOPlatformExpertDevice"));
if (platformExpert)
{
diff --git a/indra/newview/llmanipscale.cpp b/indra/newview/llmanipscale.cpp
index 66420d1cad..87b79fd18c 100644
--- a/indra/newview/llmanipscale.cpp
+++ b/indra/newview/llmanipscale.cpp
@@ -1309,6 +1309,12 @@ void LLManipScale::updateSnapGuides(const LLBBox& bbox)
LLVector3 grid_scale;
LLQuaternion grid_rotation;
LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale);
+ LLViewerCamera* camera = LLViewerCamera::getInstance();
+ if (!camera)
+ {
+ // can be null on shutdown
+ return;
+ }
bool uniform = LLManipScale::getUniform();
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index 4e14f416e9..323ea9f552 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -177,6 +177,65 @@ void LLFloaterComboOptions::onCancel()
closeFloater();
}
+class LLMaterialEditorTaskMoveObserver : public LLInventoryAddItemByAssetObserver
+{
+public:
+ LLMaterialEditorTaskMoveObserver(
+ const LLUUID& asset_id,
+ const LLUUID& dest_folder_id,
+ LLPointer<LLInventoryCallback> cb)
+ : mDestFolderID(dest_folder_id),
+ mCallback(cb)
+ {
+ watchAsset(asset_id);
+ }
+
+ virtual ~LLMaterialEditorTaskMoveObserver()
+ {
+ gInventory.removeObserver(this);
+ }
+
+ virtual void changed(U32 mask) override
+ {
+ // Call base class to populate mAddedItems
+ LLInventoryAddItemByAssetObserver::changed(mask);
+
+ // If base class completed (which calls done() and clears mAddedItems),
+ // and we set mIsDirty, that means we're finished
+ if (mIsDirty)
+ {
+ gInventory.removeObserver(this);
+ delete this;
+ }
+ }
+
+protected:
+ virtual void done() override
+ {
+ // Find the moved item
+ // There must be only one since we are watching only one item,
+ // changed wouldn't fire otherwise. But just in case.
+ if (mAddedItems.size() == 1)
+ {
+ LLUUID item_id = mAddedItems[0];
+
+ // Fire the callback
+ if (mCallback)
+ {
+ mCallback->fire(item_id);
+ }
+ }
+
+ // Todo: gInventoryMoveObserver normally opens inventory
+ // on completion, should this do the same?
+ }
+
+private:
+ LLUUID mDestFolderID;
+ std::string mNewName;
+ LLPointer<LLInventoryCallback> mCallback;
+};
+
class LLMaterialEditorCopiedCallback : public LLInventoryCallback
{
public:
@@ -190,6 +249,18 @@ public:
{}
LLMaterialEditorCopiedCallback(
+ const std::string & buffer,
+ const LLSD & old_key,
+ const std::string & new_name,
+ bool has_unsaved_changes)
+ : mBuffer(buffer),
+ mOldKey(old_key),
+ mNewName(new_name),
+ mHasUnsavedChanges(has_unsaved_changes)
+ {
+ }
+
+ LLMaterialEditorCopiedCallback(
const LLSD &old_key,
const std::string &new_name)
: mOldKey(old_key),
@@ -201,7 +272,10 @@ public:
{
if (!mNewName.empty())
{
- // making a copy from a notecard doesn't change name, do it now
+ // making a copy from a task inventory (object, notecard)
+ // doesn't change name, do it now
+ // Todo: Can calling update_inventory_item and finishSaveAs
+ // cause a race condition?
LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
if (item->getName() != mNewName)
{
@@ -1811,6 +1885,52 @@ void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD&
mNotecardInventoryID,
mAuxItem.get(),
gInventoryCallbacks.registerCB(cb));
+
+ mAssetStatus = PREVIEW_ASSET_LOADING;
+ setEnabled(false);
+ }
+ else if (mObjectUUID.notNull())
+ {
+ // Item is in task (object) inventory - must move to agent
+ // inventory first.
+ // Note: If this is too flimsy, just disable the 'save as'
+ // button when in object inventory and create a server ticket,
+ // as we need a copy with callback variant.
+ LLViewerObject* object = gObjectList.findObject(mObjectUUID);
+ if (object)
+ {
+ LLPermissions perm(item->getPermissions());
+ if (perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID())
+ && perm.allowTransferTo(gAgent.getID()))
+ {
+ // Create a callback for after the item is copied/moved
+ std::string buffer = getEncodedAsset();
+ LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(
+ buffer,
+ getKey(),
+ new_name,
+ mUnsavedChanges);
+
+ // Create observer to watch for the item arriving in inventory
+ // The observer will fire our callback when the move completes
+ LLMaterialEditorTaskMoveObserver* observer = new LLMaterialEditorTaskMoveObserver(
+ item->getAssetUUID(),
+ parent_id,
+ cb
+ );
+ gInventory.addObserver(observer);
+
+ // Copies(not moves) item from object to agent inventory
+ object->moveInventory(parent_id, item->getUUID());
+
+ mAssetStatus = PREVIEW_ASSET_LOADING;
+ setEnabled(false);
+ }
+ else
+ {
+ LL_WARNS("MaterialEditor") << "Insufficient permissions to copy material from object inventory" << LL_ENDL;
+ }
+ }
}
else
{
@@ -1823,10 +1943,10 @@ void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD&
parent_id,
new_name,
cb);
- }
- mAssetStatus = PREVIEW_ASSET_LOADING;
- setEnabled(false);
+ mAssetStatus = PREVIEW_ASSET_LOADING;
+ setEnabled(false);
+ }
}
else
{
diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp
index 305cca290c..9b61529b5d 100644
--- a/indra/newview/llmediactrl.cpp
+++ b/indra/newview/llmediactrl.cpp
@@ -62,9 +62,15 @@
#include "lllineeditor.h"
#include "llfloaterwebcontent.h"
#include "llwindowshade.h"
+#include "lleventapi.h"
+#include "llui.h"
extern bool gRestoreGL;
+const std::string PAGE_TEXT_EXTRACT_MARKER = "PAGE_TEXT_EXTRACT:";
+
+class LLMediaCtrlListener;
+
static LLDefaultChildRegistry::Register<LLMediaCtrl> r("web_browser");
LLMediaCtrl::Params::Params()
@@ -100,6 +106,7 @@ LLMediaCtrl::LLMediaCtrl( const Params& p) :
mUpdateScrolls( false ),
mTextureWidth ( 1024 ),
mTextureHeight ( 1024 ),
+ mLoadingState( LOADING_STATE_INITIALIZING ),
mClearCache(false),
mHomePageMimeType(p.initial_mime_type),
mErrorPageURL(p.error_page_url),
@@ -1024,12 +1031,14 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event)
{
LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN, url is " << self->getNavigateURI() << LL_ENDL;
hideNotification();
+ mLoadingState = LOADING_STATE_LOADING;
};
break;
case MEDIA_EVENT_NAVIGATE_COMPLETE:
{
LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL;
+ mLoadingState = LOADING_STATE_LOADED;
};
break;
@@ -1058,6 +1067,7 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event)
{
navigateTo(mErrorPageURL, HTTP_CONTENT_TEXT_HTML);
};
+ mLoadingState = LOADING_STATE_ERROR;
};
break;
@@ -1102,12 +1112,14 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event)
case MEDIA_EVENT_PLUGIN_FAILED:
{
LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL;
+ mLoadingState = LOADING_STATE_ERROR;
};
break;
case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH:
{
LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << LL_ENDL;
+ mLoadingState = LOADING_STATE_ERROR;
};
break;
@@ -1190,6 +1202,33 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event)
case MEDIA_EVENT_DEBUG_MESSAGE:
{
LL_INFOS("media") << self->getDebugMessageText() << LL_ENDL;
+
+ // Handle text extraction responses
+ std::string debug_text = self->getDebugMessageText();
+ if (debug_text.find(PAGE_TEXT_EXTRACT_MARKER) != std::string::npos)
+ {
+ if (LLPluginClassMedia* plugin = getMediaPlugin())
+ {
+ // Disable plugin debugging if it was used just for text extraction
+ static LLCachedControl<bool> media_debugging(gSavedSettings, "MediaPluginDebugging", false);
+ plugin->enableMediaPluginDebugging(media_debugging);
+ }
+ // Extract the pump name and page text
+ size_t marker_pos = debug_text.find(PAGE_TEXT_EXTRACT_MARKER);
+ if (marker_pos != std::string::npos)
+ {
+ std::string remaining = debug_text.substr(marker_pos + PAGE_TEXT_EXTRACT_MARKER.length());
+ size_t colon_pos = remaining.find(':');
+ if (colon_pos != std::string::npos)
+ {
+ std::string pump_name = remaining.substr(0, colon_pos);
+ std::string page_text = remaining.substr(colon_pos + 1);
+
+ // Send the response directly to the specified pump
+ LLEventPumps::instance().obtain(pump_name).post(LLSD().with("text", page_text));
+ }
+ }
+ }
};
break;
};
@@ -1270,3 +1309,182 @@ bool LLMediaCtrl::wantsReturnKey() const
{
return true;
}
+
+std::string LLMediaCtrl::getMediaMimeType()
+{
+ return mMediaSource ? mMediaSource->getMimeType() : "unknown";
+}
+
+std::string LLMediaCtrl::getMediaLoadingStatus()
+{
+ if (!mMediaSource)
+ {
+ return "error";
+ }
+
+ switch (mLoadingState)
+ {
+ case LOADING_STATE_INITIALIZING:
+ return "initializing";
+ case LOADING_STATE_LOADING:
+ return "loading";
+ case LOADING_STATE_LOADED:
+ return "loaded";
+ case LOADING_STATE_ERROR:
+ default:
+ return "error";
+ }
+}
+
+std::string LLMediaCtrl::getMediaTitle()
+{
+ if (mMediaSource)
+ {
+ if (LLPluginClassMedia* plugin = mMediaSource->getMediaPlugin())
+ {
+ return plugin->getMediaName();
+ }
+ }
+ return "unknown";
+}
+
+bool LLMediaCtrl::executeJavaScript(const std::string& script)
+{
+ if (mMediaSource && mMediaSource->hasMedia())
+ {
+ mMediaSource->executeJavaScript(script);
+ return true;
+ }
+ return false;
+}
+
+class LLMediaCtrlListener: public LLEventAPI
+{
+public:
+ LLMediaCtrlListener();
+
+private:
+ void getMediaInfo(const LLSD& request);
+ void getMediaText(const LLSD& request);
+ void getPluginsList(const LLSD& request);
+ void replyError(const LLSD& request, const std::string& error);
+ LLMediaCtrl* findMediaCtrl(const std::string& path);
+};
+
+LLMediaCtrlListener::LLMediaCtrlListener():
+ LLEventAPI("LLMediaAPI", "Acces to LLMediaCtrl(web_browse widget) info")
+{
+ add("getMediaInfo",
+ "Get information about the web_browser widget specified by [\"path\"].\n"
+ "Returns URL, MIME type, and loading status of the widget.",
+ &LLMediaCtrlListener::getMediaInfo,
+ llsd::map("path", LLSD(), "reply", LLSD()));
+
+ add("getMediaText",
+ "Get text content from the web_browser widget specified by [\"path\"].\n"
+ "Returns the text content of the page or a portion of it.",
+ &LLMediaCtrlListener::getMediaText,
+ llsd::map("path", LLSD(), "reply", LLSD()));
+
+ add("getPluginsList",
+ "Enumerate the active media plugin (SLPlugin) instances.\n"
+ "Reply contains [\"plugins\"] an array of { pid, url, mime_type, remote_debugging_port } entries.",
+ &LLMediaCtrlListener::getPluginsList,
+ llsd::map("reply", LLSD()));
+}
+
+LLMediaCtrl* LLMediaCtrlListener::findMediaCtrl(const std::string& path)
+{
+ LLView* view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
+ if (!view)
+ {
+ return nullptr;
+ }
+ return dynamic_cast<LLMediaCtrl*>(view);
+}
+
+void LLMediaCtrlListener::getMediaInfo(const LLSD& request)
+{
+ Response reply(LLSD(), request);
+ std::string path = request["path"];
+
+ LLMediaCtrl* media_ctrl = findMediaCtrl(path);
+ if (!media_ctrl)
+ {
+ reply["error"] = "Could not find web_browser widget at path: " + path;
+ return;
+ }
+
+ reply["url"] = media_ctrl->getCurrentNavUrl();
+ reply["mime_type"] = media_ctrl->getMediaMimeType();
+ reply["status"] = media_ctrl->getMediaLoadingStatus();
+ reply["title"] = media_ctrl->getMediaTitle();
+}
+
+void LLMediaCtrlListener::replyError(const LLSD& request, const std::string& error)
+{
+ Response reply(LLSD(), request);
+ reply["error"] = error;
+}
+
+void LLMediaCtrlListener::getMediaText(const LLSD& request)
+{
+ std::string path = request["path"];
+
+ LLMediaCtrl* media_ctrl = findMediaCtrl(path);
+ if (!media_ctrl)
+ {
+ replyError(request, "Could not find web_browser widget at path: " + path);
+ return;
+ }
+
+ LLPluginClassMedia* plugin = media_ctrl->getMediaPlugin();
+ if (!plugin)
+ {
+ replyError(request, "Media plugin is not available for widget at path: " + path);
+ return;
+ }
+
+ // Enable plugin debugging to capture console messages
+ plugin->enableMediaPluginDebugging(true);
+ std::string pump_name = request["reply"].asString();
+
+ // Execute JavaScript to extract page text, embedding pump name in the marker
+ const std::string text_extract_script = "console.log('" + PAGE_TEXT_EXTRACT_MARKER + pump_name + ":' + "
+ "(document.body ? (document.body.innerText ? document.body.innerText.substring(0, 1000).replace(/\\s+/g, ' ').trim() : "
+ "'No text content') : 'Document body not ready'));";
+
+ if (!media_ctrl->executeJavaScript(text_extract_script))
+ {
+ replyError(request, "Failed to execute JavaScript for text extraction");
+ }
+}
+
+void LLMediaCtrlListener::getPluginsList(const LLSD& request)
+{
+ Response reply(LLSD(), request);
+
+ LLSD plugins;
+
+ LLViewerMedia::impl_list& impls = LLViewerMedia::getInstance()->getPriorityList();
+ for (LLViewerMedia::impl_list::iterator it = impls.begin(); it != impls.end(); ++it)
+ {
+ LLViewerMediaImpl* impl = *it;
+ if (!impl)
+ {
+ continue;
+ }
+ LLPluginClassMedia* plugin = impl->getMediaPlugin();
+
+ LLSD entry;
+ entry["url"] = impl->getCurrentMediaURL();
+ entry["mime_type"] = impl->getMimeType();
+ entry["pid"] = plugin ? plugin->getProcessID() : 0;
+ entry["remote_debugging_port"] = plugin ? (S32)plugin->getCefRemoteDebuggingPort() : 0;
+
+ plugins.append(entry);
+ }
+ reply["plugins"] = plugins;
+}
+
+static LLMediaCtrlListener sMediaCtrlListener;
diff --git a/indra/newview/llmediactrl.h b/indra/newview/llmediactrl.h
index d2735a009b..0a092f21d2 100644
--- a/indra/newview/llmediactrl.h
+++ b/indra/newview/llmediactrl.h
@@ -77,6 +77,14 @@ protected:
public:
virtual ~LLMediaCtrl();
+ enum ELoadingState
+ {
+ LOADING_STATE_INITIALIZING = 0,
+ LOADING_STATE_LOADING = 1,
+ LOADING_STATE_LOADED = 2,
+ LOADING_STATE_ERROR = 3
+ };
+
void setBorderVisible( bool border_visible );
// For the tutorial window, we don't want to take focus on clicks,
@@ -181,6 +189,11 @@ public:
virtual bool acceptsTextInput() const { return true; }
+ std::string getMediaMimeType();
+ std::string getMediaLoadingStatus();
+ std::string getMediaTitle();
+ bool executeJavaScript(const std::string& script);
+
protected:
void convertInputCoords(S32& x, S32& y);
@@ -216,6 +229,7 @@ public:
viewer_media_t mMediaSource;
S32 mTextureWidth,
mTextureHeight;
+ ELoadingState mLoadingState;
class LLWindowShade* mWindowShade;
LLHandle<LLContextMenu> mContextMenuHandle;
diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp
index 23a62a513f..880e675c6b 100644
--- a/indra/newview/llmutelist.cpp
+++ b/indra/newview/llmutelist.cpp
@@ -167,7 +167,8 @@ LLMuteList::LLMuteList() :
mLoadState(ML_INITIAL),
mLoadSource(MLS_NONE),
mRequestStartTime(0.f),
- mTriedCacheFallback(false)
+ mTriedCacheFallback(false),
+ mTriedRegionChangeRetry(false)
{
gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList);
@@ -857,6 +858,9 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id)
// Guard against potentially writing back to disk since we're not recovering our connection
mLoadState = ML_LOADED;
mLoadSource = MLS_FALLBACK_CACHE;
+ // This code path means we have disconnected/crashed before our request has been sent.
+ // As a result we do not NEED to do anything more than set these state values.
+ // cache() is liable to be called on shutdown, but since we've set a dirty state it will avoid writing to disk.
return;
}
if (!gAgent.getRegion())
@@ -879,7 +883,7 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id)
void LLMuteList::cache(const LLUUID& agent_id)
{
// Write to disk even if empty, but never from degraded fallback state.
- if (isLoaded() && mLoadSource != MLS_FALLBACK_CACHE)
+ if (isLoaded() && !isLoadedDegraded())
{
const std::string filename = getCacheFilename(agent_id);
saveToFile(filename);
diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h
index 2781e9b177..aff23c72d1 100644
--- a/indra/newview/llmutelist.h
+++ b/indra/newview/llmutelist.h
@@ -129,9 +129,9 @@ public:
// Load state accessors.
bool isLoaded() const { return mLoadState == ML_LOADED; } // Loaded, but not necessarily from server.
bool isFailed() const { return mLoadState == ML_FAILED; } // Unable to load any mute list. Server did not reply.
- // Loaded from server, which is the only source we consider authoritative.
- bool isLoadedFromServer() const { return isLoaded() && (mLoadSource == MLS_SERVER || mLoadSource == MLS_SERVER_EMPTY); }
- // Loaded, but from cache. Would be nice to upgrade to a server load from here if possible.
+ // Loaded from an authoritative server response, including when the server directs us to use our cached copy.
+ bool isLoadedFromServer() const { return isLoaded() && (mLoadSource == MLS_SERVER || mLoadSource == MLS_SERVER_EMPTY || mLoadSource == MLS_SERVER_CACHE); }
+ // Loaded without an authoritative server response. Would be nice to upgrade to a server load from here if possible.
bool isLoadedDegraded() const { return isLoaded() && !isLoadedFromServer(); }
// Advance the load state machine, trying cache fallback if necessary.
diff --git a/indra/newview/llnotificationstorage.cpp b/indra/newview/llnotificationstorage.cpp
index 5cec35fc88..3fb922f93f 100644
--- a/indra/newview/llnotificationstorage.cpp
+++ b/indra/newview/llnotificationstorage.cpp
@@ -108,7 +108,10 @@ bool LLNotificationStorage::readNotifications(LLSD& pNotificationData, bool is_n
{
std::string filename = is_new_filename? mFileName : mOldFileName;
- LL_INFOS("LLNotificationStorage") << "starting read '" << filename << "'" << LL_ENDL;
+ if (!filename.empty())
+ {
+ LL_INFOS("LLNotificationStorage") << "starting read '" << filename << "'" << LL_ENDL;
+ }
bool didFileRead;
@@ -118,7 +121,17 @@ bool LLNotificationStorage::readNotifications(LLSD& pNotificationData, bool is_n
didFileRead = notifyFile.is_open();
if (!didFileRead)
{
- LL_WARNS("LLNotificationStorage") << "Failed to open file '" << filename << "'" << LL_ENDL;
+ if (!filename.empty())
+ {
+ if (LLFile::isfile(filename))
+ {
+ LL_WARNS("LLNotificationStorage") << "Failed to open file '" << filename << "'" << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("LLNotificationStorage") << "File '" << filename << "' doesn't exist" << LL_ENDL;
+ }
+ }
}
else
{
@@ -144,7 +157,10 @@ bool LLNotificationStorage::readNotifications(LLSD& pNotificationData, bool is_n
if(didFileRead)
{
writeNotifications(pNotificationData);
- LLFile::remove(mOldFileName);
+ if (!mOldFileName.empty())
+ {
+ LLFile::remove(mOldFileName, ENOENT);
+ }
}
}
}
diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp
index 7db79c7010..d49d1d662a 100644
--- a/indra/newview/lloutfitslist.cpp
+++ b/indra/newview/lloutfitslist.cpp
@@ -519,6 +519,22 @@ void LLOutfitsList::resetItemSelection(LLWearableItemsList* list, const LLUUID&
list->resetSelection();
mItemSelected = false;
signalSelectionOutfitUUID(category_id);
+
+ // If filtering was applied while tab was collapsed, item visibility is updated but the parent tab height might not be updated.
+ // Force rearrange to recompute the height, when tab is expanded.
+ static LLCachedControl<bool> show_all_items(gSavedSettings, "OutfitListFilterFullList", 1);
+ if (!show_all_items)
+ {
+ outfits_map_t::const_iterator tab_iter = mOutfitsMap.find(category_id);
+ if (tab_iter != mOutfitsMap.end())
+ {
+ LLOutfitAccordionCtrlTab* tab = tab_iter->second;
+ if (tab && tab->getDisplayChildren())
+ {
+ list->notify(LLSD().with("rearrange", true));
+ }
+ }
+ }
}
void LLOutfitsList::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
@@ -664,6 +680,8 @@ void LLOutfitsList::applyFilterToTab(
}
else
{
+ // We got a match
+ tab->setVisible(true);
// Try restoring the tab selection.
restoreOutfitSelection(tab, category_id);
}
@@ -1648,7 +1666,7 @@ bool LLOutfitListSortMenu::onEnable(LLSD::String param)
}
else if ("show_entire_outfit" == param)
{
- static LLCachedControl<bool> filter_mode(gSavedSettings, "OutfitListFilterFullList", 0);
+ static LLCachedControl<bool> filter_mode(gSavedSettings, "OutfitListFilterFullList", 1);
return filter_mode;
}
diff --git a/indra/newview/llpaneldirgroups.cpp b/indra/newview/llpaneldirgroups.cpp
index 992d92091c..324d84efde 100644
--- a/indra/newview/llpaneldirgroups.cpp
+++ b/indra/newview/llpaneldirgroups.cpp
@@ -51,6 +51,12 @@ bool LLPanelDirGroups::postBuild()
childSetAction("Search", &LLPanelDirBrowser::onClickSearchCore, this);
setDefaultBtn( "Search" );
+ if (gAgent.isTeen())
+ {
+ childSetEnabled("incmature", false);
+ gSavedSettings.setBOOL("ShowMatureGroups", false);
+ }
+
return true;
}
@@ -72,11 +78,25 @@ void LLPanelDirGroups::performQuery()
U32 scope = DFQ_GROUPS;
// Check group mature filter.
- if ( !gSavedSettings.getBOOL("ShowMatureGroups") || gAgent.isTeen() )
+ if ( gSavedSettings.getBOOL("ShowMatureGroups") && !gAgent.isTeen() )
{
+ // Supposed behavior:
+ // if nothing is set will search for <= mature
+ // if DFQ_INC_PG is set, will look for <= PG
+ // if DFQ_INC_MATURE is set, will look for == mature
+ // if DFQ_INC_ADULT is set, will look for >= adult
+ // Not compatible with legacy DFQ_FILTER_MATURE.
+ // But there appears to be a server bug, so we only use
+ // this to show all and use legacy setting for 'pg only'
+ scope |= DFQ_INC_PG;
+ scope |= DFQ_INC_MATURE;
+ scope |= DFQ_INC_ADULT;
+ }
+ else
+ {
+ // DFQ_FILTER_MATURE is a legacy setting
scope |= DFQ_FILTER_MATURE;
}
-
mCurrentSortColumn = "score";
mCurrentSortAscending = false;
diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp
index 345426824e..3488685f14 100644
--- a/indra/newview/llpanelface.cpp
+++ b/indra/newview/llpanelface.cpp
@@ -854,24 +854,56 @@ struct LLPanelFaceSetAlignedTEFunctor : public LLSelectedTEFunctor
}
// Also align GLTF material if any
- S32 gltf_info_index = 0; // base texture
+ LLGLTFMaterial::TextureInfo gltf_info_index = mPanel->getPBRTextureInfo();
LLVector2 gltf_offset, gltf_scale;
F32 gltf_rot;
- if (facep->calcAlignedPlanarGLTF(mCenterFace, &gltf_offset, &gltf_scale, &gltf_rot, gltf_info_index))
+
+ if (gltf_info_index == LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT)
{
+ // "Complete material" - update all texture transforms
LLGLTFMaterial new_override;
const LLTextureEntry* tep = object->getTE(te);
if (tep && tep->getGLTFMaterialOverride())
{
new_override = *tep->getGLTFMaterialOverride();
}
+ bool any_changed = false;
+
+ for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+ {
+ if (facep->calcAlignedPlanarGLTF(mCenterFace, &gltf_offset, &gltf_scale, &gltf_rot, i))
+ {
+ LLGLTFMaterial::TextureTransform& transform = new_override.mTextureTransform[i];
+ transform.mOffset.set(gltf_offset.mV[0], gltf_offset.mV[1]);
+ transform.mScale.set(gltf_scale.mV[0], gltf_scale.mV[1]);
+ transform.mRotation = gltf_rot;
+ any_changed = true;
+ }
+ }
+
+ if (any_changed)
+ {
+ LLGLTFMaterialList::queueModify(object, te, &new_override);
+ }
+ }
+ else
+ {
+ if (facep->calcAlignedPlanarGLTF(mCenterFace, &gltf_offset, &gltf_scale, &gltf_rot, gltf_info_index))
+ {
+ LLGLTFMaterial new_override;
+ const LLTextureEntry* tep = object->getTE(te);
+ if (tep && tep->getGLTFMaterialOverride())
+ {
+ new_override = *tep->getGLTFMaterialOverride();
+ }
- LLGLTFMaterial::TextureTransform& transform = new_override.mTextureTransform[gltf_info_index];
- transform.mOffset.set(gltf_offset.mV[0], gltf_offset.mV[1]);
- transform.mScale.set(gltf_scale.mV[0], gltf_scale.mV[1]);
- transform.mRotation = gltf_rot;
+ LLGLTFMaterial::TextureTransform& transform = new_override.mTextureTransform[gltf_info_index];
+ transform.mOffset.set(gltf_offset.mV[0], gltf_offset.mV[1]);
+ transform.mScale.set(gltf_scale.mV[0], gltf_scale.mV[1]);
+ transform.mRotation = gltf_rot;
- LLGLTFMaterialList::queueModify(object, te, &new_override);
+ LLGLTFMaterialList::queueModify(object, te, &new_override);
+ }
}
}
if (!set_aligned)
@@ -1073,6 +1105,9 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
// only turn on auto-adjust button if there is a media renderer and the media is loaded
mBtnAlign->setEnabled(editable);
+ // enable if needed before changing selection
+ mComboMatMedia->setEnabledByValue("Materials", !has_pbr_material);
+
if (mComboMatMedia->getCurrentIndex() < MATMEDIA_MATERIAL)
{
// When selecting an object with a pbr and UI combo is not set,
@@ -1677,7 +1712,6 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
mCheckFullbright->setValue((S32)(fullbright_flag != 0));
mCheckFullbright->setEnabled(editable && !has_pbr_material);
mCheckFullbright->setTentative(!identical_fullbright);
- mComboMatMedia->setEnabledByValue("Materials", !has_pbr_material);
}
// Repeats per meter
diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp
index e1f2d7588c..b4adbe0819 100644
--- a/indra/newview/llpanelgrouproles.cpp
+++ b/indra/newview/llpanelgrouproles.cpp
@@ -1938,9 +1938,11 @@ LLPanelGroupRolesSubTab::LLPanelGroupRolesSubTab()
mRolesList(NULL),
mAssignedMembersList(NULL),
mAllowedActionsList(NULL),
+ mActionDescription(NULL),
mRoleName(NULL),
mRoleTitle(NULL),
mRoleDescription(NULL),
+ mMembersNotLoadedLbl(NULL),
mMemberVisibleCheck(NULL),
mDeleteRoleButton(NULL),
mCopyRoleButton(NULL),
@@ -1969,6 +1971,7 @@ bool LLPanelGroupRolesSubTab::postBuildSubTab(LLView* root)
mAssignedMembersList = parent->getChild<LLNameListCtrl>("role_assigned_members");
mAllowedActionsList = parent->getChild<LLScrollListCtrl>("role_allowed_actions");
mActionDescription = parent->getChild<LLTextEditor>("role_action_description");
+ mMembersNotLoadedLbl = parent->getChild<LLUICtrl>("members_not_loaded");
mRoleName = parent->getChild<LLLineEditor>("role_name");
mRoleTitle = parent->getChild<LLLineEditor>("role_title");
@@ -2199,12 +2202,18 @@ void LLPanelGroupRolesSubTab::update(LLGroupChange gc)
}
}
- if ((GC_ROLE_MEMBER_DATA == gc || GC_MEMBER_DATA == gc)
- && gdatap
- && gdatap->isMemberDataComplete()
- && gdatap->isRoleMemberDataComplete())
+ if (gdatap && gdatap->isMemberDataComplete())
{
- buildMembersList();
+ if ((GC_ROLE_MEMBER_DATA == gc || GC_MEMBER_DATA == gc)
+ && gdatap->isRoleMemberDataComplete())
+ {
+ buildMembersList();
+ }
+ mMembersNotLoadedLbl->setVisible(false);
+ }
+ else
+ {
+ mMembersNotLoadedLbl->setVisible(true);
}
}
@@ -3224,6 +3233,12 @@ void LLPanelGroupBanListSubTab::setBanCount(U32 ban_count)
void LLPanelGroupBanListSubTab::populateBanList()
{
+ if (mGroupID.isNull())
+ {
+ mBanList->deleteAllItems();
+ return;
+ }
+
LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID);
if(!gdatap)
{
diff --git a/indra/newview/llpanelgrouproles.h b/indra/newview/llpanelgrouproles.h
index e320efa1c7..18c0e5dfea 100644
--- a/indra/newview/llpanelgrouproles.h
+++ b/indra/newview/llpanelgrouproles.h
@@ -297,6 +297,7 @@ protected:
LLLineEditor* mRoleTitle;
LLTextEditor* mRoleDescription;
+ LLUICtrl* mMembersNotLoadedLbl;
LLCheckBoxCtrl* mMemberVisibleCheck;
LLButton* mDeleteRoleButton;
LLButton* mCreateRoleButton;
diff --git a/indra/newview/llpanellandmedia.cpp b/indra/newview/llpanellandmedia.cpp
index 294bd4021d..feea97c4a9 100644
--- a/indra/newview/llpanellandmedia.cpp
+++ b/indra/newview/llpanellandmedia.cpp
@@ -135,8 +135,6 @@ void LLPanelLandMedia::refresh()
mMediaURLEdit->setText(parcel->getMediaURL());
mMediaURLEdit->setEnabled( false );
- getChild<LLUICtrl>("current_url")->setValue(parcel->getMediaCurrentURL());
-
mMediaDescEdit->setText(parcel->getMediaDesc());
mMediaDescEdit->setEnabled( can_change_media );
@@ -234,12 +232,9 @@ void LLPanelLandMedia::setMediaURL(const std::string& media_url)
LLParcel *parcel = mParcel->getParcel();
if(parcel)
parcel->setMediaCurrentURL(media_url);
- // LLViewerMedia::navigateHome();
mMediaURLEdit->onCommit();
- // LLViewerParcelMedia::sendMediaNavigateMessage(media_url);
- getChild<LLUICtrl>("current_url")->setValue(media_url);
}
std::string LLPanelLandMedia::getMediaURL()
{
@@ -322,8 +317,6 @@ void LLPanelLandMedia::onResetBtn(void *userdata)
LLParcel* parcel = self->mParcel->getParcel();
// LLViewerMedia::navigateHome();
self->refresh();
- self->getChild<LLUICtrl>("current_url")->setValue(parcel->getMediaURL());
- // LLViewerParcelMedia::sendMediaNavigateMessage(parcel->getMediaURL());
}
diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp
index b68599dae9..9b5a339958 100644
--- a/indra/newview/llpanellogin.cpp
+++ b/indra/newview/llpanellogin.cpp
@@ -808,6 +808,8 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl)
case LLSLURL::HOME_LOCATION:
//location_combo->setCurrentByIndex(0); // home location
break;
+ case LLSLURL::LAST_LOCATION:
+ break;
default:
LL_WARNS("AppInit")<<"invalid login slurl, using home"<<LL_ENDL;
diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp
index 04eebcefc1..b8adb2d919 100644
--- a/indra/newview/llpanelmaininventory.cpp
+++ b/indra/newview/llpanelmaininventory.cpp
@@ -253,11 +253,21 @@ bool LLPanelMainInventory::postBuild()
LLParamSDParser parser;
parser.readSD(recent_items, p);
mRecentPanel->getFilter().fromParams(p);
- mRecentPanel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::RECENTITEMS_SORT_ORDER));
+
+ // Restore sort order if it was saved
+ if (p.order.isProvided())
+ {
+ mRecentPanel->setSortOrder(p.order());
+ }
+ else
+ {
+ mRecentPanel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::RECENTITEMS_SORT_ORDER));
+ }
}
}
if(mActivePanel)
{
+ // 'all items' tab
if(savedFilterState.has(mActivePanel->getFilter().getName()))
{
LLSD items = savedFilterState.get(mActivePanel->getFilter().getName());
diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp
index 557c7bbd7b..e0dbd9acd2 100644
--- a/indra/newview/llpanelmarketplaceinboxinventory.cpp
+++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -82,10 +82,20 @@ LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge
LLInboxFolderViewFolder::Params params;
- params.name = bridge->getDisplayName();
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ // Only usable for debug and first call has a large
+ // overhead from search string construction.
+ // As inventory names aren't unique and can change,
+ // there is little we can use them for in release builds.
+ params.name = bridge->getName();
+#else
+ // We don't have a source of unique names and inventory
+ // items can reach millions in quantity, just use
+ // a short descriptor
+ params.name = "fld";
+#endif
params.root = mFolderRoot.get();
params.listener = bridge;
- params.tool_tip = params.name;
params.font_color = item_color;
params.font_highlight_color = item_color;
params.allow_drop = allow_drop;
@@ -99,12 +109,22 @@ LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * b
LLInboxFolderViewItem::Params params;
- params.name = bridge->getDisplayName();
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ // Only usable for debug and first call has a large
+ // overhead from search string construction.
+ // As inventory names aren't unique and can change,
+ // there is little we can use them for in release builds.
+ params.name = bridge->getName();
+#else
+ // We don't have a source of unique names and inventory
+ // items can reach millions in quantity, just use
+ // a short descriptor
+ params.name = "itm";
+#endif
params.creation_date = bridge->getCreationDate();
params.root = mFolderRoot.get();
params.listener = bridge;
params.rect = LLRect (0, 0, 0, 0);
- params.tool_tip = params.name;
params.font_color = item_color;
params.font_highlight_color = item_color;
diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp
index fc1f70ed73..5f5c8302fe 100644
--- a/indra/newview/llpanelobjectinventory.cpp
+++ b/indra/newview/llpanelobjectinventory.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -1623,7 +1623,6 @@ void LLPanelObjectInventory::createViewsForCategory(LLInventoryObject::object_li
params.name = obj->getName();
params.root = mFolders;
params.listener = bridge;
- params.tool_tip = params.name;
params.font_color = item_color;
params.font_highlight_color = item_color;
view = LLUICtrlFactory::create<LLFolderViewFolder>(params);
@@ -1637,7 +1636,6 @@ void LLPanelObjectInventory::createViewsForCategory(LLInventoryObject::object_li
params.listener = bridge;
params.creation_date = bridge->getCreationDate();
params.rect = LLRect();
- params.tool_tip = params.name;
params.font_color = item_color;
params.font_highlight_color = item_color;
view = LLUICtrlFactory::create<LLFolderViewItem>(params);
diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp
index 5c866905d6..5dd4960542 100644
--- a/indra/newview/llpanelplaces.cpp
+++ b/indra/newview/llpanelplaces.cpp
@@ -84,6 +84,7 @@ static const std::string LANDMARK_INFO_TYPE = "landmark";
static const std::string REMOTE_PLACE_INFO_TYPE = "remote_place";
static const std::string TELEPORT_HISTORY_INFO_TYPE = "teleport_history";
static const std::string LANDMARK_TAB_INFO_TYPE = "open_landmark_tab";
+static const std::string TELEPORT_HISTORY_TAB_INFO_TYPE = "open_teleport_history_tab";
// Support for secondlife:///app/parcel/{UUID}/about SLapps
class LLParcelHandler : public LLCommandHandler
@@ -411,6 +412,15 @@ void LLPanelPlaces::onOpen(const LLSD& key)
// Update the buttons at the bottom of the panel
updateVerbs();
}
+ else if (key_type == TELEPORT_HISTORY_TAB_INFO_TYPE)
+ {
+ // toggle twice, similar to LANDMARK_TAB_INFO_TYPE
+ togglePlaceInfoPanel(false);
+ mPlaceInfoType = key_type;
+ togglePlaceInfoPanel(false);
+ onTabSelected();
+ updateVerbs();
+ }
else if (key_type == CREATE_PICK_TYPE)
{
LLUUID item_id = key["item_id"];
@@ -1104,6 +1114,18 @@ void LLPanelPlaces::togglePlaceInfoPanel(bool visible)
}
}
}
+ else if (mPlaceInfoType == TELEPORT_HISTORY_TAB_INFO_TYPE)
+ {
+ mLandmarkInfo->setVisible(false);
+ mPlaceProfile->setVisible(false);
+ if (!visible)
+ {
+ if (LLPanel* teleport_history_panel = mTabContainer->getPanelByName("Teleport History"))
+ {
+ mTabContainer->selectTabPanel(teleport_history_panel);
+ }
+ }
+ }
}
// virtual
diff --git a/indra/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp
index c9626bf9ea..6870d719af 100644
--- a/indra/newview/llpanelprofilepicks.cpp
+++ b/indra/newview/llpanelprofilepicks.cpp
@@ -622,6 +622,7 @@ bool LLPanelProfilePick::postBuild()
{
mPickName = getChild<LLLineEditor>("pick_name");
mPickDescription = getChild<LLTextEditor>("pick_desc");
+ mPickLocation = getChild<LLLineEditor>("pick_location");
mSaveButton = getChild<LLButton>("save_changes_btn");
mCreateButton = getChild<LLButton>("create_changes_btn");
mCancelButton = getChild<LLButton>("cancel_changes_btn");
@@ -646,11 +647,20 @@ bool LLPanelProfilePick::postBuild()
mPickDescription->setKeystrokeCallback(boost::bind(&LLPanelProfilePick::onPickChanged, this, _1));
mPickDescription->setFocusReceivedCallback(boost::bind(&LLPanelProfilePick::onDescriptionFocusReceived, this));
- getChild<LLUICtrl>("pick_location")->setEnabled(false);
+ mPickLocation->setEnabled(false);
return true;
}
+void LLPanelProfilePick::reshape(S32 width, S32 height, bool called_from_parent)
+{
+ LLPanelProfilePropertiesProcessorTab::reshape(width, height, called_from_parent);
+ if (mPickLocation)
+ {
+ mPickLocation->setCursor(0);
+ }
+}
+
void LLPanelProfilePick::onDescriptionFocusReceived()
{
if (!mIsEditing && getSelfProfile())
@@ -749,7 +759,14 @@ void LLPanelProfilePick::setPickLocation(const LLUUID &parcel_id, const std::str
void LLPanelProfilePick::setPickLocation(const std::string& location)
{
- getChild<LLUICtrl>("pick_location")->setValue(location);
+ mPickLocation->setValue(location);
+ // Pick location can be set with a long 'substitute' value or
+ // just a long value.
+ // If user sets cursor at the end, application of the substitute
+ // value can shift text from visible are to the left. When text
+ // gets restored or set, text position isn't, so just drop cursor
+ // position.
+ mPickLocation->setCursor(0);
mPickLocationStr = location;
mLastRequestTimer.reset();
}
@@ -896,6 +913,10 @@ void LLPanelProfilePick::sendParcelInfoRequest()
void LLPanelProfilePick::processParcelInfo(const LLParcelData& parcel_data)
{
+ // Region might have moved since the pick was saved; refresh the stored global position
+ // using the parcel info so map/teleport use the current location.
+ setPosGlobal(LLVector3d(parcel_data.global_x, parcel_data.global_y, parcel_data.global_z));
+
setPickLocation(createLocationText(LLStringUtil::null, parcel_data.name, parcel_data.sim_name, getPosGlobal()));
// We have received parcel info for the requested ID so clear it now.
diff --git a/indra/newview/llpanelprofilepicks.h b/indra/newview/llpanelprofilepicks.h
index 847ac57cea..b925f09f92 100644
--- a/indra/newview/llpanelprofilepicks.h
+++ b/indra/newview/llpanelprofilepicks.h
@@ -112,6 +112,8 @@ public:
void setAvatarId(const LLUUID& avatar_id) override;
+ void reshape(S32 width, S32 height, bool called_from_parent = true) override;
+
void setPickId(const LLUUID& id) { mPickId = id; }
virtual LLUUID& getPickId() { return mPickId; }
@@ -228,13 +230,14 @@ public:
protected:
- LLTextureCtrl* mSnapshotCtrl;
- LLLineEditor* mPickName;
- LLTextEditor* mPickDescription;
- LLButton* mSetCurrentLocationButton;
- LLButton* mSaveButton;
- LLButton* mCreateButton;
- LLButton* mCancelButton;
+ LLTextureCtrl* mSnapshotCtrl = nullptr;
+ LLLineEditor* mPickName = nullptr;
+ LLTextEditor* mPickDescription = nullptr;
+ LLLineEditor* mPickLocation = nullptr;
+ LLButton* mSetCurrentLocationButton = nullptr;
+ LLButton* mSaveButton = nullptr;
+ LLButton* mCreateButton = nullptr;
+ LLButton* mCancelButton = nullptr;
LLVector3d mPosGlobal;
LLUUID mParcelId;
diff --git a/indra/newview/llpanelsnapshot.cpp b/indra/newview/llpanelsnapshot.cpp
index 56c0294dbe..daf1986522 100644
--- a/indra/newview/llpanelsnapshot.cpp
+++ b/indra/newview/llpanelsnapshot.cpp
@@ -64,7 +64,12 @@ bool LLPanelSnapshot::postBuild()
{
S32 w = getTypedPreviewWidth();
S32 h = getTypedPreviewHeight();
- getChild<LLUICtrl>("save_btn")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost(w, h)));
+ LLUICtrl *save_btn = findChild<LLUICtrl>("save_btn");
+ if (save_btn)
+ {
+ // Not all snapshot floaters have a save button
+ save_btn->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost(w, h)));
+ }
getChild<LLUICtrl>(getImageSizeComboName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onResolutionComboCommit, this, _1));
if (!getWidthSpinnerName().empty())
{
@@ -193,7 +198,11 @@ void LLPanelSnapshot::updateImageQualityLevel()
quality_lvl = LLTrans::getString("snapshot_quality_very_high");
}
- getChild<LLTextBox>("image_quality_level")->setTextArg("[QLVL]", quality_lvl);
+ LLTextBox* quality_lvl_ctrl = findChild<LLTextBox>("image_quality_level");
+ if (quality_lvl_ctrl)
+ {
+ quality_lvl_ctrl->setTextArg("[QLVL]", quality_lvl);
+ }
}
void LLPanelSnapshot::goBack()
diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp
index 5aaa53b732..609177046e 100644
--- a/indra/newview/llpanelvoicedevicesettings.cpp
+++ b/indra/newview/llpanelvoicedevicesettings.cpp
@@ -149,7 +149,7 @@ void LLPanelVoiceDeviceSettings::draw()
LLColor4 color;
if (power_bar_idx < discrete_power)
{
- color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("SpeakingColor");
+ color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("OutfitGalleryItemSelected");
}
else
{
diff --git a/indra/newview/llpreviewtexture.cpp b/indra/newview/llpreviewtexture.cpp
index 800fc7ec0c..e886957049 100644
--- a/indra/newview/llpreviewtexture.cpp
+++ b/indra/newview/llpreviewtexture.cpp
@@ -300,7 +300,8 @@ void LLPreviewTexture::saveAs()
if( mLoadingFullImage )
return;
- std::string filename = getItem() ? LLDir::getScrubbedFileName(getItem()->getName()) : LLStringUtil::null;
+ // startPicker will sanitize the name
+ std::string filename = getItem() ? getItem()->getName() : LLStringUtil::null;
LLFilePickerReplyThread::startPicker(boost::bind(&LLPreviewTexture::saveTextureToFile, this, _1), LLFilePicker::FFSAVE_TGAPNG, filename);
}
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index cf64cec9b2..a09b54ffac 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -253,7 +253,6 @@ LLSelectMgr::LLSelectMgr()
LLSelectMgr::~LLSelectMgr()
{
clearSelections();
- mSlectionLodModChangedConnection.disconnect();
}
void LLSelectMgr::clearSelections()
diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h
index 11aad3b806..792a37297f 100644
--- a/indra/newview/llselectmgr.h
+++ b/indra/newview/llselectmgr.h
@@ -943,7 +943,6 @@ private:
bool mForceSelection;
std::vector<LLAnimPauseRequest> mPauseRequests;
- boost::signals2::connection mSlectionLodModChangedConnection;
};
// *DEPRECATED: For callbacks or observers, use
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index ec819a046a..3aa2c92590 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -24,6 +24,72 @@
* $/LicenseInfo$
*/
+//
+// LOGIN AND CONNECTION SEQUENCE OVERVIEW
+// ======================================
+// The Viewer connects to the SL service in two phases: HTTP authentication
+// followed by UDP "Circuit" establishment to the first Simulator.
+//
+// PHASE 1: HTTP LOGIN (see lllogin.cpp, process_login_success_response())
+// -----------------------------------------------------------------------
+// Viewer sends an XMLRPC HTTP POST to LoginServer containing:
+// - Credentials (first name, last name, password)
+// - Client version, channel, MAC address, machine ID
+// - Start location preferences
+//
+// Login-server responds with critical connection data:
+// - agent_id, session_id, secure_session_id (authentication tokens)
+// - Circuit_code (used to establish UDP Circuit with Simulator)
+// - sim_ip, sim_port (Simulator address to connect to)
+// - seed_capability (base URL for HTTP capability requests)
+// - region_x, region_y (region grid coordinates)
+//
+// PHASE 2: UDP CIRCUIT ESTABLISHMENT (see idle_startup() state machine below)
+// ---------------------------------------------------------------------------
+// After HTTP login succeeds, Viewer establishes a UDP Circuit with the
+// simulator. This also happens whenever the Viewer connects to new Simulators
+// in the same session. The following UDP messages are exchanged:
+//
+// 1. UseCircuitCode (Viewer -> Simulator)
+// - Sent in STATE_WORLD_INIT
+// - Contains: Circuit_code, session_id, agent_id
+// - Establishes the UDP Circuit with the Simulator
+//
+// 2. RegionHandshake (Simulator -> Viewer)
+// - Handled by process_region_handshake() in llworld.cpp
+// - Contains: region name, terrain textures, water height, region flags
+// - Viewer responds with RegionHandshakeReply
+//
+// 3. CompleteAgentMovement (Viewer -> Simulator)
+// - Sent in STATE_AGENT_SEND via send_complete_agent_movement()
+// - Signals the Viewer is ready to enter the world
+//
+// 4. AgentMovementComplete (Simulator -> Viewer)
+// - Handled by process_agent_movement_complete() in llviewermessage.cpp
+// - Contains: final agent position, look_at direction, region handle
+// - Sets gAgentMovementCompleted = true
+// - Agent is now fully connected to the region
+//
+// STARTUP STATE MACHINE
+// ---------------------
+// The connection sequence is managed by idle_startup() which progresses
+// through these key states:
+//
+// STATE_LOGIN_WAIT - Waiting for HTTP login response
+// STATE_LOGIN_PROCESS_RESPONSE - Processing login response data
+// STATE_WORLD_INIT - Send UseCircuitCode, enable UDP Circuit
+// STATE_WORLD_WAIT - Wait for Circuit acknowledgment
+// STATE_AGENT_SEND - Send CompleteAgentMovement
+// STATE_AGENT_WAIT - Wait for AgentMovementComplete
+// STATE_INVENTORY_SEND - Agent connected, begin loading inventory
+//
+// HTTP CAPABILITIES
+// -----------------
+// After UDP connection, Viewer fetches "capability" URLs from the
+// seed_capability endpoint. These provide HTTP endpoints for various
+// services (inventory, textures, etc.) that supplement the UDP protocol.
+//
+
#include "llviewerprecompiledheaders.h"
#include "llappviewer.h"
@@ -276,7 +342,6 @@ void show_first_run_dialog();
bool first_run_dialog_callback(const LLSD& notification, const LLSD& response);
void set_startup_status(const F32 frac, const std::string& string, const std::string& msg);
bool login_alert_status(const LLSD& notification, const LLSD& response);
-void use_circuit_callback(void**, S32 result);
void register_viewer_callbacks(LLMessageSystem* msg);
void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32);
bool callback_choose_gender(const LLSD& notification, const LLSD& response);
@@ -335,7 +400,7 @@ void do_startup_frame()
break;
}
}
- if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0)
+ if (needs_drain || gMessageSystem->getNumBufferedPackets() > 0)
{
gMessageSystem->drainUdpSocket();
}
@@ -719,7 +784,7 @@ bool idle_startup()
F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage");
- msg->mPacketRing.setDropPercentage(dropPercent);
+ msg->setDropPercentage(dropPercent);
}
LL_INFOS("AppInit") << "Message System Initialized." << LL_ENDL;
@@ -831,6 +896,7 @@ bool idle_startup()
set_startup_status(0.03f, msg.c_str(), gAgent.mMOTD.c_str());
do_startup_frame();
// LLViewerMedia::initBrowser();
+ LLAppViewer::instance()->createInitedMarker();
LLStartUp::setStartupState( STATE_LOGIN_SHOW );
return false;
}
@@ -1713,13 +1779,48 @@ bool idle_startup()
gUseCircuitCallbackCalled = false;
msg->enableCircuit(gFirstSim, true);
- // now, use the circuit info to tell simulator about us!
+
+ // UDP CONNECTION STEP 1: Send UseCircuitCode
+ // This is the first UDP message sent to Simulator after HTTP login.
+ // It establishes the UDP circuit using the circuit_code received from
+ // LoginServer. Simulator will respond with an ACK, then send
+ // RegionHandshake message with region details.
LL_INFOS("AppInit") << "viewer: UserLoginLocationReply() Enabling " << gFirstSim << " with code " << msg->mOurCircuitCode << LL_ENDL;
msg->newMessageFast(_PREHASH_UseCircuitCode);
msg->nextBlockFast(_PREHASH_CircuitCode);
msg->addU32Fast(_PREHASH_Code, msg->mOurCircuitCode);
msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
msg->addUUIDFast(_PREHASH_ID, gAgent.getID());
+
+ // build a lambda to be used as callback on ACK or timeout
+ void (*use_circuit_callback)(void**, S32) = [](void**, S32 result)
+ {
+ // bail if we're quitting.
+ if(LLApp::isExiting()) return;
+ if( !gUseCircuitCallbackCalled )
+ {
+ gUseCircuitCallbackCalled = true;
+ if (result != LL_ERR_NOERR)
+ {
+ // Make sure user knows something bad happened. JC
+ LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL;
+ if (gRememberPassword)
+ {
+ LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status);
+ }
+ else
+ {
+ LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status);
+ }
+ reset_login();
+ }
+ else
+ {
+ gGotUseCircuitCodeAck = true;
+ }
+ }
+ };
+
msg->sendReliable(
gFirstSim,
gSavedSettings.getS32("UseCircuitCodeMaxRetries"),
@@ -1737,6 +1838,9 @@ bool idle_startup()
//---------------------------------------------------------------------
// World Wait
//---------------------------------------------------------------------
+ // UDP CONNECTION STEP 2: Wait for UseCircuitCode acknowledgment
+ // While waiting, Simulator also sends RegionHandshake (handled by
+ // process_region_handshake() in llworld.cpp) containing region info.
if(STATE_WORLD_WAIT == LLStartUp::getStartupState())
{
LL_DEBUGS("AppInit") << "Waiting for simulator ack...." << LL_ENDL;
@@ -1752,13 +1856,16 @@ bool idle_startup()
//---------------------------------------------------------------------
// Agent Send
//---------------------------------------------------------------------
+ // UDP CONNECTION STEP 3: Send CompleteAgentMovement
+ // After the circuit is established and RegionHandshake received, we signal
+ // to Simulator the Viewer is ready to enter the world.
if (STATE_AGENT_SEND == LLStartUp::getStartupState())
{
LL_DEBUGS("AppInit") << "Connecting to region..." << LL_ENDL;
set_startup_status(0.60f, LLTrans::getString("LoginConnectingToRegion"), gAgent.mMOTD);
do_startup_frame();
- // register with the message system so it knows we're
- // expecting this message
+ // Register handler process_agent_movement_complete for AgentMovementComplete -
+ // the final UDP message confirming the agent is connected.
LLMessageSystem* msg = gMessageSystem;
msg->setHandlerFuncFast(
_PREHASH_AgentMovementComplete,
@@ -1801,12 +1908,17 @@ bool idle_startup()
//---------------------------------------------------------------------
// Agent Wait
//---------------------------------------------------------------------
+ // UDP CONNECTION STEP 4: Wait for AgentMovementComplete
+ // Simulator responds with the agent's confirmed position and look_at
+ // direction. Once received, gAgentMovementCompleted is set true and the
+ // agent is fully connected to the region.
if (STATE_AGENT_WAIT == LLStartUp::getStartupState())
{
do_startup_frame();
if (gAgentMovementCompleted)
{
+ // Connection complete - agent is now in-world
LLStartUp::setStartupState( STATE_INVENTORY_SEND );
}
do_startup_frame();
@@ -2805,34 +2917,6 @@ bool login_alert_status(const LLSD& notification, const LLSD& response)
}
-void use_circuit_callback(void**, S32 result)
-{
- // bail if we're quitting.
- if(LLApp::isExiting()) return;
- if( !gUseCircuitCallbackCalled )
- {
- gUseCircuitCallbackCalled = true;
- if (result)
- {
- // Make sure user knows something bad happened. JC
- LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL;
- if (gRememberPassword)
- {
- LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status);
- }
- else
- {
- LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status);
- }
- reset_login();
- }
- else
- {
- gGotUseCircuitCodeAck = true;
- }
- }
-}
-
void register_viewer_callbacks(LLMessageSystem* msg)
{
msg->setHandlerFuncFast(_PREHASH_LayerData, process_layer_data);
@@ -3691,6 +3775,14 @@ bool init_benefits(LLSD& response)
return succ;
}
+// HTTP LOGIN RESPONSE PROCESSING
+// Called after successful HTTP XMLRPC authentication. Extracts critical data
+// from LoginServer response needed to establish the UDP connection:
+// - agent_id, session_id, secure_session_id (authentication tokens)
+// - circuit_code (used in UseCircuitCode UDP message)
+// - sim_ip, sim_port (simulator address for UDP circuit)
+// - seed_capability (URL for fetching HTTP capability endpoints)
+//
bool process_login_success_response()
{
LLSD response = LLLoginInstance::getInstance()->getResponse();
@@ -3813,6 +3905,8 @@ bool process_login_success_response()
gAgentStartLocation.assign(text);
}
+ // Extract UDP circuit parameters from login response.
+ // These are used in STATE_WORLD_INIT to establish the UDP circuit.
text = response["circuit_code"].asString();
if(!text.empty())
{
diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp
index 44bada13c2..0df3c27f3e 100644
--- a/indra/newview/llstatusbar.cpp
+++ b/indra/newview/llstatusbar.cpp
@@ -143,6 +143,7 @@ LLStatusBar::~LLStatusBar()
// virtual
void LLStatusBar::draw()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
refresh();
LLPanel::draw();
}
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 51ade60827..144940c705 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -1297,7 +1297,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
else
{
mCanUseCapability = false;
- if (gDisconnected)
+ if (gDisconnected || LLAppViewer::isExiting())
{
// We lost connection or are shutting down.
mCanUseHTTP = false;
@@ -1315,6 +1315,12 @@ bool LLTextureFetchWorker::doWork(S32 param)
else
{
mCanUseCapability = false;
+ if (gDisconnected || LLAppViewer::isExiting())
+ {
+ // We lost connection or are shutting down.
+ mCanUseHTTP = false;
+ return true; // abort
+ }
mRegionRetryAttempt++;
mRegionRetryTimer.setTimerExpirySec(CAP_MISSING_EXPIRATION_DELAY);
// This will happen if not logged in or if a region deoes not have HTTP Texture enabled
diff --git a/indra/newview/lltoolbrush.cpp b/indra/newview/lltoolbrush.cpp
index cf7b123fa7..d31dc078e5 100644
--- a/indra/newview/lltoolbrush.cpp
+++ b/indra/newview/lltoolbrush.cpp
@@ -685,7 +685,10 @@ bool LLToolBrushLand::canTerraformParcel(LLViewerRegion* regionp) const
if (selected_parcel)
{
bool owner_release = LLViewerParcelMgr::isParcelOwnedByAgent(selected_parcel, GP_LAND_ALLOW_EDIT_LAND);
- is_terraform_allowed = ( gAgent.canManageEstate() || (selected_parcel->getOwnerID() == regionp->getOwner()) || owner_release);
+ is_terraform_allowed = ( regionp->canManageEstate()
+ || (selected_parcel->getOwnerID() == regionp->getOwner())
+ || owner_release
+ || selected_parcel->getAllowTerraform());
}
return is_terraform_allowed;
diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp
index eb8ace339e..33fd9f2dd9 100644
--- a/indra/newview/lltooldraganddrop.cpp
+++ b/indra/newview/lltooldraganddrop.cpp
@@ -2578,7 +2578,12 @@ bool is_water_exclusion_face(LLViewerObject* obj, S32 face)
bool exclude_water = (image->getID() == IMG_ALPHA_GRAD) && obj->isImageAlphaBlended(face);
// transparency
- exclude_water &= (obj->getTE(face)->getColor().mV[VALPHA] == 1);
+ // getTE gets from a different source than getTEImage, so check for null
+ LLTextureEntry* te = obj->getTE(face);
+ if (te)
+ {
+ exclude_water &= (te->getColor().mV[VALPHA] == 1);
+ }
//absence of normal and specular textures
image = obj->getTENormalMap(face);
diff --git a/indra/newview/lltoolmorph.cpp b/indra/newview/lltoolmorph.cpp
index 8ce9a8b632..11dd26309e 100644
--- a/indra/newview/lltoolmorph.cpp
+++ b/indra/newview/lltoolmorph.cpp
@@ -93,6 +93,20 @@ LLVisualParamHint::LLVisualParamHint(
{
LLVisualParamHint::sInstances.insert( this );
mBackgroundp = LLUI::getUIImage("avatar_thumb_bkgrnd.png");
+ if (mBackgroundp)
+ {
+ LLViewerFetchedTexture* tex = dynamic_cast<LLViewerFetchedTexture*>(mBackgroundp->getImage().get());
+ if (tex)
+ {
+ tex->setKnownDrawSize(width, height);
+ }
+
+ mBackgroundLoadedConnection = mBackgroundp->addLoadedCallback([this]()
+ {
+ mNeedsUpdate = true;
+ mDelayFrames = 0;
+ });
+ }
llassert(width != 0);
llassert(height != 0);
@@ -104,6 +118,7 @@ LLVisualParamHint::LLVisualParamHint(
LLVisualParamHint::~LLVisualParamHint()
{
LLVisualParamHint::sInstances.erase( this );
+ mBackgroundLoadedConnection.disconnect();
}
//virtual
@@ -143,12 +158,21 @@ void LLVisualParamHint::requestHintUpdates( LLVisualParamHint* exception1, LLVis
bool LLVisualParamHint::needsRender()
{
+ // needsRender gets called once per frame from
+ // LLViewerDynamicTexture::updateAllInstances()
return mNeedsUpdate && mDelayFrames-- <= 0 && !gAgentAvatarp->getIsAppearanceAnimating() && mAllowsUpdates;
}
void LLVisualParamHint::preRender(bool clear_depth)
{
- LLViewerWearable* wearable = (LLViewerWearable*)mWearablePtr;
+ LL_DEBUGS("ParamHint") << "preRender: param=" << mVisualParam->getName()
+ << " id=" << mVisualParam->getID()
+ << " weight=" << mVisualParamWeight
+ << " dirtyMesh=" << gAgentAvatarp->mDirtyMesh
+ << " pixelArea=" << gAgentAvatarp->mAdjustedPixelArea
+ << " appearanceAnimating=" << gAgentAvatarp->getIsAppearanceAnimating()
+ << LL_ENDL;
+ LLViewerWearable* wearable = dynamic_cast<LLViewerWearable*>(mWearablePtr);
if (wearable)
{
wearable->setVolatile(true);
@@ -167,6 +191,22 @@ void LLVisualParamHint::preRender(bool clear_depth)
{
gAgentAvatarp->updateGeometry(gAgentAvatarp->mDrawable);
gAgentAvatarp->updateLOD();
+
+ if (gAgentAvatarp->mDrawable->isState(LLDrawable::REBUILD_GEOMETRY)
+ || gAgentAvatarp->mDirtyMesh > 0)
+ {
+ // updateLOD marks meshes for an update.
+ LL_DEBUGS("ParamHint") << "preRender: updateMeshData"
+ << " param=" << mVisualParam->getName()
+ << " dirtyMesh=" << gAgentAvatarp->mDirtyMesh
+ << " rebuildGeom=" << gAgentAvatarp->mDrawable->isState(LLDrawable::REBUILD_GEOMETRY)
+ << LL_ENDL;
+ // Clear states and force update so that renderSkinned inside generateImpostor
+ // does not re-run updateMeshData() with a partially consistent mesh state,
+ // which would reassign mFace pointers on some meshes while others are
+ // mid-draw with stale vertex offsets.
+ gAgentAvatarp->updateMeshData();
+ }
}
else
{
@@ -246,8 +286,15 @@ bool LLVisualParamHint::render()
LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false);
- if (gAgentAvatarp->mDrawable.notNull())
+ llassert(!LLPipeline::sImpostorRender); // We are rendering this from UI, shouldn't be set
+ if (gAgentAvatarp->mDrawable.notNull() && !LLPipeline::sImpostorRender)
{
+ LL_DEBUGS("ParamHint") << "render: generating impostor"
+ << " param=" << mVisualParam->getName()
+ << " drawableState=0x" << std::hex << gAgentAvatarp->mDrawable->getState() << std::dec
+ << " dirtyMesh=" << gAgentAvatarp->mDirtyMesh
+ << " mMeshValid=" << gAgentAvatarp->mMeshValid
+ << LL_ENDL;
LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE);
gGL.flush();
gGL.setSceneBlendType(LLRender::BT_REPLACE);
@@ -258,7 +305,7 @@ bool LLVisualParamHint::render()
gAgentAvatarp->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight);
mWearablePtr->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight);
- LLViewerWearable* wearable = (LLViewerWearable*)mWearablePtr;
+ LLViewerWearable* wearable = dynamic_cast<LLViewerWearable*>(mWearablePtr);
if (wearable)
{
wearable->setVolatile(false);
diff --git a/indra/newview/lltoolmorph.h b/indra/newview/lltoolmorph.h
index fb62ba9bba..9f4b5f6fe9 100644
--- a/indra/newview/lltoolmorph.h
+++ b/indra/newview/lltoolmorph.h
@@ -95,6 +95,7 @@ protected:
LLJoint* mCamTargetJoint; // joint to target with preview camera
LLUIImagePtr mBackgroundp;
+ boost::signals2::scoped_connection mBackgroundLoadedConnection;
typedef std::set< LLVisualParamHint* > instance_list_t;
static instance_list_t sInstances;
diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp
index dcd1582eb9..2d5baae52e 100644
--- a/indra/newview/lltranslate.cpp
+++ b/indra/newview/lltranslate.cpp
@@ -155,7 +155,7 @@ void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, LLSD
{
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
- httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("getMerchantStatusCoro", httpPolicy);
+ httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("verifyKeyCoro", httpPolicy);
LLCore::HttpRequest::ptr_t httpRequest = std::make_shared<LLCore::HttpRequest>();
LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>();
LLCore::HttpHeaders::ptr_t httpHeaders = std::make_shared<LLCore::HttpHeaders>();
@@ -807,13 +807,7 @@ LLSD LLAzureTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCorout
LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray);
LLCore::BufferArrayStream outs(rawbody.get());
- static const std::string allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz "
- "0123456789"
- "-._~";
-
- outs << "[{\"text\":\"";
- outs << LLURI::escape(msg, allowed_chars);
- outs << "\"}]";
+ outs << boost::json::serialize(boost::json::array{ boost::json::object{{ "text", msg }} });
return adapter->postRawAndSuspend(request, url, rawbody, options, headers);
}
diff --git a/indra/newview/llurlfloaterdispatchhandler.cpp b/indra/newview/llurlfloaterdispatchhandler.cpp
index 9bee4870be..cf0657ea2f 100644
--- a/indra/newview/llurlfloaterdispatchhandler.cpp
+++ b/indra/newview/llurlfloaterdispatchhandler.cpp
@@ -29,15 +29,9 @@
#include "llurlfloaterdispatchhandler.h"
#include "llfloaterreg.h"
-#include "llfloaterhowto.h"
#include "llfloaterwebcontent.h"
#include "llsdserialize.h"
-#include "llviewercontrol.h"
#include "llviewergenericmessage.h"
-#include "llweb.h"
-
-// Example:
-// llOpenFloater("guidebook", "http://page.com", []);
// values specified by server side's dispatcher
// for llopenfloater
@@ -50,20 +44,12 @@ const std::string KEY_URL("floater_url");
const std::string KEY_PARAMS("floater_params");
// Supported floaters
-const std::string FLOATER_GUIDEBOOK("guidebook");
-const std::string FLOATER_HOW_TO("how_to"); // alias for guidebook
const std::string FLOATER_WEB_CONTENT("web_content");
// All arguments are palceholders! Server side will need to add validation first.
// Web content universal argument
const std::string KEY_TRUSTED_CONTENT("trusted_content");
-// Guidebook specific arguments
-const std::string KEY_WIDTH("width");
-const std::string KEY_HEGHT("height");
-const std::string KEY_CAN_CLOSE("can_close");
-const std::string KEY_TITLE("title");
-
// web_content specific arguments
const std::string KEY_SHOW_PAGE_TITLE("show_page_title");
const std::string KEY_ALLOW_ADRESS_ENTRY("allow_address_entry"); // It is not recomended to set this to true if trusted content is allowed
@@ -143,48 +129,7 @@ bool LLUrlFloaterDispatchHandler::operator()(const LLDispatcher *, const std::st
LLFloaterWebContent::Params params;
params.url = url;
- if (floater == FLOATER_GUIDEBOOK || floater == FLOATER_HOW_TO)
- {
- LL_DEBUGS("URLFloater") << "Opening how_to floater with parameters: " << message << LL_ENDL;
- if (command_params.isMap()) // by default is undefines
- {
- params.trusted_content = command_params.has(KEY_TRUSTED_CONTENT) ? command_params[KEY_TRUSTED_CONTENT].asBoolean() : false;
-
- // Script's side argument list can't include other lists, neither
- // there is a LLRect type, so expect just width and height
- if (command_params.has(KEY_WIDTH) && command_params.has(KEY_HEGHT))
- {
- LLRect rect(0, command_params[KEY_HEGHT].asInteger(), command_params[KEY_WIDTH].asInteger(), 0);
- params.preferred_media_size.setValue(rect);
- }
- }
-
- // Some locations will have customized guidebook, which this function easists for
- // only one instance of guidebook can exist at a time, so if this command arrives,
- // we need to close previous guidebook then reopen it.
-
- LLFloater* instance = LLFloaterReg::findInstance("guidebook");
- if (instance)
- {
- instance->closeHostedFloater();
- }
-
- LLFloaterReg::toggleInstanceOrBringToFront("guidebook", params);
-
- if (command_params.isMap())
- {
- LLFloater* instance = LLFloaterReg::findInstance("guidebook");
- if (command_params.has(KEY_CAN_CLOSE))
- {
- instance->setCanClose(command_params[KEY_CAN_CLOSE].asBoolean());
- }
- if (command_params.has(KEY_TITLE))
- {
- instance->setTitle(command_params[KEY_TITLE].asString());
- }
- }
- }
- else if (floater == FLOATER_WEB_CONTENT)
+ if (floater == FLOATER_WEB_CONTENT)
{
LL_DEBUGS("URLFloater") << "Opening web_content floater with parameters: " << message << LL_ENDL;
if (command_params.isMap()) // by default is undefines, might be better idea to init params from command_params
diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp
index 90eb977bba..927a5bbc2c 100644
--- a/indra/newview/llvelopack.cpp
+++ b/indra/newview/llvelopack.cpp
@@ -31,6 +31,7 @@
#include "llstring.h"
#include "llcorehttputil.h"
#include "llversioninfo.h"
+#include "llversioninfovars.h" // LL_VIEWER_CHANNEL
#include <boost/json.hpp>
#include <fstream>
@@ -43,15 +44,19 @@
#include "Velopack.h"
#if LL_WINDOWS
+#include "llappviewerwin32.h"
#include <windows.h>
#include <shlobj.h>
#include <shobjidl.h>
#include <shlwapi.h>
#include <objbase.h>
#include <filesystem>
+#include <propkey.h>
+#include <propvarutil.h>
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "propsys.lib")
#pragma comment(lib, "shell32.lib")
#endif // LL_WINDOWS
@@ -238,7 +243,7 @@ static bool custom_download_asset(void* user_data,
{
// The asset has already been downloaded at the coroutine level (before vpkc_download_updates).
// This callback just copies the pre-downloaded file to where Velopack expects it.
- // We cannot use getRawAndSuspend here — coroutine context is lost through the Rust FFI boundary.
+ // We cannot use getRawAndSuspend here - coroutine context is lost through the Rust FFI boundary.
if (sPreDownloadedAssetPath.empty())
{
LL_WARNS("Velopack") << "No pre-downloaded asset available" << LL_ENDL;
@@ -330,6 +335,17 @@ static std::wstring get_desktop_path()
return L"";
}
+static std::wstring get_app_user_model_id()
+{
+ // Format: CompanyName.ProductName
+ // This ID is used for:
+ // 1. Registry registration (HKCU\Software\Classes\Applications\{exe}\AppUserModelID)
+ // 2. Shortcut property (IPropertyStore::SetValue with PKEY_AppUserModel_ID)
+ // 3. Runtime process ID (SetCurrentProcessExplicitAppUserModelID if needed)
+ // Must be consistent across all uses for proper taskbar grouping.
+ return L"LindenLab." + get_app_name_oneword();
+}
+
static HRESULT create_shortcut(const std::wstring& shortcut_path,
const std::wstring& target_path,
const std::wstring& arguments,
@@ -354,6 +370,28 @@ static HRESULT create_shortcut(const std::wstring& shortcut_path,
PathRemoveFileSpecW(work_dir);
shell_link->SetWorkingDirectory(work_dir);
+ // Set AppUserModelID on the shortcut
+ IPropertyStore* prop_store = nullptr;
+ hr = shell_link->QueryInterface(IID_IPropertyStore, (void**)&prop_store);
+ if (SUCCEEDED(hr))
+ {
+ PROPVARIANT pv;
+ PropVariantInit(&pv);
+ std::wstring app_id = get_app_user_model_id();
+ hr = InitPropVariantFromString(app_id.c_str(), &pv);
+ if (SUCCEEDED(hr))
+ {
+ hr = prop_store->SetValue(PKEY_AppUserModel_ID, pv);
+ PropVariantClear(&pv);
+ if (SUCCEEDED(hr))
+ {
+ HRESULT hr_commit = prop_store->Commit();
+ if (FAILED(hr_commit)) hr = hr_commit;
+ }
+ }
+ prop_store->Release();
+ }
+
IPersistFile* persist_file = nullptr;
hr = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
if (SUCCEEDED(hr))
@@ -405,6 +443,42 @@ static void register_protocol_handler(const std::wstring& protocol,
}
}
+static void register_app_user_model_id(const std::wstring& exe_name)
+{
+ // Register AppUserModelID for the executable
+ // This allows Windows to properly group taskbar items when users drag-and-drop
+ // the exe to pin it, without requiring runtime calls to SetCurrentProcessExplicitAppUserModelID
+ std::wstring key_path = L"SOFTWARE\\Classes\\Applications\\" + exe_name;
+ HKEY hkey;
+
+ if (RegCreateKeyExW(HKEY_CURRENT_USER, key_path.c_str(), 0, NULL,
+ REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS)
+ {
+ std::wstring app_user_model_id = get_app_user_model_id();
+ LSTATUS status = RegSetValueExW(hkey, L"AppUserModelID", 0, REG_SZ,
+ (BYTE*)app_user_model_id.c_str(),
+ (DWORD)((app_user_model_id.size() + 1) * sizeof(wchar_t)));
+ RegCloseKey(hkey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ LL_DEBUGS("Velopack") << "Registered AppUserModelID: "
+ << ll_convert_wide_to_string(app_user_model_id)
+ << " for " << ll_convert_wide_to_string(exe_name) << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("Velopack") << "Failed to set AppUserModelID (error " << status << ") for "
+ << ll_convert_wide_to_string(exe_name) << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Velopack") << "Failed to register AppUserModelID for "
+ << ll_convert_wide_to_string(exe_name) << LL_ENDL;
+ }
+}
+
static bool get_shortcut_target(const std::wstring& lnk_path, std::wstring& target_path_str)
{
// Resolve the shortcut to check its target
@@ -717,6 +791,12 @@ static void unregister_protocol_handler(const std::wstring& protocol)
RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str());
}
+static void unregister_app_user_model_id(const std::wstring& exe_name)
+{
+ std::wstring key_path = L"SOFTWARE\\Classes\\Applications\\" + exe_name;
+ RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str());
+}
+
static void register_uninstall_info(const std::wstring& install_dir,
const std::wstring& app_name,
const std::wstring& version)
@@ -793,7 +873,9 @@ static void unregister_uninstall_info()
RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str());
}
-static void create_shortcuts(const std::wstring& install_dir, const std::wstring& app_name)
+static void create_shortcuts(
+ const std::wstring& install_dir,
+ const std::wstring& app_name)
{
std::wstring exe_path = install_dir + L"\\" + get_viewer_exe_name();
std::wstring start_menu_dir = get_start_menu_path() + L"\\" + app_name;
@@ -865,21 +947,32 @@ static void on_after_install(void* user_data, const char* app_version)
{
std::wstring install_dir = get_install_dir();
std::wstring app_name = get_app_name();
- std::wstring exe_path = install_dir + L"\\" + get_viewer_exe_name();
+ std::wstring exe_name = get_viewer_exe_name();
+ std::wstring exe_path = install_dir + L"\\" + exe_name;
register_protocol_handler(PROTOCOL_SECONDLIFE, L"URL:Second Life", exe_path);
register_protocol_handler(PROTOCOL_GRID_INFO, L"URL:Second Life", exe_path);
+
+ // Register AppUserModelID for taskbar pinning support
+ register_app_user_model_id(exe_name);
+
create_shortcuts(install_dir, app_name);
}
static void on_before_uninstall(void* user_data, const char* app_version)
{
std::wstring app_name = get_app_name();
+ std::wstring exe_name = get_viewer_exe_name();
unregister_protocol_handler(PROTOCOL_SECONDLIFE);
unregister_protocol_handler(PROTOCOL_GRID_INFO);
- unregister_uninstall_info();
+ unregister_app_user_model_id(exe_name);
remove_shortcuts(app_name);
+
+ std::wstring install_dir = get_install_dir();
+ LLAppViewerWin32::sendShutdownToOtherInstances(install_dir);
+
+ unregister_uninstall_info();
}
static void on_log_message(void* user_data, const char* level, const char* message)
@@ -998,7 +1091,7 @@ static void ensure_update_manager(bool allow_downgrade)
LL_INFOS("Velopack") << "Auto-detect failed (" << ll_safe_string(err)
<< "), falling back to explicit locator" << LL_ENDL;
- // Auto-detection failed — construct an explicit locator.
+ // Auto-detection failed - construct an explicit locator.
// This handles legacy DMG installs that don't have Velopack's
// install state (UpdateMac, sq.version) in the bundle.
vpkc_locator_config_t locator = {};
@@ -1166,7 +1259,7 @@ static void on_downloading_closed(const LLSD& notification, const LLSD& response
sDownloadingNotification = nullptr;
if (sIsRequired)
{
- // User closed the downloading dialog during a required update — re-show it
+ // User closed the downloading dialog during a required update - re-show it
show_downloading_notification(sTargetVersion);
}
}
@@ -1255,12 +1348,12 @@ void velopack_check_for_updates(const std::string& required_version, const std::
// strictly lower than what we're running (e.g., a retracted build).
bool has_required = !required_version.empty();
int ver_cmp = has_required ? compare_running_version(required_version) : 0;
- bool allow_downgrade = ver_cmp > 0; // running > required → rollback scenario
+ bool allow_downgrade = ver_cmp > 0; // running > required -> rollback scenario
ensure_update_manager(allow_downgrade);
if (!sUpdateManager)
return;
- // Ask Velopack to check its feed — this is the source of truth
+ // Ask Velopack to check its feed - this is the source of truth
vpkc_update_info_t* update_info = nullptr;
vpkc_update_check_t result = vpkc_check_for_updates(sUpdateManager, &update_info);
@@ -1286,7 +1379,7 @@ void velopack_check_for_updates(const std::string& required_version, const std::
sPendingCheckInfo = update_info;
// Determine if this is mandatory: running version is below VVM's required floor
- bool is_required = ver_cmp < 0; // running < required → must update
+ bool is_required = ver_cmp < 0; // running < required -> must update
sIsRequired = is_required;
if (is_required)
@@ -1297,12 +1390,12 @@ void velopack_check_for_updates(const std::string& required_version, const std::
return;
}
- // Optional update — check user preference
+ // Optional update - check user preference
U32 updater_setting = gSavedSettings.getU32("UpdaterServiceSetting");
if (updater_setting == 3)
{
- // "Install each update automatically" — download silently, apply on quit
+ // "Install each update automatically" - download silently, apply on quit
LL_INFOS("Velopack") << "Optional update to " << target_version
<< ", downloading automatically (UpdaterServiceSetting=3)" << LL_ENDL;
velopack_download_pending_update();
diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp
index 5b580ef415..296ba1d715 100644
--- a/indra/newview/llviewerassetstorage.cpp
+++ b/indra/newview/llviewerassetstorage.cpp
@@ -506,7 +506,7 @@ void LLViewerAssetStorage::assetRequestCoro(
if (!gAgent.getRegion()->capabilitiesReceived())
{
- LL_WARNS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL;
+ LL_INFOS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL;
LLEventStream capsRecv("waitForCaps", true);
@@ -533,8 +533,8 @@ void LLViewerAssetStorage::assetRequestCoro(
return;
}
- LL_WARNS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL;
- LL_WARNS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL;
+ LL_INFOS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL;
+ LL_INFOS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL;
}
if (mViewerAssetUrl.empty() && gAgent.getRegion())
{
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index d907dca3ee..54a8b22a5a 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -79,13 +79,13 @@
#include "llfloaterfonttest.h"
#include "llfloaterforgetuser.h"
#include "llfloatergesture.h"
+#include "llfloatergestureautocompletepicker.h"
#include "llfloatergltfasseteditor.h"
#include "llfloatergodtools.h"
#include "llfloatergridstatus.h"
#include "llfloatergroups.h"
#include "llfloaterhelpbrowser.h"
#include "llfloaterhoverheight.h"
-#include "llfloaterhowto.h"
#include "llfloaterhud.h"
#include "llfloaterimagepreview.h"
#include "llfloaterimsession.h"
@@ -384,6 +384,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("font_test", "floater_font_test.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFontTest>);
LLFloaterReg::add("forget_username", "floater_forget_user.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterForgetUser>);
+ LLFloaterReg::add("gesture_autocomplete_picker", "floater_gesture_autocomplete_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGestureAutocompletePicker>);
LLFloaterReg::add("gestures", "floater_gesture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGesture>);
LLFloaterReg::add("gltf_asset_editor", "floater_gltf_asset_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGLTFAssetEditor>);
LLFloaterReg::add("god_tools", "floater_god_tools.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGodTools>);
@@ -505,7 +506,6 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("search", "floater_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSearch>);
LLFloaterReg::add("legacy_search", "floater_directory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterDirectory>);
LLFloaterReg::add("profile", "floater_profile.xml",(LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterProfile>);
- LLFloaterReg::add("guidebook", "floater_how_to.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterHowTo>);
LLFloaterReg::add("slapp_test", "floater_test_slapp.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSLappTest>);
LLFloaterUIPreviewUtil::registerFloater();
diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp
index be43c9c785..200fd619b1 100644
--- a/indra/newview/llviewerinventory.cpp
+++ b/indra/newview/llviewerinventory.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2014, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -684,6 +684,25 @@ void LLViewerInventoryCategory::setVersion(S32 version)
mVersion = version;
}
+const std::string& LLViewerInventoryCategory::getDisplayName() const
+{
+ if (mNeedsDisplayNameUpdate)
+ {
+ buildDisplayName();
+ }
+ if (!mDisplayName.empty())
+ {
+ return mDisplayName;
+ }
+ return getName();
+}
+
+void LLViewerInventoryCategory::invalidateDisplayName()
+{
+ mNeedsDisplayNameUpdate = true;
+ mDisplayName.clear();
+}
+
bool LLViewerInventoryCategory::fetch(S32 expiry_seconds)
{
if((VERSION_UNKNOWN == getVersion())
@@ -900,6 +919,34 @@ void LLViewerInventoryCategory::localizeName()
LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName);
}
+void LLViewerInventoryCategory::buildDisplayName() const
+{
+ // Secure and library folders can't be renamed,
+ // so we only need to do this once.
+ mNeedsDisplayNameUpdate = false;
+
+ //"Accessories" inventory category has folder type FT_NONE. So, this folder
+ //can not be detected as protected with LLFolderType::lookupIsProtectedType
+ //
+ // HACK: EXT - 6028 ([HARD CODED]? Inventory > Library > "Accessories" folder)
+ // Translation of Accessories folder in Library inventory folder
+ LLFolderType::EType preferred_type = getPreferredType();
+
+ bool is_accessories = false;
+ if (getName() == "Accessories")
+ {
+ // To ensure that Accessories folder is in Library we have to check its parent folder.
+ const LLUUID& parent_folder_id = getParentUUID();
+ is_accessories = (parent_folder_id == gInventory.getLibraryRootFolderID());
+ }
+
+ if (is_accessories || LLFolderType::lookupIsProtectedType(preferred_type))
+ {
+ // All predefined folders have translations in strings.xml.
+ LLTrans::findString(mDisplayName, std::string("InvFolder ") + getName(), LLSD());
+ }
+}
+
// virtual
bool LLViewerInventoryCategory::unpackMessage(const LLSD& category)
{
diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h
index ff1349d908..6e2e953106 100644
--- a/indra/newview/llviewerinventory.h
+++ b/indra/newview/llviewerinventory.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -219,6 +219,13 @@ public:
S32 getVersion() const;
void setVersion(S32 version);
+ const std::string& getDisplayName() const;
+ // The display name gets cached on demand, so needs a cleanup method.
+ // But in practice only secure folders' display name mismatches
+ // actual name, and those folders can't be renamed, so in practice
+ // this is useless unless we want to free memory.
+ void invalidateDisplayName();
+
// Returns true if a fetch was issued (not nessesary in progress).
// no requests will happen during expiry_seconds even if fetch completed
bool fetch(S32 expiry_seconds = 10);
@@ -263,6 +270,19 @@ protected:
S32 mDescendentCount;
EFetchType mFetching;
LLFrameTimer mDescendentsRequested;
+
+ // Display names are generated on demand and cached.
+ // buildDisplayName is essentially a way to localize
+ // system and library folders.
+ //
+ // TODO: This is on demand and mutable because that's how it
+ // worked in inventory bridge, before it was moved.
+ // But system folders always get loaded, it's likely better
+ // to just generate from the get go.
+ // Consider merging with localizeName.
+ void buildDisplayName() const;
+ mutable std::string mDisplayName;
+ mutable bool mNeedsDisplayNameUpdate = true;
};
class LLInventoryCallback : public LLRefCount
diff --git a/indra/newview/llviewerjoint.cpp b/indra/newview/llviewerjoint.cpp
index 3c5c4752df..125465f326 100644
--- a/indra/newview/llviewerjoint.cpp
+++ b/indra/newview/llviewerjoint.cpp
@@ -145,6 +145,7 @@ U32 LLViewerJoint::render( F32 pixelArea, bool first_pass, bool is_dummy )
{
// LLViewerJoint is derived from LLAvatarJoint,
// all children of LLAvatarJoint are assumed to be LLAvatarJoint
+ // LLJoint is not used directly and is always LLAvatarJoint
LLAvatarJoint* joint = static_cast<LLAvatarJoint*>(j);
F32 jointLOD = joint->getLOD();
if (pixelArea >= jointLOD || sDisableLOD)
diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp
index 7870fe7823..a910d8ec2a 100644
--- a/indra/newview/llviewerjointmesh.cpp
+++ b/indra/newview/llviewerjointmesh.cpp
@@ -89,10 +89,9 @@ const S32 NUM_AXES = 3;
// rotation Z 0-n
// pivot parent 0-n -- child = n+1
-static LLMatrix4 gJointMatUnaligned[32];
-static LLMatrix4a gJointMatAligned[32];
-static LLMatrix3 gJointRotUnaligned[32];
-static LLVector4 gJointPivot[32];
+static LLMatrix4 gJointMatUnaligned[LL_CHARACTER_MAX_JOINTS_PER_MESH];
+static LLMatrix3 gJointRotUnaligned[LL_CHARACTER_MAX_JOINTS_PER_MESH];
+static LLVector4 gJointPivot[LL_CHARACTER_MAX_JOINTS_PER_MESH];
//-----------------------------------------------------------------------------
// uploadJointMatrices()
@@ -101,18 +100,14 @@ void LLViewerJointMesh::uploadJointMatrices()
{
S32 joint_num;
LLPolyMesh *reference_mesh = mMesh->getReferenceMesh();
- LLDrawPool *poolp = mFace ? mFace->getPool() : NULL;
- bool hardware_skinning = (poolp && poolp->getShaderLevel() > 0);
//calculate joint matrices
- for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++)
+ size_t num_joints = llmin(reference_mesh->mJointRenderData.size(), LL_CHARACTER_MAX_JOINTS_PER_MESH);
+ for (joint_num = 0; joint_num < num_joints; joint_num++)
{
LLMatrix4 joint_mat = *reference_mesh->mJointRenderData[joint_num]->mWorldMatrix;
- if (hardware_skinning)
- {
- joint_mat *= LLDrawPoolAvatar::getModelView();
- }
+ joint_mat *= LLDrawPoolAvatar::getModelView();
gJointMatUnaligned[joint_num] = joint_mat;
gJointRotUnaligned[joint_num] = joint_mat.getMat3();
}
@@ -131,12 +126,20 @@ void LLViewerJointMesh::uploadJointMatrices()
LLVector4 parent_pivot(sj->mRootToParentJointSkinOffset);
parent_pivot.mV[VW] = 0.f;
gJointPivot[j++] = parent_pivot;
+ if (j >= LL_CHARACTER_MAX_JOINTS_PER_MESH)
+ {
+ break;
+ }
}
LLVector4 child_pivot(sj->mRootToJointSkinOffset);
child_pivot.mV[VW] = 0.f;
gJointPivot[j++] = child_pivot;
+ if (j >= LL_CHARACTER_MAX_JOINTS_PER_MESH)
+ {
+ break;
+ }
last_pivot_uploaded = true;
}
@@ -156,12 +159,10 @@ void LLViewerJointMesh::uploadJointMatrices()
}
// upload matrices
- if (hardware_skinning)
- {
GLfloat mat[45*4];
memset(mat, 0, sizeof(GLfloat)*45*4);
- for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++)
+ for (joint_num = 0; joint_num < num_joints; joint_num++)
{
gJointMatUnaligned[joint_num].transpose();
@@ -178,15 +179,6 @@ void LLViewerJointMesh::uploadJointMatrices()
LLGLSLShader::sCurBoundShaderPtr->uniform4fv(LLViewerShaderMgr::AVATAR_MATRIX, 45, mat);
}
stop_glerror();
- }
- else
- {
- //load gJointMatUnaligned into gJointMatAligned
- for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); ++joint_num)
- {
- gJointMatAligned[joint_num].loadu(gJointMatUnaligned[joint_num]);
- }
- }
}
//--------------------------------------------------------------------
@@ -215,6 +207,7 @@ U32 LLViewerJointMesh::drawShape( F32 pixelArea, bool first_pass, bool is_dummy)
if (!mValid || !mMesh || !mFace || !mVisible ||
!mFace->getVertexBuffer() ||
mMesh->getNumFaces() == 0 ||
+ mMesh->mFaceVertexCount == 0 ||
LLGLSLShader::sCurBoundShaderPtr == NULL)
{
return 0;
@@ -284,12 +277,9 @@ U32 LLViewerJointMesh::drawShape( F32 pixelArea, bool first_pass, bool is_dummy)
if (mMesh->hasWeights())
{
- if ((mFace->getPool()->getShaderLevel() > 0))
+ if (first_pass)
{
- if (first_pass)
- {
- uploadJointMatrices();
- }
+ uploadJointMatrices();
}
buff->setBuffer();
@@ -349,15 +339,6 @@ void LLViewerJointMesh::updateFaceData(LLFace *face, F32 pixel_area, bool damp_w
return;
}
- LLDrawPool *poolp = mFace->getPool();
- bool hardware_skinning = (poolp && poolp->getShaderLevel() > 0);
-
- if (!hardware_skinning && terse_update)
- { //no need to do terse updates if we're doing software vertex skinning
- // since mMesh is being copied into mVertexBuffer every frame
- return;
- }
-
LL_PROFILE_ZONE_SCOPED;
LLStrider<LLVector3> verticesp;
@@ -441,75 +422,8 @@ bool LLViewerJointMesh::updateLOD(F32 pixel_area, bool activate)
return (valid != activate);
}
-// static
-void LLViewerJointMesh::updateGeometry(LLFace *mFace, LLPolyMesh *mMesh)
-{
- LLStrider<LLVector3> o_vertices;
- LLStrider<LLVector3> o_normals;
-
- //get vertex and normal striders
- LLVertexBuffer* buffer = mFace->getVertexBuffer();
- buffer->getVertexStrider(o_vertices, 0);
- buffer->getNormalStrider(o_normals, 0);
-
- F32* __restrict vert = o_vertices[0].mV;
- F32* __restrict norm = o_normals[0].mV;
-
- const F32* __restrict weights = mMesh->getWeights();
- const LLVector4a* __restrict coords = (LLVector4a*) mMesh->getCoords();
- const LLVector4a* __restrict normals = (LLVector4a*) mMesh->getNormals();
-
- U32 offset = mMesh->mFaceVertexOffset*4;
- vert += offset;
- norm += offset;
-
- for (U32 index = 0; index < mMesh->getNumVertices(); index++)
- {
- // equivalent to joint = floorf(weights[index]);
- S32 joint = _mm_cvtt_ss2si(_mm_load_ss(weights+index));
- F32 w = weights[index] - joint;
-
- LLMatrix4a gBlendMat;
-
- if (w != 0.f)
- {
- // blend between matrices and apply
- gBlendMat.setLerp(gJointMatAligned[joint+0],
- gJointMatAligned[joint+1], w);
-
- LLVector4a res;
- gBlendMat.affineTransform(coords[index], res);
- res.store4a(vert+index*4);
- gBlendMat.rotate(normals[index], res);
- res.store4a(norm+index*4);
- }
- else
- { // No lerp required in this case.
- LLVector4a res;
- gJointMatAligned[joint].affineTransform(coords[index], res);
- res.store4a(vert+index*4);
- gJointMatAligned[joint].rotate(normals[index], res);
- res.store4a(norm+index*4);
- }
- }
-
- buffer->unmapBuffer();
-}
-
void LLViewerJointMesh::updateJointGeometry()
{
- if (!(mValid
- && mMesh
- && mFace
- && mMesh->hasWeights()
- && mFace->getVertexBuffer()
- && LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) == 0))
- {
- return;
- }
-
- uploadJointMatrices();
- updateGeometry(mFace, mMesh);
}
void LLViewerJointMesh::dump()
diff --git a/indra/newview/llviewerjointmesh.h b/indra/newview/llviewerjointmesh.h
index a3afd84d06..ec410a3138 100644
--- a/indra/newview/llviewerjointmesh.h
+++ b/indra/newview/llviewerjointmesh.h
@@ -68,11 +68,6 @@ public:
void dump() override;
bool isAnimatable() const override { return false; }
-
-private:
-
- //copy mesh into given face's vertex buffer, applying current animation pose
- static void updateGeometry(LLFace* face, LLPolyMesh* mesh);
};
#endif // LL_LLVIEWERJOINTMESH_H
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 0590abc42e..5d01e08875 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -560,55 +560,34 @@ LLViewerMedia::impl_list &LLViewerMedia::getPriorityList()
// This is the predicate function used to sort sViewerMediaImplList by priority.
bool LLViewerMedia::priorityComparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2)
{
- if(i1->isForcedUnloaded() && !i2->isForcedUnloaded())
+ // isForcedUnloaded can be pricey, avoid a repeat,
+ // note that this one is specifically i2, when everything else is i1
+ // Consider making isForcedUnloaded cache the value temporarily?
+ bool i2_forced_unloaded = i2->isForcedUnloaded();
+ if (i1->isForcedUnloaded() != i2_forced_unloaded)
{
// Muted or failed items always go to the end of the list, period.
- return false;
- }
- else if(i2->isForcedUnloaded() && !i1->isForcedUnloaded())
- {
- // Muted or failed items always go to the end of the list, period.
- return true;
- }
- else if(i1->hasFocus())
- {
- // The item with user focus always comes to the front of the list, period.
- return true;
+ return i2_forced_unloaded;
}
- else if(i2->hasFocus())
+ else if(i1->hasFocus() != i2->hasFocus())
{
// The item with user focus always comes to the front of the list, period.
- return false;
- }
- else if(i1->isParcelMedia())
- {
- // The parcel media impl sorts above all other inworld media, unless one has focus.
- return true;
+ return i1->hasFocus();
}
- else if(i2->isParcelMedia())
+ else if(i1->isParcelMedia() != i2->isParcelMedia())
{
// The parcel media impl sorts above all other inworld media, unless one has focus.
- return false;
- }
- else if(i1->getUsedInUI() && !i2->getUsedInUI())
- {
- // i1 is a UI element, i2 is not. This makes i1 "less than" i2, so it sorts earlier in our list.
- return true;
+ return i1->isParcelMedia();
}
- else if(i2->getUsedInUI() && !i1->getUsedInUI())
+ else if (i1->getUsedInUI() != i2->getUsedInUI())
{
- // i2 is a UI element, i1 is not. This makes i2 "less than" i1, so it sorts earlier in our list.
- return false;
- }
- else if(i1->isPlayable() && !i2->isPlayable())
- {
- // Playable items sort above ones that wouldn't play even if they got high enough priority
- return true;
+ // UI elements sort above inworld media.
+ return i1->getUsedInUI();
}
- else if(!i1->isPlayable() && i2->isPlayable())
+ else if (i1->isPlayable() != i2->isPlayable())
{
// Playable items sort above ones that wouldn't play even if they got high enough priority
- return false;
+ return i1->isPlayable();
}
else if(i1->getInterest() == i2->getInterest())
{
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index eaaa923b59..7131d84a4e 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -2299,7 +2299,7 @@ class LLAdvancedDropPacket : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
- gMessageSystem->mPacketRing.dropPackets(1);
+ gMessageSystem->dropPackets(1);
return true;
}
};
@@ -3712,7 +3712,7 @@ class LLAvatarSetImpostorMode : public view_listener_t
return false;
}
- LLVOAvatar::cullAvatarsByPixelArea();
+ LLVOAvatar::setCullNeedsUpdate();
return true;
} // handleEvent()
};
@@ -5208,6 +5208,27 @@ void handle_link_objects()
}
}
+void handle_unlink_objects()
+{
+ if (LLSelectMgr::getInstance()->getSelection()->isEmpty())
+ {
+ LLPanel* visited_panel = LLFloaterSidePanelContainer::getPanel("places", "Teleport History");
+ if (visited_panel && visited_panel->isInVisibleChain())
+ {
+ LLFloaterReg::hideInstance("places");
+ }
+ else
+ {
+ LLFloaterReg::toggleInstanceOrBringToFront("places");
+ LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "open_teleport_history_tab"));
+ }
+ }
+ else
+ {
+ LLSelectMgr::getInstance()->unlinkObjects();
+ }
+}
+
// You can return an object to its owner if it is on your land.
class LLObjectReturn : public view_listener_t
{
@@ -8573,15 +8594,6 @@ class LLToolsEnableSaveToObjectInventory : public view_listener_t
}
};
-class LLToggleHowTo : public view_listener_t
-{
- bool handleEvent(const LLSD& userdata)
- {
- LLFloaterReg::toggleInstanceOrBringToFront("guidebook");
- return true;
- }
-};
-
class LLViewEnableMouselook : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
@@ -9719,6 +9731,16 @@ void handle_flush_name_caches()
if (gCacheName) gCacheName->clear();
}
+bool is_master_audio_muted()
+{
+ return LLAppViewer::instance()->getMasterSystemAudioMute();
+}
+
+void toggle_master_audio()
+{
+ LLAppViewer::instance()->setMasterSystemAudioMute(!is_master_audio_muted());
+}
+
class LLUploadCostCalculator : public view_listener_t
{
std::string mCostStr;
@@ -9990,6 +10012,8 @@ void initialize_menus()
view_listener_t::addMenu(new LLWorldEnableEnvPreset(), "World.EnableEnvPreset");
view_listener_t::addMenu(new LLWorldCheckBanLines() , "World.CheckBanLines");
view_listener_t::addMenu(new LLWorldShowBanLines() , "World.ShowBanLines");
+ commit.add("World.ToggleMasterAudio", boost::bind(&toggle_master_audio));
+ enable.add("World.IsMasterAudioMuted", boost::bind(&is_master_audio_muted));
// Tools menu
view_listener_t::addMenu(new LLToolsSelectTool(), "Tools.SelectTool");
@@ -10006,7 +10030,7 @@ void initialize_menus()
view_listener_t::addMenu(new LLToolsUseSelectionForGrid(), "Tools.UseSelectionForGrid");
view_listener_t::addMenu(new LLToolsSelectNextPartFace(), "Tools.SelectNextPart");
commit.add("Tools.Link", boost::bind(&handle_link_objects));
- commit.add("Tools.Unlink", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance()));
+ commit.add("Tools.Unlink", boost::bind(&handle_unlink_objects));
view_listener_t::addMenu(new LLToolsStopAllAnimations(), "Tools.StopAllAnimations");
view_listener_t::addMenu(new LLToolsReleaseKeys(), "Tools.ReleaseKeys");
view_listener_t::addMenu(new LLToolsEnableReleaseKeys(), "Tools.EnableReleaseKeys");
@@ -10036,11 +10060,6 @@ void initialize_menus()
view_listener_t::addMenu(new LLToolsCheckScriptEditorServer(), "Tools.CheckScriptEditorServer");
view_listener_t::addMenu(new LLToolsEnableScriptEditorServer(), "Tools.EnableScriptEditorServer");
view_listener_t::addMenu(new LLToolsToggleScriptEditorServer(), "Tools.ToggleScriptEditorServer");
-
- // Help menu
- // most items use the ShowFloater method
- view_listener_t::addMenu(new LLToggleHowTo(), "Help.ToggleHowTo");
-
// Advanced menu
view_listener_t::addMenu(new LLAdvancedToggleConsole(), "Advanced.ToggleConsole");
view_listener_t::addMenu(new LLAdvancedCheckConsole(), "Advanced.CheckConsole");
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index 423bb5f31d..ce95d7363e 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -335,7 +335,9 @@ void LLFilePickerReplyThread::startPicker(const file_picked_signal_t::slot_type
void LLFilePickerReplyThread::startPicker(const file_picked_signal_t::slot_type & cb, LLFilePicker::ESaveFilter filter, const std::string & proposed_name, const file_picked_signal_t::slot_type & failure_cb)
{
- (new LLFilePickerReplyThread(cb, filter, proposed_name, failure_cb))->getFile();
+ // Remove invalid characters
+ std::string sanitized_name = LLDir::getScrubbedFileName(proposed_name);
+ (new LLFilePickerReplyThread(cb, filter, sanitized_name, failure_cb))->getFile();
}
void LLFilePickerReplyThread::notify(const std::vector<std::string>& filenames)
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 1ba32df378..1f76914335 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -419,7 +419,26 @@ void send_complete_agent_movement(const LLHost& sim_host)
msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode);
- msg->sendReliable(sim_host);
+
+ // build a lambda to be used as callback on ACK or timeout
+ void (*complete_agent_movement_callback)(void**, S32) = [](void**, S32 result)
+ {
+ if(LLApp::isExiting()) return;
+ if (result != LL_ERR_NOERR)
+ {
+ LL_WARNS("Messaging") << "CompleteAgentMovement failed with err=" << result << LL_ENDL;
+ }
+ };
+
+ // We use same retry strategy as UseCircuitCode because this is a crucial message
+ // that MUST arrive else we'll suffer a failed login/teleport/region-cross
+ msg->sendReliable(
+ sim_host,
+ gSavedSettings.getS32("UseCircuitCodeMaxRetries"),
+ false,
+ (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"),
+ complete_agent_movement_callback,
+ NULL);
}
void process_logout_reply(LLMessageSystem* msg, void**)
@@ -4162,11 +4181,12 @@ void process_avatar_appearance(LLMessageSystem *mesgsys, void **user_data)
if (avatarp)
{
avatarp->processAvatarAppearance( mesgsys );
+ return;
}
- else
- {
- LL_WARNS("Messaging") << "avatar_appearance sent for unknown avatar " << uuid << LL_ENDL;
- }
+ // The avatar object doesn't exist yet.
+ // We will re-request its appearance data after it is created.
+ LLVOAvatar::registerEarlyAppearance(uuid);
+ LL_WARNS("Messaging") << "AvatarAppearance received for avatar " << uuid << " before object created" << LL_ENDL;
}
void process_camera_constraint(LLMessageSystem *mesgsys, void **user_data)
diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp
index 8ec2a6d661..fb52d0a8e6 100644
--- a/indra/newview/llviewernetwork.cpp
+++ b/indra/newview/llviewernetwork.cpp
@@ -135,7 +135,8 @@ void LLGridManager::initialize(const std::string& grid_file)
LLSD other_grids;
llifstream llsd_xml;
- if (!grid_file.empty())
+ // grids.xml is not supplied by default
+ if (!grid_file.empty() && LLFile::isfile(grid_file))
{
LL_INFOS("GridManager")<<"Grid configuration file '"<<grid_file<<"'"<<LL_ENDL;
llsd_xml.open( grid_file.c_str(), std::ios::in | std::ios::binary );
diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index 8432e0bdb9..b51a63a068 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -4181,6 +4181,11 @@ U32 LLViewerObject::getTriangleCount(S32* vcount) const
return 0;
}
+U32 LLViewerObject::getLODTriangleCount(S32 lod)
+{
+ return 0;
+}
+
U32 LLViewerObject::getHighLODTriangleCount()
{
return 0;
@@ -4202,6 +4207,27 @@ U32 LLViewerObject::recursiveGetTriangleCount(S32* vcount) const
return total_tris;
}
+void LLViewerObject::recursiveGetLODTriangleCount(S32& high_lod, S32& medium_lod, S32& low_lod, S32& lowest_lod)
+{
+ high_lod = (S32)getLODTriangleCount(LLModel::LOD_HIGH);
+ medium_lod = (S32)getLODTriangleCount(LLModel::LOD_MEDIUM);
+ low_lod = (S32)getLODTriangleCount(LLModel::LOD_LOW);
+ lowest_lod = (S32)getLODTriangleCount(LLModel::LOD_IMPOSTOR);
+ LLViewerObject::const_child_list_t& child_list = getChildren();
+ for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin();
+ iter != child_list.end(); ++iter)
+ {
+ LLViewerObject* childp = *iter;
+ if (childp)
+ {
+ high_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_HIGH);
+ medium_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_MEDIUM);
+ low_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_LOW);
+ lowest_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_IMPOSTOR);
+ }
+ }
+}
+
// This is using the stored surface area for each volume (which
// defaults to 1.0 for the case of everything except a sculpt) and
// then scaling it linearly based on the largest dimension in the
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index 6777753754..9d05f0ee78 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -426,10 +426,12 @@ public:
virtual F32 getStreamingCost() const;
virtual bool getCostData(LLMeshCostData& costs) const;
virtual U32 getTriangleCount(S32* vcount = NULL) const;
+ virtual U32 getLODTriangleCount(S32 lod);
virtual U32 getHighLODTriangleCount();
F32 recursiveGetScaledSurfaceArea() const;
U32 recursiveGetTriangleCount(S32* vcount = NULL) const;
+ void recursiveGetLODTriangleCount(S32 &high_lod, S32 &medium_lod, S32 &low_lod, S32 &lowest_lod);
void setObjectCost(F32 cost);
F32 getObjectCost();
diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp
index 34e5cc9de8..31b2108cf0 100644
--- a/indra/newview/llviewerobjectlist.cpp
+++ b/indra/newview/llviewerobjectlist.cpp
@@ -386,7 +386,7 @@ LLViewerObject* LLViewerObjectList::processObjectUpdateFromCache(LLVOCacheEntry*
objectp->setLastUpdateType(OUT_FULL_COMPRESSED); //newly cached
objectp->setLastUpdateCached(true);
}
- LLVOAvatar::cullAvatarsByPixelArea();
+ LLVOAvatar::setCullNeedsUpdate();
return objectp;
}
@@ -683,7 +683,7 @@ void LLViewerObjectList::processObjectUpdate(LLMessageSystem *mesgsys,
objectp->setLastUpdateType(update_type);
}
- LLVOAvatar::cullAvatarsByPixelArea();
+ LLVOAvatar::setCullNeedsUpdate();
}
void LLViewerObjectList::processCompressedObjectUpdate(LLMessageSystem *mesgsys,
@@ -1379,6 +1379,7 @@ void LLViewerObjectList::killObjects(LLViewerRegion *regionp)
void LLViewerObjectList::killAllObjects()
{
+ LL_PROFILE_ZONE_SCOPED;
// Used only on global destruction.
// Mass cleanup to not clear lists one item at a time
@@ -1401,7 +1402,10 @@ void LLViewerObjectList::killAllObjects()
gMeshRepo.unregisterAllMeshes();
- cleanDeadObjects(false);
+ // Direct clear instead of cleanDeadObjects - all objects are already dead
+ mObjects.clear();
+ mDeadObjects.clear();
+ mNumDeadObjects = 0;
if(!mObjects.empty())
{
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 0bb3edfc7e..a4298accc1 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -3207,7 +3207,26 @@ void LLViewerRegion::unpackRegionHandshake()
flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes.
}
msg->addU32("Flags", flags );
- msg->sendReliable(host);
+
+ // build a lambda to be used as callback on ACK or timeout
+ void (*region_handshake_reply_callback)(void**, S32) = [](void**, S32 result)
+ {
+ if(LLApp::isExiting()) return;
+ if (result != LL_ERR_NOERR)
+ {
+ LL_WARNS("Messaging") << "RegionHandshakeReply failed with err=" << result << LL_ENDL;
+ }
+ };
+
+ // This is a crucial message for establishing a connection to a region
+ // (either the main region or a visible neighbor).
+ msg->sendReliable(
+ host,
+ gSavedSettings.getS32("UseCircuitCodeMaxRetries"),
+ false,
+ (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"),
+ region_handshake_reply_callback,
+ NULL);
mRegionTimer.reset(); //reset region timer.
}
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index 08bedc697b..8c66046524 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -128,7 +128,6 @@ LLGLSLShader gPathfindingNoNormalsProgram;
//avatar shader handles
LLGLSLShader gAvatarProgram;
-LLGLSLShader gAvatarEyeballProgram;
LLGLSLShader gImpostorProgram;
// Effects Shaders
@@ -413,7 +412,6 @@ void LLViewerShaderMgr::finalizeShaderList()
//ONLY shaders that need WL Param management should be added here
mShaderList.push_back(&gAvatarProgram);
mShaderList.push_back(&gWaterProgram);
- mShaderList.push_back(&gAvatarEyeballProgram);
mShaderList.push_back(&gImpostorProgram);
mShaderList.push_back(&gObjectBumpProgram);
mShaderList.push_back(&gObjectFullbrightAlphaMaskProgram);
@@ -700,7 +698,7 @@ void LLViewerShaderMgr::setShaders()
if (loadShadersObject())
{ //hardware skinning is enabled and rigged attachment shaders loaded correctly
// cloth is a class3 shader
- S32 avatar_class = 1;
+ constexpr S32 avatar_class = 1;
// Set the actual level
mShaderLevel[SHADER_AVATAR] = avatar_class;
@@ -709,8 +707,11 @@ void LLViewerShaderMgr::setShaders()
llassert(loaded);
}
else
- { //hardware skinning not possible, neither is deferred rendering
- llassert(false); // SHOULD NOT BE POSSIBLE
+ {
+ // SHOULD NOT BE POSSIBLE
+ // Hardware skinning not possible, neither is deferred rendering
+ // but both are a hard requirement, viewer will crash without them
+ LL_ERRS() << "Failed to load object shaders, cannot continue." << LL_ENDL;
}
}
@@ -3108,14 +3109,6 @@ bool LLViewerShaderMgr::loadShadersAvatar()
LL_PROFILE_ZONE_SCOPED;
#if 1 // DEPRECATED -- forward rendering is deprecated
bool success = true;
-
- if (mShaderLevel[SHADER_AVATAR] == 0)
- {
- gAvatarProgram.unload();
- gAvatarEyeballProgram.unload();
- return true;
- }
-
if (success)
{
gAvatarProgram.mName = "Avatar Shader";
@@ -3139,27 +3132,13 @@ bool LLViewerShaderMgr::loadShadersAvatar()
}
}
- if (success)
- {
- gAvatarEyeballProgram.mName = "Avatar Eyeball Program";
- gAvatarEyeballProgram.mFeatures.calculatesLighting = true;
- gAvatarEyeballProgram.mFeatures.isSpecular = true;
- gAvatarEyeballProgram.mFeatures.calculatesAtmospherics = true;
- gAvatarEyeballProgram.mFeatures.hasGamma = true;
- gAvatarEyeballProgram.mFeatures.hasAtmospherics = true;
- gAvatarEyeballProgram.mFeatures.hasLighting = true;
- gAvatarEyeballProgram.mFeatures.hasAlphaMask = true;
- gAvatarEyeballProgram.mShaderFiles.clear();
- gAvatarEyeballProgram.mShaderFiles.push_back(make_pair("avatar/eyeballV.glsl", GL_VERTEX_SHADER));
- gAvatarEyeballProgram.mShaderFiles.push_back(make_pair("avatar/eyeballF.glsl", GL_FRAGMENT_SHADER));
- gAvatarEyeballProgram.mShaderLevel = mShaderLevel[SHADER_AVATAR];
- success = gAvatarEyeballProgram.createShader();
- }
-
if( !success )
{
+ // This isn't supposed to fail, otherwise defferred shaders would have failed.
+ // Is only used for avatar previews (like LLVisualParamHint)
mShaderLevel[SHADER_AVATAR] = 0;
mMaxAvatarShaderLevel = 0;
+ LL_WARNS() << "Failed to create avatar shader!" << LL_ENDL;
return false;
}
#endif
diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h
index 46da30017d..3efd23ad79 100644
--- a/indra/newview/llviewershadermgr.h
+++ b/indra/newview/llviewershadermgr.h
@@ -208,7 +208,6 @@ extern LLGLSLShader gPathfindingNoNormalsProgram;
// avatar shader handles
extern LLGLSLShader gAvatarProgram;
-extern LLGLSLShader gAvatarEyeballProgram;
extern LLGLSLShader gImpostorProgram;
// Post Process Shaders
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index aa9ec9c56f..45f7058acb 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -793,7 +793,7 @@ void send_viewer_stats(bool include_preferences)
LLSD &fail = body["stats"]["failures"];
fail["send_packet"] = (S32) gMessageSystem->mSendPacketFailureCount;
- fail["dropped"] = (S32) gMessageSystem->mDroppedPackets;
+ fail["dropped"] = (S32) gMessageSystem->getTotalNumDroppedPackets();
fail["resent"] = (S32) gMessageSystem->mResentPackets;
fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets;
fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets;
diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp
index 95e34b4df1..a58d7546ce 100644
--- a/indra/newview/llviewertexteditor.cpp
+++ b/indra/newview/llviewertexteditor.cpp
@@ -249,6 +249,25 @@ public:
/*virtual*/ bool canEdit() const { return false; }
+ /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask)
+ {
+ bool show_menu = !mEditor.hasSelection()
+ || (mEditor.mSelectionStart == mStart && mEditor.mSelectionEnd == mStart + 1);
+ if (show_menu && mEditor.getShowContextMenu())
+ {
+ // User clicked an item, user expects the menu to be
+ // in 'context' for the item. Change selection to match.
+ // Todo: Might be better to 'smartly' deselect here
+ // and to have an object specific menu.
+ mEditor.setCursorPos(mStart + 1);
+ mEditor.mSelectionStart = mStart;
+ mEditor.mSelectionEnd = mStart + 1;
+
+ mEditor.showContextMenu(x, y);
+ return true;
+ }
+ return false;
+ }
/*virtual*/ bool handleHover(S32 x, S32 y, MASK mask)
{
diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp
index e4fd947892..4d64f897b1 100644
--- a/indra/newview/llviewertexturelist.cpp
+++ b/indra/newview/llviewertexturelist.cpp
@@ -1609,6 +1609,7 @@ void LLViewerTextureList::processImageNotInDatabase(LLMessageSystem *msg,void **
// guaranteed.
void LLUIImageList::cleanUp()
{
+ LLUIImage::cleanupClass();
mUIImages.clear();
mUITextureList.clear() ;
}
diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp
index 3ccfbea6e2..90a60c7a94 100644
--- a/indra/newview/llviewerthrottle.cpp
+++ b/indra/newview/llviewerthrottle.cpp
@@ -45,8 +45,8 @@ using namespace LLOldEvents;
const F32 MAX_FRACTIONAL = 1.5f;
const F32 MIN_FRACTIONAL = 0.2f;
-const F32 MIN_BANDWIDTH = 50.f;
-const F32 MAX_BANDWIDTH = 6000.f;
+const F32 MIN_BANDWIDTH = 50.f; // Kbps
+const F32 MAX_BANDWIDTH = 6000.f; // Kbps
const F32 STEP_FRACTIONAL = 0.1f;
const F32 HIGH_BUFFER_LOAD_TRESHOLD = 1.f;
const F32 LOW_BUFFER_LOAD_TRESHOLD = 0.8f;
@@ -244,6 +244,8 @@ void LLViewerThrottle::sendToSim() const
F32 LLViewerThrottle::getMaxBandwidthKbps()
{
+ // Why are thse different than the constants with the same names higher up?
+ // Anybody know? -- Leviathan
constexpr F32 MIN_BANDWIDTH = 100.0f; // 100 Kbps
constexpr F32 MAX_BANDWIDTH = 10000.0f; // 10 Mbps
@@ -309,6 +311,21 @@ void LLViewerThrottle::resetDynamicThrottle()
void LLViewerThrottle::updateDynamicThrottle()
{
+ // User configurable bandwidth settings are merely vague aspirations. Translating those
+ // to what should be sent to the servers is complicated. Servers will tend to spike data
+ // transmission upon arrival, packets will arrive faster than we can process them, and this
+ // can overflow the buffer, which causes packet loss.
+ //
+ // In an attempt to avoid catastrophe we periodically measure buffer load and packet loss
+ // and then transmit a modified desired bandwidth to the server. Unfortunately, this system
+ // does not work well for spikey data bursts because:
+ // (1) the response takes too long (up to 5 seconds) to kick in
+ // (2) once it starts it tends to ramp up too slowly
+ // (3) it doesn't know which region is providing the flood; it just assumes it is
+ // all from the main region
+ //
+ // TODO: fix those ^^^ problems
+
if (mUpdateTimer.getElapsedTimeF32() < DYNAMIC_UPDATE_DURATION)
{
return;
@@ -347,5 +364,7 @@ void LLViewerThrottle::updateDynamicThrottle()
LL_INFOS() << "Easing network throttle to " << mCurrentBandwidth << LL_ENDL;
}
+ // Now that we've used mBufferLoadRate we reset it to zero because otherwise it only
+ // increases (see clamping behavior in setBufferLoadRate()).
mBufferLoadRate = 0;
}
diff --git a/indra/newview/llviewerthrottle.h b/indra/newview/llviewerthrottle.h
index ef898a97d7..7ff0a7d1f0 100644
--- a/indra/newview/llviewerthrottle.h
+++ b/indra/newview/llviewerthrottle.h
@@ -77,8 +77,8 @@ public:
static const std::string sNames[TC_EOF];
protected:
- F32 mMaxBandwidth;
- F32 mCurrentBandwidth;
+ F32 mMaxBandwidth; // bps
+ F32 mCurrentBandwidth; // bps
F32 mBufferLoadRate = 0;
LLViewerThrottleGroup mCurrent;
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index f02136789b..8730b14614 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -36,6 +36,7 @@
#include "llagent.h"
#include "llagentcamera.h"
+#include "llcallbacklist.h"
#include "llcommandhandler.h"
#include "llcommunicationchannel.h"
#include "llfloaterreg.h"
@@ -1464,12 +1465,68 @@ void LLViewerWindow::handleMouseLeave(LLWindow *window)
LLToolTipMgr::instance().blockToolTips();
}
+void LLViewerWindow::handlePreCloseRequest()
+{
+ // WINDOW THREAD! since we need this to act fast.
+ if (!LLApp::isExiting() && !LLApp::isStopped())
+ {
+ LLAppViewer::instance()->createCloseRequestMarker();
+ }
+}
+
+void LLViewerWindow::handleCloseRequestCanceled()
+{
+ // WINDOW THREAD! since we need this to act fast.
+ if (!LLApp::isExiting() && !LLApp::isStopped())
+ {
+ LLAppViewer::instance()->removeCloseRequestMarker();
+ }
+}
+
+void LLViewerWindow::handleSuspendRequest()
+{
+ static LLCachedControl<S32> os_hibernation_mode(gSavedSettings, "OSHibernationMode", 0);
+ if (os_hibernation_mode == 0)
+ {
+ LL_INFOS() << "Got a 'suspend' event from OS" << LL_ENDL;
+ // Viewer doesn't handle hibernation.
+ // Just send statistics.
+ LLAppViewer::instance()->sendViewerStatistics(false);
+ }
+ else
+ {
+ LL_INFOS() << "Got a 'suspend' event from OS, disconnecting" << LL_ENDL;
+ // Viewer is set to prevent hibernation if agent isn't away.
+ // If we got here, likely Agent 'went' away then viewer got
+ // a hibernation message.
+ // We have a limited timeframe. Sends stats then disconnect.
+ LLViewerRegion* region = gAgent.getRegion();
+ if (region)
+ {
+ LLAppViewer::instance()->sendViewerStatistics(true);
+ LLAppViewer::instance()->metricsSend(!gDisconnected);
+ // Make sure to show a message.
+ LLAppViewer::instance()->forceDisconnect(LLTrans::getString("YouHaveBeenDisconnected"));
+ }
+ }
+}
+
bool LLViewerWindow::handleCloseRequest(LLWindow *window, bool from_user)
{
if (!LLApp::isExiting() && !LLApp::isStopped())
{
if (from_user)
{
+ // Task naamger kills viewer after 1 second, 3 seconds
+ // is overkill, but decided to be on a safe side.
+ doAfterInterval([]()
+ {
+ // if user quits, marker will be cleaned by cleanup,
+ // if user cancels quit, marker will be cleaned here,
+ // but if task manager kills us, marker stays.
+ LLAppViewer::instance()->removeCloseRequestMarker();
+ }, 3.0f);
+
// User has indicated they want to close, but we may need to ask
// about modified documents.
LLAppViewer::instance()->userQuit();
@@ -1490,6 +1547,9 @@ bool LLViewerWindow::handleSessionExit(LLWindow* window)
{
// Viewer received WM_ENDSESSION and app will be killed soon if it doesn't respond
LLAppViewer* app = LLAppViewer::instance();
+ // Normally we'd include preferences, but serializing them can be expensive.
+ // There is also a chance this won't be processed if the logout request arrives first.
+ app->sendViewerStatistics(false /*no preferences*/);
app->sendSimpleLogoutRequest();
app->earlyExitNoNotify();
@@ -1774,7 +1834,7 @@ bool LLViewerWindow::handleTimerEvent(LLWindow *window)
return false;
}
-bool LLViewerWindow::handleDeviceChange(LLWindow *window)
+bool LLViewerWindow::handleDeviceChange(LLWindow *window, const std::string& change_type)
{
// give a chance to use a joystick after startup (hot-plugging)
if (!LLViewerJoystick::getInstance()->isJoystickInitialized() )
@@ -1782,6 +1842,10 @@ bool LLViewerWindow::handleDeviceChange(LLWindow *window)
LLViewerJoystick::getInstance()->init(true);
return true;
}
+ else
+ {
+ LL_INFOS("Window") << "Device change event: " << change_type << LL_ENDL;
+ }
return false;
}
@@ -1803,6 +1867,7 @@ bool LLViewerWindow::handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32
bool LLViewerWindow::handleDisplayChanged()
{
+ LL_INFOS("Window") << "Display change event" << LL_ENDL;
LLFontGL::sResolutionGeneration++;
return false;
}
diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h
index 882d5ae908..06d49df56d 100644
--- a/indra/newview/llviewerwindow.h
+++ b/indra/newview/llviewerwindow.h
@@ -203,6 +203,9 @@ public:
/*virtual*/ bool handleUnicodeChar(llwchar uni_char, MASK mask); // NOT going to handle extended
/*virtual*/ bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask);
/*virtual*/ bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask);
+ /*virtual*/ void handlePreCloseRequest();
+ /*virtual*/ void handleCloseRequestCanceled();
+ /*virtual*/ void handleSuspendRequest();
/*virtual*/ bool handleCloseRequest(LLWindow *window, bool from_user);
/*virtual*/ bool handleSessionExit(LLWindow* window);
/*virtual*/ void handleQuit(LLWindow *window);
@@ -231,7 +234,7 @@ public:
/*virtual*/ void handleWindowUnblock(LLWindow *window);
/*virtual*/ void handleDataCopy(LLWindow *window, S32 data_type, void *data);
/*virtual*/ bool handleTimerEvent(LLWindow *window);
- /*virtual*/ bool handleDeviceChange(LLWindow *window);
+ /*virtual*/ bool handleDeviceChange(LLWindow *window, const std::string& change_type);
/*virtual*/ bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height);
/*virtual*/ bool handleDisplayChanged();
/*virtual*/ bool handleWindowDidChangeScreen(LLWindow *window);
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 5bffd620d9..85d32e19cb 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -600,6 +600,8 @@ bool LLVOAvatar::sLimitNonImpostors = false; // True unless RenderAvatarMaxNonIm
F32 LLVOAvatar::sRenderDistance = 256.f;
S32 LLVOAvatar::sNumVisibleAvatars = 0;
S32 LLVOAvatar::sNumLODChangesThisFrame = 0;
+bool LLVOAvatar::sAvatarCullNeedsUpdate = true;
+F64 LLVOAvatar::sLastCullUpdateTime = 0.0;
const LLUUID LLVOAvatar::sStepSoundOnLand("e8af4a28-aa83-4310-a7c4-c047e15ea0df");
const LLUUID LLVOAvatar::sStepSounds[LL_MCODE_END] =
@@ -613,6 +615,8 @@ const LLUUID LLVOAvatar::sStepSounds[LL_MCODE_END] =
SND_RUBBER_RUBBER
};
+uuid_list_t LLVOAvatar::sEarlyAppearanceList;
+
S32 LLVOAvatar::sRenderName = RENDER_NAME_ALWAYS;
S32 LLVOAvatar::sRenderGroupTitles = RENDER_GROUP_TITLE_ALWAYS;
S32 LLVOAvatar::sNumVisibleChatBubbles = 0;
@@ -776,6 +780,20 @@ LLVOAvatar::LLVOAvatar(const LLUUID& id,
mVisuallyMuteSetting = LLVOAvatar::VisualMuteSettings(LLRenderMuteList::getInstance()->getSavedVisualMuteSetting(getID()));
sInstances.push_back(this);
+
+ uuid_list_t::iterator it = sEarlyAppearanceList.find(id);
+ if (it != sEarlyAppearanceList.end())
+ {
+ // Note: aside from LLVOAvatar::resetEarlyAppearanceList() (called on
+ // teleport), this is the only place where we remove from
+ // sEarlyAppearanceList, which means any agent who receives an
+ // AvatarAppearance message but is never actually instantiated will
+ // remain on the list until the next teleport. This is a resource leak
+ // but we expect it to be small enough per-session to not cause problems.
+ sEarlyAppearanceList.erase(it);
+ LL_INFOS("Avatar") << "Re-requesting AvatarAppearance for new avatar " << id << LL_ENDL;
+ LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID());
+ }
}
std::string LLVOAvatar::avString() const
@@ -2564,6 +2582,10 @@ void LLVOAvatar::updateMeshData()
f_num++ ;
}
}
+
+ mDirtyMesh = 0;
+ mNeedsSkin = true;
+ mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY);
}
}
@@ -2766,6 +2788,28 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time)
// force immediate pixel area update on avatars using last frames data (before drawable or camera updates)
setPixelAreaAndAngle(gAgent);
+ if (!isSelf())
+ {
+ F32 current_pixel_area = getPixelArea();
+ if (mLastCulledPixelArea >= 0.f)
+ {
+ // Avoid rapidly switching two avatars back and forth between ranks.
+ // And update frequency reduction
+ F32 pixel_area_change = fabsf(current_pixel_area - mLastCulledPixelArea) / mLastCulledPixelArea;
+ if (pixel_area_change > 0.1f) // 10% threshold
+ {
+ sAvatarCullNeedsUpdate = true;
+ mLastCulledPixelArea = current_pixel_area;
+ }
+ }
+ else
+ {
+ // First frame
+ sAvatarCullNeedsUpdate = true;
+ mLastCulledPixelArea = current_pixel_area;
+ }
+ }
+
// force asynchronous drawable update
if(mDrawable.notNull())
{
@@ -5131,9 +5175,11 @@ void LLVOAvatar::updateVisibility()
LL_DEBUGS("AvatarRender") << "visible was " << mVisible << " now " << visible << LL_ENDL;
}
+ if (mVisible != visible)
+ {
+ setCullNeedsUpdate();
+ }
mVisible = visible;
-
- mVisibilityPreference = visible ? getPixelArea() : 0;
}
// private
@@ -5174,9 +5220,6 @@ U32 LLVOAvatar::renderSkinned()
if (needs_rebuild || mDirtyMesh >= 2 || mVisibilityRank <= 4)
{
updateMeshData();
- mDirtyMesh = 0;
- mNeedsSkin = true;
- mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY);
}
}
@@ -9915,7 +9958,7 @@ void LLVOAvatar::applyParsedAppearanceMessage(LLAppearanceMessageContents& conte
if (visualParamWeightsAreDefault() && mRuthTimer.getElapsedTimeF32() > LOADING_TIMEOUT_SECONDS)
{
// re-request appearance, hoping that it comes back with a shape next time
- LL_INFOS() << "Re-requesting AvatarAppearance for object: " << getID() << LL_ENDL;
+ LL_INFOS() << "Re-requesting AvatarAppearance for agent: " << getID() << LL_ENDL;
LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID());
mRuthTimer.reset();
}
@@ -9945,7 +9988,7 @@ void LLVOAvatar::applyParsedAppearanceMessage(LLAppearanceMessageContents& conte
setCompositeUpdatesEnabled( true );
// If all of the avatars are completely baked, release the global image caches to conserve memory.
- cullAvatarsByPixelArea();
+ setCullNeedsUpdate();
if (isSelf())
{
@@ -10609,39 +10652,71 @@ S32 LLVOAvatar::getUnbakedPixelAreaRank()
return 0;
}
-// static
+// static, gets called once per frame from updateApparentAngles.
void LLVOAvatar::cullAvatarsByPixelArea()
{
- LLCharacter::sInstances.sort([](LLCharacter* lhs, LLCharacter* rhs)
- {
- return ((LLVOAvatar*)lhs)->mVisibilityPreference > ((LLVOAvatar*)rhs)->mVisibilityPreference;
- });
+ F64 current_time = LLFrameTimer::getElapsedSeconds();
+ bool needs_resort = sAvatarCullNeedsUpdate || ((current_time - sLastCullUpdateTime) >= 1.0);
- // Update the avatars that have changed status
- U32 rank = 2; // Rank 1 is reserved for self.
- for (LLCharacter* character : LLCharacter::sInstances)
+ if (needs_resort)
{
- LLVOAvatar* inst = (LLVOAvatar*)character;
- bool culled = !inst->isSelf() && !inst->isFullyBaked();
-
- if (inst->mCulled != culled)
+ LLCharacter::sInstances.sort([](LLCharacter* lhs, LLCharacter* rhs)
{
- inst->mCulled = culled;
- LL_DEBUGS() << "avatar " << inst->getID() << (culled ? " start culled" : " start not culled" ) << LL_ENDL;
- inst->updateMeshTextures();
- }
+ LLVOAvatar* lhs_av = (LLVOAvatar*)lhs;
+ LLVOAvatar* rhs_av = (LLVOAvatar*)rhs;
+ if (lhs_av->mVisible != rhs_av->mVisible)
+ {
+ return lhs_av->mVisible;
+ }
+ // Sort by pixel area in descending order (larger pixel area = higher priority)
+ return lhs_av->getPixelArea() > rhs_av->getPixelArea();
+ });
- if (inst->isSelf())
- {
- inst->setVisibilityRank(1);
- }
- else if (inst->mDrawable.notNull() && inst->mDrawable->isVisible())
+ // Update the avatars that have changed status
+ U32 rank = 2; // Rank 1 is reserved for self.
+ for (LLCharacter* character : LLCharacter::sInstances)
{
- inst->setVisibilityRank(rank++);
+ LLVOAvatar* inst = (LLVOAvatar*)character;
+ bool culled = !inst->isSelf() && !inst->isFullyBaked();
+
+ if (inst->mCulled != culled)
+ {
+ inst->mCulled = culled;
+ LL_DEBUGS() << "avatar " << inst->getID() << (culled ? " start culled" : " start not culled" ) << LL_ENDL;
+ inst->updateMeshTextures();
+ }
+
+ if (inst->isSelf())
+ {
+ inst->setVisibilityRank(1);
+ }
+ else if (inst->mDrawable.notNull() && inst->mDrawable->isVisible())
+ {
+ inst->setVisibilityRank(rank++);
+ }
+ else
+ {
+ inst->setVisibilityRank(sMaxNonImpostors * 5);
+ }
+ inst->mLastCulledPixelArea = inst->getPixelArea();
}
- else
+ sAvatarCullNeedsUpdate = false;
+ sLastCullUpdateTime = current_time;
+ }
+ else
+ {
+ for (LLCharacter* character : LLCharacter::sInstances)
{
- inst->setVisibilityRank(sMaxNonImpostors * 5);
+ // Todo: this can be optimized by tracking baked's callbacks
+ LLVOAvatar* inst = (LLVOAvatar*)character;
+ bool culled = !inst->isSelf() && !inst->isFullyBaked();
+
+ if (inst->mCulled != culled)
+ {
+ inst->mCulled = culled;
+ LL_DEBUGS() << "avatar " << inst->getID() << (culled ? " start culled" : " start not culled") << LL_ENDL;
+ inst->updateMeshTextures();
+ }
}
}
@@ -10737,9 +10812,6 @@ bool LLVOAvatar::updateLOD()
if (mDirtyMesh >= 2 || mDrawable->isState(LLDrawable::REBUILD_GEOMETRY))
{ //LOD changed or new mesh created, allocate new vertex buffer if needed
updateMeshData();
- mDirtyMesh = 0;
- mNeedsSkin = true;
- mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY);
}
updateVisibility();
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index e3e793c35a..37a7be7efb 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -95,6 +95,7 @@ class LLVOAvatar :
public:
friend class LLVOAvatarSelf;
friend class LLAvatarCheckImpostorMode;
+ friend class LLVisualParamHint;
/********************************************************************************
** **
@@ -697,7 +698,6 @@ public:
protected:
void updateVisibility();
private:
- F32 mVisibilityPreference;
U32 mVisibilityRank;
bool mVisible;
@@ -763,10 +763,13 @@ private:
// Culling
//--------------------------------------------------------------------
public:
+ static void setCullNeedsUpdate() { sAvatarCullNeedsUpdate = true; }
static void cullAvatarsByPixelArea();
bool isCulled() const { return mCulled; }
private:
bool mCulled;
+ static bool sAvatarCullNeedsUpdate;
+ static F64 sLastCullUpdateTime; // Time of last cull update
//--------------------------------------------------------------------
// Constants
@@ -945,9 +948,26 @@ protected:
** APPEARANCE
**/
+public:
+ // Used when an AvatarAppearance UDP message is received before the
+ // corresponding avatar could be created.
+ static void registerEarlyAppearance(const LLUUID& av_id)
+ {
+ sEarlyAppearanceList.emplace(av_id);
+ }
+
+ // Entries left behind by agents who never get instantiated (e.g. an
+ // AvatarAppearance message arrives for an avatar we never rez) are a
+ // small resource leak. Teleporting to another region invalidates the
+ // whole list, since it was only ever relevant to avatars in the region
+ // we're leaving, so clear it out at that point to bound the leak.
+ static void resetEarlyAppearanceList()
+ {
+ sEarlyAppearanceList.clear();
+ }
+
LLPointer<LLAppearanceMessageContents> mLastProcessedAppearance;
-public:
void parseAppearanceMessage(LLMessageSystem* mesgsys, LLAppearanceMessageContents& msg);
void processAvatarAppearance(LLMessageSystem* mesgsys);
void applyParsedAppearanceMessage(LLAppearanceMessageContents& contents, bool slam_params);
@@ -979,6 +999,8 @@ private:
bool mIsEditingAppearance; // flag for if we're actively in appearance editing mode
bool mUseLocalAppearance; // flag for if we're using a local composite
+ static uuid_list_t sEarlyAppearanceList;
+
//--------------------------------------------------------------------
// Visibility
//--------------------------------------------------------------------
@@ -1275,7 +1297,8 @@ protected:
private:
F32 mMinPixelArea;
F32 mMaxPixelArea;
- F32 mAdjustedPixelArea;
+ F32 mAdjustedPixelArea = 0.f;
+ F32 mLastCulledPixelArea = -1.f; // Pixel area when last culled, for tracking significant changes
std::string mDebugText;
std::string mBakedTextureDebugText;
diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp
index e5c14a34a5..42cec5854c 100644
--- a/indra/newview/llvoavatarself.cpp
+++ b/indra/newview/llvoavatarself.cpp
@@ -754,6 +754,27 @@ bool LLVOAvatarSelf::setVisualParamWeight(S32 index, F32 weight)
return setParamWeight(param,weight);
}
+bool LLVOAvatarSelf::setVisualParamWeight(S32 index, S32 type, F32 weight)
+{
+ LLViewerVisualParam* param = (LLViewerVisualParam*)LLCharacter::getVisualParam(index);
+ if (!param)
+ {
+ return false;
+ }
+ if (param->getWearableType() == type)
+ {
+ return setParamWeight(param, weight);
+ }
+ else
+ {
+ // setVisualParamWeight at the moment is only used in writeToAvatar.
+ // The type is supposed to match since wearable is a subset of avatar by type.
+ llassert(false);
+ LL_WARNS() << "Visual param index " << index << " is not of type " << type << LL_ENDL;
+ }
+ return false;
+}
+
bool LLVOAvatarSelf::setParamWeight(const LLViewerVisualParam *param, F32 weight)
{
if (!param)
diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h
index 45985b2a80..ff8cbc20a1 100644
--- a/indra/newview/llvoavatarself.h
+++ b/indra/newview/llvoavatarself.h
@@ -97,6 +97,7 @@ public:
/*virtual*/ bool setVisualParamWeight(const LLVisualParam *which_param, F32 weight);
/*virtual*/ bool setVisualParamWeight(const char* param_name, F32 weight);
/*virtual*/ bool setVisualParamWeight(S32 index, F32 weight);
+ /*virtual*/ bool setVisualParamWeight(S32 index, S32 type, F32 weight);
/*virtual*/ void updateVisualParams();
void writeWearablesToAvatar();
/*virtual*/ void idleUpdateAppearanceAnimation();
diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp
index 081eddff8f..df14b46acd 100644
--- a/indra/newview/llvocache.cpp
+++ b/indra/newview/llvocache.cpp
@@ -1756,7 +1756,7 @@ void LLVOCache::writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry:
if(mReadOnly)
{
- LL_WARNS() << "Not writing cache for " << filename << " (handle:" << handle << "): Cache is currently in read-only mode." << LL_ENDL;
+ LL_INFOS() << "Not writing cache for " << filename << " (handle:" << handle << "): Cache is currently in read-only mode." << LL_ENDL;
return ;
}
@@ -1797,7 +1797,10 @@ void LLVOCache::writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry:
if(!dirty_cache)
{
- LL_WARNS() << "Skipping write to cache for " << filename << " (handle:" << handle << "): cache not dirty" << LL_ENDL;
+ if (!LLAppViewer::instance()->isQuitting())
+ {
+ LL_WARNS() << "Skipping write to cache for " << filename << " (handle:" << handle << "): cache not dirty" << LL_ENDL;
+ }
return ; //nothing changed, no need to update.
}
diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp
index 0852258994..6a5e80365b 100644
--- a/indra/newview/llvoicechannel.cpp
+++ b/indra/newview/llvoicechannel.cpp
@@ -128,6 +128,12 @@ void LLVoiceChannel::onChange(EStatusType type, const LLSD& channelInfo, bool pr
{
mChannelInfo = channelInfo;
}
+
+ if (!LLVoiceClient::instanceExists())
+ {
+ return;
+ }
+
if (!LLVoiceClient::getInstance()->compareChannels(mChannelInfo, channelInfo))
{
return;
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index 43a1a284ec..b91b4b180b 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -40,8 +40,8 @@
#include "llagent.h"
#include "lltrans.h"
#include "lluiusage.h"
-#include "llmutelist.h"
#include "llnearbyvoicemoderation.h"
+#include "llmutelist.h"
const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
@@ -602,14 +602,14 @@ LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const L
LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion();
voice_server_type = versionInfo.internalVoiceServerType;
}
+#ifndef DISABLE_WEBRTC
if (voiceChannelInfo.has("voice_server_type") && voiceChannelInfo["voice_server_type"] != voice_server_type)
{
// there's a mismatch between what the peer is offering and what our server
// can handle, so default to webrtc
-#ifndef DISABLE_WEBRTC
voice_server_type = WEBRTC_VOICE_SERVER_TYPE;
-#endif
}
+#endif
LLVoiceModuleInterface *module = getVoiceModule(voice_server_type);
return dynamic_cast<LLVoiceP2POutgoingCallInterface *>(module);
}
@@ -798,9 +798,10 @@ bool LLVoiceClient::getVoiceEnabled(const LLUUID& id) const
std::string LLVoiceClient::getDisplayName(const LLUUID& id) const
{
- std::string result;
#ifndef DISABLE_WEBRTC
- result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id);
+ std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id);
+#else
+ std::string result;
#endif
return result;
}
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
index 1a128a6660..9b4a371cec 100644
--- a/indra/newview/llvoicewebrtc.cpp
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -3197,7 +3197,14 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b
if (!mPrimary && isSpatial() && gAgent.getRegion())
{
is_primary_region = (mRegionID == gAgent.getRegion()->getRegionID());
- LL_WARNS() << "mPrimary is false, expected: " << is_primary_region << " connection state: " << getVoiceConnectionState() << LL_ENDL;
+ if (is_primary_region)
+ {
+ LL_WARNS("Voice") << "mPrimary is false, expected: true, connection state: " << getVoiceConnectionState() << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("Voice") << "mPrimary is false, expected: false, connection state: " << getVoiceConnectionState() << LL_ENDL;
+ }
}
boost::json::object voice_data = voice_data_parsed.as_object();
boost::json::object mute;
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 3b41ccb6fc..076e5eb22e 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -4445,7 +4445,7 @@ U32 LLVOVolume::getTriangleCount(S32* vcount) const
return count;
}
-U32 LLVOVolume::getHighLODTriangleCount()
+U32 LLVOVolume::getLODTriangleCount(S32 lod)
{
U32 ret = 0;
@@ -4453,16 +4453,16 @@ U32 LLVOVolume::getHighLODTriangleCount()
if (!isSculpted())
{
- LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3);
+ LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), lod);
ret = ref->getNumTriangles();
LLPrimitive::getVolumeManager()->unrefVolume(ref);
}
else if (isMesh())
{
- LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3);
+ LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), lod);
if (!ref->isMeshAssetLoaded() || ref->getNumVolumeFaces() == 0)
{
- gMeshRepo.loadMesh(this, volume->getParams(), LLModel::LOD_HIGH);
+ gMeshRepo.loadMesh(this, volume->getParams(), lod);
}
ret = ref->getNumTriangles();
LLPrimitive::getVolumeManager()->unrefVolume(ref);
@@ -4475,6 +4475,11 @@ U32 LLVOVolume::getHighLODTriangleCount()
return ret;
}
+U32 LLVOVolume::getHighLODTriangleCount()
+{
+ return getLODTriangleCount(LLModel::LOD_HIGH);
+}
+
//static
void LLVOVolume::preUpdateGeom()
{
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index b6044bc319..00d6138512 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -155,6 +155,7 @@ public:
/*virtual*/ bool getCostData(LLMeshCostData& costs) const override;
/*virtual*/ U32 getTriangleCount(S32* vcount = NULL) const override;
+ /*virtual*/ U32 getLODTriangleCount(S32 lod) override;
/*virtual*/ U32 getHighLODTriangleCount() override;
/*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
S32 face = -1, // which face to check, -1 = ALL_SIDES
diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp
index 38e49d0750..54eb61a6bf 100644
--- a/indra/newview/llwindowlistener.cpp
+++ b/indra/newview/llwindowlistener.cpp
@@ -90,6 +90,12 @@ LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter&
"to list; all nodes from root if no [\"under\"].",
&LLWindowListener::getPaths,
LLSDMap("reply", LLSD()));
+ add("getSubtree",
+ "Send on [\"reply\"] a nested tree of info maps for all descendants of\n"
+ "optional [\"under\"] path (default: root). Each node contains the same\n"
+ "fields as getInfo plus a [\"children\"] array of child nodes.",
+ &LLWindowListener::getSubtree,
+ LLSDMap("reply", LLSD()));
add("keyDown",
keySomething + "keypress event.\n" + keyExplain +
"The [\"char\"] parameter detects and handles non-ASCII characters seperately\n" + mask,
@@ -265,6 +271,41 @@ void LLWindowListener::getPaths(LLSD const & request)
}
}
+static LLSD buildSubtree(LLView* view)
+{
+ LLSD children;
+ LLSD node = view->getInfo();
+ for (LLView::child_list_const_iter_t it = view->beginChild(); it != view->endChild(); ++it)
+ {
+ children.append(buildSubtree(*it));
+ }
+ node["children"] = children;
+ return node;
+}
+
+void LLWindowListener::getSubtree(LLSD const & request)
+{
+ Response response(LLSD(), request);
+ LLView* root = LLUI::getInstance()->getRootView();
+ LLView* base = nullptr;
+ std::string under(request["under"]);
+
+ if (under.empty())
+ {
+ base = root;
+ }
+ else
+ {
+ base = LLUI::getInstance()->resolvePath(root, under);
+ if (!base)
+ {
+ return response.error(STRINGIZE(request["op"].asString() << " request specified invalid \"under\" path: '" << under << "'"));
+ }
+ }
+
+ response.setResponse(buildSubtree(base));
+}
+
void LLWindowListener::keyDown(LLSD const & evt)
{
Response response(LLSD(), evt);
diff --git a/indra/newview/llwindowlistener.h b/indra/newview/llwindowlistener.h
index d3a16cfde9..537322b19b 100644
--- a/indra/newview/llwindowlistener.h
+++ b/indra/newview/llwindowlistener.h
@@ -41,6 +41,7 @@ public:
void getInfo(LLSD const & evt);
void getPaths(LLSD const & evt);
+ void getSubtree(LLSD const & evt);
void keyDown(LLSD const & evt);
void keyUp(LLSD const & evt);
void mouseDown(LLSD const & evt);
diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp
index d02694de7d..b09c2d00bb 100644
--- a/indra/newview/llworld.cpp
+++ b/indra/newview/llworld.cpp
@@ -794,10 +794,10 @@ void LLWorld::updateNetStats()
S32 packets_in = gMessageSystem->mPacketsIn - mLastPacketsIn;
S32 packets_out = gMessageSystem->mPacketsOut - mLastPacketsOut;
- S32 packets_lost = gMessageSystem->mDroppedPackets - mLastPacketsLost;
+ S32 packets_lost = gMessageSystem->mLostPackets - mLastPacketsLost;
- F64Bits actual_in_bits(gMessageSystem->mPacketRing.getAndResetActualInBits());
- F64Bits actual_out_bits(gMessageSystem->mPacketRing.getAndResetActualOutBits());
+ F64Bits actual_in_bits(gMessageSystem->getAndResetActualInBits());
+ F64Bits actual_out_bits(gMessageSystem->getAndResetActualOutBits());
add(LLStatViewer::MESSAGE_SYSTEM_DATA_IN, actual_in_bits);
add(LLStatViewer::MESSAGE_SYSTEM_DATA_OUT, actual_out_bits);
@@ -806,16 +806,17 @@ void LLWorld::updateNetStats()
add(LLStatViewer::PACKETS_OUT, packets_out);
add(LLStatViewer::PACKETS_LOST, packets_lost);
- F32 total_packets_in = (F32)LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN);
+ LLTrace::Recording& recording = LLViewerStats::instance().getRecording();
+ F32 total_packets_in = (F32)recording.getSum(LLStatViewer::PACKETS_IN);
if (total_packets_in > 0.f)
{
- F32 total_packets_lost = (F32)LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST);
+ F32 total_packets_lost = (F32)recording.getSum(LLStatViewer::PACKETS_LOST);
sample(LLStatViewer::PACKETS_LOST_PERCENT, LLUnits::Ratio::fromValue((F32)total_packets_lost/(F32)total_packets_in));
}
mLastPacketsIn = gMessageSystem->mPacketsIn;
mLastPacketsOut = gMessageSystem->mPacketsOut;
- mLastPacketsLost = gMessageSystem->mDroppedPackets;
+ mLastPacketsLost = gMessageSystem->mLostPackets;
}
@@ -838,7 +839,7 @@ void LLWorld::printPacketsLost()
<< " packets lost: " << cdp->getPacketsLost() << LL_ENDL;
}
}
- LL_INFOS() << "Packets dropped by Packet Ring: " << gMessageSystem->mPacketRing.getNumDroppedPackets() << LL_ENDL;
+ LL_INFOS() << "Packets dropped by Packet Ring: " << gMessageSystem->getTotalNumDroppedPackets() << LL_ENDL;
}
void LLWorld::processCoarseUpdate(LLMessageSystem* msg, void** user_data)
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 6cea54fb16..ab7058b5df 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -625,6 +625,16 @@ void LLPipeline::init()
LLFontWidthBuffer::enableBufferCollection(enable_buffers);
});
}
+
+ cntrl_ptr = gSavedSettings.getControl("CollectUIImageVertexBuffers");
+ if (cntrl_ptr.notNull())
+ {
+ cntrl_ptr->getCommitSignal()->connect([](LLControlVariable* control, const LLSD& value, const LLSD& previous)
+ {
+ bool enable_buffers = control->getValue().asBoolean();
+ LLUIImage::enableDisplayListsCollection(enable_buffers);
+ });
+ }
}
LLPipeline::~LLPipeline()
@@ -1163,6 +1173,8 @@ void LLPipeline::refreshCachedSettings()
bool enable_buffers = gSavedSettings.getBOOL("CollectFontVertexBuffers");
LLFontVertexBuffer::enableBufferCollection(enable_buffers);
LLFontWidthBuffer::enableBufferCollection(enable_buffers);
+ enable_buffers = gSavedSettings.getBOOL("CollectUIImageVertexBuffers");
+ LLUIImage::enableDisplayListsCollection(enable_buffers);
}
void LLPipeline::releaseGLBuffers()
diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index b7f6d23bcc..8e36db587d 100644
--- a/indra/newview/skins/default/textures/textures.xml
+++ b/indra/newview/skins/default/textures/textures.xml
@@ -143,7 +143,6 @@ with the same filename but different name
<texture name="Command_Destinations_Icon" file_name="toolbar_icons/destinations.png" preload="true" />
<texture name="Command_Gestures_Icon" file_name="toolbar_icons/gestures.png" preload="true" />
<texture name="Command_Grid_Status_Icon" file_name="toolbar_icons/grid_status.png" preload="true" />
- <texture name="Command_HowTo_Icon" file_name="toolbar_icons/howto.png" preload="true" />
<texture name="Command_Inventory_Icon" file_name="toolbar_icons/inventory.png" preload="true" />
<texture name="Command_Map_Icon" file_name="toolbar_icons/map.png" preload="true" />
<texture name="Command_Marketplace_Icon" file_name="toolbar_icons/marketplace.png" preload="true" />
diff --git a/indra/newview/skins/default/textures/toolbar_icons/howto.png b/indra/newview/skins/default/textures/toolbar_icons/howto.png
deleted file mode 100644
index 8594d71113..0000000000
--- a/indra/newview/skins/default/textures/toolbar_icons/howto.png
+++ /dev/null
Binary files differ
diff --git a/indra/newview/skins/default/textures/widgets/ComboButton_Off.png b/indra/newview/skins/default/textures/widgets/ComboButton_Off.png
index 7199470307..1bd7f3228d 100644
--- a/indra/newview/skins/default/textures/widgets/ComboButton_Off.png
+++ b/indra/newview/skins/default/textures/widgets/ComboButton_Off.png
Binary files differ
diff --git a/indra/newview/skins/default/textures/widgets/ComboButton_Selected.png b/indra/newview/skins/default/textures/widgets/ComboButton_Selected.png
index 715b401ce5..c0566912e3 100644
--- a/indra/newview/skins/default/textures/widgets/ComboButton_Selected.png
+++ b/indra/newview/skins/default/textures/widgets/ComboButton_Selected.png
Binary files differ
diff --git a/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Off.png b/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Off.png
index 1a9f7e9d71..f00c18d1a8 100644
--- a/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Off.png
+++ b/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Off.png
Binary files differ
diff --git a/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Selected.png b/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Selected.png
index dbe6651dc8..da419b94ea 100644
--- a/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Selected.png
+++ b/indra/newview/skins/default/textures/widgets/SegmentedBtn_Left_Selected.png
Binary files differ
diff --git a/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml
index 0cc13fd736..c6a78fff4e 100644
--- a/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="SCRIPT: NYT SCRIPT">
<floater.string name="not_allowed">
- Du kan ikke se eller redigere dette script, da det er sat til &quot;no copy&quot;. Du skal bruge fulde rettigheder for at kunne se og redigere et script i dette objekt.
+ Du kan ikke se eller redigere dette script, da det er sat til &quot;no copy&quot; eller &quot;no modify&quot;. Du skal have disse tilladelser for at kunne se eller redigere et script.
</floater.string>
<floater.string name="script_running">
Kører
diff --git a/indra/newview/skins/default/xui/da/panel_preferences_setup.xml b/indra/newview/skins/default/xui/da/panel_preferences_setup.xml
index 7be9a9d555..981c35800f 100644
--- a/indra/newview/skins/default/xui/da/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/da/panel_preferences_setup.xml
@@ -45,4 +45,12 @@
<combo_box.item label="Installér automatisk" name="Install_automatically"/>
<combo_box.item label="Hent og installér opdateringer manuelt" name="Install_manual"/>
</combo_box>
+ <text name="os_hibernation_mode">
+ Forhindr OS i at gå i dvale, hvis du ikke er væk:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Forhindrer kun OS i at gå i dvale, hvis brugeren ikke er væk">
+ <combo_box.item label="Kan gå i dvale" name="can_hibernate"/>
+ <combo_box.item label="Kan ikke gå i dvale, kan slukke skærmen" name="cant_hibernate"/>
+ <combo_box.item label="Kan ikke gå i dvale, kan ikke slukke skærmen" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/da/panel_script_ed.xml b/indra/newview/skins/default/xui/da/panel_script_ed.xml
index 3dec4bf101..ae073300b1 100644
--- a/indra/newview/skins/default/xui/da/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/da/panel_script_ed.xml
@@ -4,7 +4,7 @@
Henter...
</panel.string>
<panel.string name="can_not_view">
- Du kan ikke se eller rette dette script, da det er sat til &quot;no copy&quot;. Du skal have fulde rettigheder for at se eller rette et script i et objekt.
+ Du kan ikke se eller redigere dette script, da det er sat til &quot;no copy&quot; eller &quot;no modify&quot;. Du skal have disse tilladelser for at kunne se eller redigere et script.
</panel.string>
<panel.string name="public_objects_can_not_run">
Offentlige objekter kan ikke afvikle scripts
diff --git a/indra/newview/skins/default/xui/de/floater_how_to.xml b/indra/newview/skins/default/xui/de/floater_how_to.xml
deleted file mode 100644
index caea221f83..0000000000
--- a/indra/newview/skins/default/xui/de/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="INFOS"/>
diff --git a/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml
index ae2dd4db67..5b44c44b28 100644
--- a/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="SKRIPT: NEUES SKRIPT">
<floater.string name="not_allowed">
- Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren&quot; festgelegt wurde. Um ein Skript innerhalb eines Objektes anzuzeigen oder zu bearbeiten, benötigen Sie die vollständige Berechtigung.
+ Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren&quot; oder „keine Änderungen&quot; festgelegt wurde. Um ein Skript anzuzeigen oder zu bearbeiten, benötigen Sie diese Berechtigungen.
</floater.string>
<floater.string name="script_running">
Läuft
diff --git a/indra/newview/skins/default/xui/de/notifications.xml b/indra/newview/skins/default/xui/de/notifications.xml
index 6ad71e0ad1..40b34d99ba 100644
--- a/indra/newview/skins/default/xui/de/notifications.xml
+++ b/indra/newview/skins/default/xui/de/notifications.xml
@@ -1127,7 +1127,6 @@ Falls Sie [SECOND_LIFE] zum ersten Mal verwenden, müssen Sie zuerst ein Konto e
Ihr Avatar erscheint jeden Moment.
Benutzen Sie die Pfeiltasten, um sich fortzubewegen.
-Drücken Sie F1 für Hilfe oder für weitere Informationen über [SECOND_LIFE].
Bitte wählen Sie einen männlichen oder weiblichen Avatar.
Sie können sich später noch umentscheiden.
<usetemplate name="okcancelbuttons" notext="Weiblich" yestext="Männlich"/>
diff --git a/indra/newview/skins/default/xui/de/panel_preferences_setup.xml b/indra/newview/skins/default/xui/de/panel_preferences_setup.xml
index a8509cabac..ef7ac09dba 100644
--- a/indra/newview/skins/default/xui/de/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/de/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
Proxy-Einstellungen:
</text>
<button label="Proxy-Einstellungen ändern" label_selected="Durchsuchen" name="set_proxy" width="160"/>
+ <text name="os_hibernation_mode">
+ OS-Ruhezustand verhindern, wenn nicht abwesend:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Verhindert den OS-Ruhezustand nur, wenn der Benutzer nicht abwesend ist">
+ <combo_box.item label="Ruhezustand möglich" name="can_hibernate"/>
+ <combo_box.item label="Kein Ruhezustand, Bildschirm kann ausgeschaltet werden" name="cant_hibernate"/>
+ <combo_box.item label="Kein Ruhezustand, Bildschirm kann nicht ausgeschaltet werden" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/de/panel_script_ed.xml b/indra/newview/skins/default/xui/de/panel_script_ed.xml
index 8975cd40f8..f4ecfd23d6 100644
--- a/indra/newview/skins/default/xui/de/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/de/panel_script_ed.xml
@@ -4,7 +4,7 @@
Wird geladen...
</panel.string>
<panel.string name="can_not_view">
- Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren&quot; festgelegt wurde. Um ein Skript innerhalb eines Objektes anzuzeigen oder zu bearbeiten, benötigen Sie die vollständige Berechtigung.
+ Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren&quot; oder „keine Änderungen&quot; festgelegt wurde. Um ein Skript anzuzeigen oder zu bearbeiten, benötigen Sie diese Berechtigungen.
</panel.string>
<panel.string name="public_objects_can_not_run">
Öffentliche Objekte können keine Skripts ausführen
diff --git a/indra/newview/skins/default/xui/de/sidepanel_item_info.xml b/indra/newview/skins/default/xui/de/sidepanel_item_info.xml
index 3570ccacd2..78f1db943b 100644
--- a/indra/newview/skins/default/xui/de/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/de/sidepanel_item_info.xml
@@ -41,7 +41,7 @@
<text name="LabelItemDescTitle">
Beschreibung:
</text>
- <line_editor name="LabelItemDesc" tool_tip="Bei Auswahl der Option &quot;Mauszeiger-Tipps zu allen Objekten&quot; in den Viewer-Einstellungen wird für alle Objekte das Popup mit der Objektbeschreibung unter dem Mauszeiger eingeblendet. Die formelle Beschreibung ist auf 127 Byte begrenzt, und alle längeren Zeichenketten werden gekürzt."/>
+ <text_editor name="LabelItemDesc" tool_tip="Bei Auswahl der Option &quot;Mauszeiger-Tipps zu allen Objekten&quot; in den Viewer-Einstellungen wird für alle Objekte das Popup mit der Objektbeschreibung unter dem Mauszeiger eingeblendet. Die formelle Beschreibung ist auf 127 Byte begrenzt, und alle längeren Zeichenketten werden gekürzt."/>
<text name="LabelCreatorTitle">
Ersteller:
</text>
diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml
index d7b2ee57a8..bd26672ccf 100644
--- a/indra/newview/skins/default/xui/de/strings.xml
+++ b/indra/newview/skins/default/xui/de/strings.xml
@@ -5537,9 +5537,6 @@ Setzen Sie den Editorpfad in Anführungszeichen
<string name="Command_Grid_Status_Label">
Grid-Status
</string>
- <string name="Command_HowTo_Label">
- Infos
- </string>
<string name="Command_Inventory_Label">
Inventar
</string>
@@ -5636,9 +5633,6 @@ Setzen Sie den Editorpfad in Anführungszeichen
<string name="Command_Grid_Status_Tooltip">
Aktuellen Grid-Status anzeigen
</string>
- <string name="Command_HowTo_Tooltip">
- Wie führe ich gängige Aufgaben aus?
- </string>
<string name="Command_Inventory_Tooltip">
Ihr Eigentum anzeigen und benutzen
</string>
diff --git a/indra/newview/skins/default/xui/en/floater_about_land.xml b/indra/newview/skins/default/xui/en/floater_about_land.xml
index ffb9c0f78e..ebb05c23a5 100644
--- a/indra/newview/skins/default/xui/en/floater_about_land.xml
+++ b/indra/newview/skins/default/xui/en/floater_about_land.xml
@@ -247,23 +247,6 @@
width="360">
TestString PleaseIgnore
</text>
- <button
- follows="right"
- height="23"
- image_pressed="Info_Press"
- image_unselected="Info_Over"
- left_pad="3"
- name="info_btn"
- top_delta="-2"
- width="16" />
- <!-- <button
- follows="left|top"
- height="23"
- label="Profile"
- layout="topleft"
- left_pad="4"
- name="Profile..."
- width="90" />-->
<text
type="string"
length="1"
@@ -272,11 +255,10 @@
layout="topleft"
left="10"
name="Group:"
- top_pad="7"
+ top_pad="13"
width="100">
Group:
</text>
- <!--TODO: HOOK UP GROUP ICON-->
<text
follows="left|top"
height="16"
@@ -287,15 +269,6 @@
width="240">
TestString PleaseIgnore
</text>
- <button
- follows="right"
- height="23"
- image_pressed="Info_Press"
- image_unselected="Info_Over"
- left_pad="3"
- name="info_btn"
- top_delta="-2"
- width="16" />
<button
follows="left|top"
height="23"
@@ -305,7 +278,7 @@
right="-154"
name="Set..."
width="90"
- top_delta="-2"/>
+ top_delta="-5"/>
<check_box
enabled="false"
height="16"
diff --git a/indra/newview/skins/default/xui/en/floater_color_picker.xml b/indra/newview/skins/default/xui/en/floater_color_picker.xml
index 397b66fbd2..d937860f53 100644
--- a/indra/newview/skins/default/xui/en/floater_color_picker.xml
+++ b/indra/newview/skins/default/xui/en/floater_color_picker.xml
@@ -2,7 +2,7 @@
<floater
legacy_header_height="18"
can_minimize="false"
- height="380"
+ height="460"
layout="topleft"
name="ColorPicker"
help_topic="colorpicker"
@@ -153,6 +153,41 @@
name="lspin"
top_delta="0"
width="50" />
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="20"
+ layout="topleft"
+ top_pad="6"
+ left="10"
+ mouse_opaque="false"
+ name="hex_text"
+ width="413">
+ Hex:
+ </text>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="20"
+ layout="topleft"
+ left="60"
+ name="hash_text"
+ top_pad="-20"
+ width="20">
+ #
+ </text>
+ <line_editor
+ font="SansSerif"
+ max_length_bytes="16"
+ top_pad="-22"
+ follows="left"
+ height="20"
+ layout="topleft"
+ left_delta="13"
+ name="hex_color"
+ width="50" />
<check_box
follows="left|bottom"
height="20"
@@ -160,7 +195,7 @@
layout="topleft"
left="10"
name="apply_immediate"
- top_pad="185"
+ top_pad="240"
width="100" />
<button
follows="left|bottom"
@@ -168,19 +203,52 @@
image_selected="eye_button_active.tga"
image_unselected="eye_button_inactive.tga"
layout="topleft"
- left_pad="50"
+ left_pad="0"
name="color_pipette"
width="28" />
+ <combo_box
+ control_name="CopyColorDestination"
+ follows="right|bottom"
+ layout="topleft"
+ right="-197"
+ top_delta="8"
+ name="copy_color_combobox"
+ width="84">
+ <combo_box.item
+ label="Copy as..."
+ name="copy_as"
+ value="0" />
+ <combo_box.item
+ label="LSL"
+ name="lsl_color"
+ value="1" />
+ <combo_box.item
+ label="HEX"
+ name="hex_color"
+ value="2" />
+ <combo_box.item
+ label="RGB"
+ name="rgb_color"
+ value="3" />
+ <combo_box.item
+ label="HSL"
+ name="hsl_color"
+ value="4" />
+ <combo_box.item
+ label="CMYK"
+ name="cmyk_color"
+ value="5" />
+ </combo_box>
<button
follows="right|bottom"
height="23"
label="OK"
label_selected="OK"
layout="topleft"
- right="-120"
- top_delta="9"
+ right="-100"
+ top_delta="0"
name="select_btn"
- width="100" />
+ width="80" />
<button
follows="right|bottom"
height="23"
@@ -190,7 +258,7 @@
left_pad="5"
right="-10"
name="cancel_btn"
- width="100" />
+ width="80" />
<text
type="string"
length="1"
@@ -199,7 +267,7 @@
layout="topleft"
left="10"
name="Current color:"
- top="172"
+ top="194"
width="110">
Current color:
</text>
@@ -211,7 +279,7 @@
layout="topleft"
left="10"
name="(Drag below to save.)"
- top_pad="66"
+ top_pad="62"
width="130"
wrap="true">
(Drag below to save)
diff --git a/indra/newview/skins/default/xui/en/floater_create_landmark.xml b/indra/newview/skins/default/xui/en/floater_create_landmark.xml
index 6d3da72962..343325c124 100644
--- a/indra/newview/skins/default/xui/en/floater_create_landmark.xml
+++ b/indra/newview/skins/default/xui/en/floater_create_landmark.xml
@@ -87,7 +87,7 @@
name="notes_editor"
spellcheck="true"
text_readonly_color="white"
- prevalidator="ascii"
+ prevalidator="ascii_with_newline_no_pipe"
commit_on_focus_lost="true"
top_pad="5"
width="290"
diff --git a/indra/newview/skins/default/xui/en/floater_edit_ext_day_cycle.xml b/indra/newview/skins/default/xui/en/floater_edit_ext_day_cycle.xml
index b911e9b6f5..ea37b45e1e 100644
--- a/indra/newview/skins/default/xui/en/floater_edit_ext_day_cycle.xml
+++ b/indra/newview/skins/default/xui/en/floater_edit_ext_day_cycle.xml
@@ -622,11 +622,13 @@
<button
follows="top|left"
height="23"
+ image_selected="SegmentedBtn_Left_Selected"
+ image_unselected="SegmentedBtn_Left_Off"
label="Save"
left="200"
top_pad="0"
name="save_btn"
- width="156" />
+ width="134" />
<button
follows="top|left"
@@ -634,14 +636,11 @@
name="btn_flyout"
label=""
layout="topleft"
- left_pad="-20"
+ left_pad="0"
top="0"
- image_selected="SegmentedBtn_Right_Selected_Press"
- image_unselected="SegmentedBtn_Right_Off"
- image_pressed="SegmentedBtn_Right_Press"
- image_pressed_selected="SegmentedBtn_Right_Selected_Press"
- image_overlay="Arrow_Small_Up"
- width="20"/>
+ image_selected="ComboButton_Selected"
+ image_unselected="ComboButton_Off"
+ width="22"/>
<button
follows="top|left"
diff --git a/indra/newview/skins/default/xui/en/floater_gesture_autocomplete_picker.xml b/indra/newview/skins/default/xui/en/floater_gesture_autocomplete_picker.xml
new file mode 100644
index 0000000000..cc4a3133cb
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_gesture_autocomplete_picker.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ name="gesture_autocomplete_picker"
+ title="CHOOSE GESTURE"
+ single_instance="true"
+ can_minimize="false"
+ can_tear_off="false"
+ can_resize="true"
+ auto_close="true"
+ layout="topleft"
+ min_width="250"
+ chrome="true"
+ sound_flags="0"
+ height="136"
+ width="340">
+ <floater.string
+ name="showing_count">
+ Showing [COUNT] of [TOTAL]
+ </floater.string>
+ <scroll_list
+ follows="all"
+ height="130"
+ width="336"
+ layout="topleft"
+ left="3"
+ top="3"
+ name="gesture_list"
+ draw_heading="false"
+ multi_select="false">
+ <scroll_list.columns
+ name="trigger"
+ label="Trigger"
+ width="128" />
+ <scroll_list.columns
+ name="name"
+ label="Gesture"
+ dynamic_width="true" />
+ </scroll_list>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/floater_how_to.xml b/indra/newview/skins/default/xui/en/floater_how_to.xml
deleted file mode 100644
index 5b00d23faa..0000000000
--- a/indra/newview/skins/default/xui/en/floater_how_to.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater
- legacy_header_height="18"
- can_resize="false"
- can_minimize="false"
- height="525"
- layout="topleft"
- name="floater_how_to"
- single_instance="true"
- save_visibility="true"
- save_rect="true"
- title="WELCOME ISLAND GUIDEBOOK"
- width="310"
- rel_x="-0.469309"
- rel_y="-0.011166"
- filename="floater_web_content.xml"/>
diff --git a/indra/newview/skins/default/xui/en/floater_inventory_settings.xml b/indra/newview/skins/default/xui/en/floater_inventory_settings.xml
index 5dd3dfa2bf..7bad20b378 100644
--- a/indra/newview/skins/default/xui/en/floater_inventory_settings.xml
+++ b/indra/newview/skins/default/xui/en/floater_inventory_settings.xml
@@ -265,10 +265,10 @@
layout="topleft"
left_pad="12"
top_delta="2"
- name="single_folder_txt"
+ name="avatar_attachment_action"
text_color="White"
- width="300">
- Pressing enter on an avatar attachment
+ width="380">
+ Pressing enter or double clicking on an avatar attachment
</text>
<combo_box
control_name="InventoryAddAttachmentBehavior"
diff --git a/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml
index 26281989a7..9b14eade96 100644
--- a/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml
@@ -15,7 +15,7 @@
width="508">
<floater.string
name="not_allowed">
- You can not view or edit this script, since it has been set as &quot;no copy&quot;. You need full permissions to view or edit a script inside an object.
+ You can not view or edit this script, since it has been set as &quot;no copy&quot; or &quot;no modify&quot;. You need these permissions to view or edit a script.
</floater.string>
<floater.string
name="script_running">
diff --git a/indra/newview/skins/default/xui/en/floater_object_weights.xml b/indra/newview/skins/default/xui/en/floater_object_weights.xml
index 709fbdd27e..e68c00a8d4 100644
--- a/indra/newview/skins/default/xui/en/floater_object_weights.xml
+++ b/indra/newview/skins/default/xui/en/floater_object_weights.xml
@@ -2,14 +2,14 @@
<floater
can_close="true"
can_tear_off="false"
- height="372"
+ height="479"
help_topic="object_weights"
layout="topleft"
name="object_weights"
save_rect="true"
single_instance="true"
title="ADVANCED"
- width="200">
+ width="205">
<floater.string
name="nothing_selected"
value="--"/>
@@ -48,7 +48,7 @@
name="objects"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<text
follows="left|top"
height="16"
@@ -67,7 +67,7 @@
name="prims"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<text
follows="left|top"
height="16"
@@ -85,7 +85,7 @@
left="10"
name="selected_text_border"
top_pad="5"
- width="180"/>
+ width="185"/>
<text
@@ -107,7 +107,7 @@
name="download"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -134,7 +134,7 @@
name="physics"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -161,7 +161,7 @@
name="server"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -188,7 +188,7 @@
name="display"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -214,7 +214,7 @@
left="10"
name="weights_text_border"
top_pad="5"
- width="180"/>
+ width="185"/>
<text
@@ -236,7 +236,7 @@
name="selected"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -263,7 +263,7 @@
name="rezzed_on_land"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -290,7 +290,7 @@
name="remaining_capacity"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -317,7 +317,7 @@
name="total_capacity"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -336,6 +336,15 @@
value="Total capacity"
width="130" />
+ <view_border
+ bevel_style="none"
+ follows="top|left"
+ height="0"
+ layout="topleft"
+ left="10"
+ name="rendering_text_border"
+ top_pad="5"
+ width="185"/>
<text
follows="left|top"
@@ -356,7 +365,7 @@
name="lod_level"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -383,7 +392,7 @@
name="triangles_shown"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -410,7 +419,7 @@
name="pixel_area"
top_pad="3"
value="--"
- width="40" />
+ width="45" />
<loading_indicator
follows="left|top"
height="16"
@@ -428,4 +437,133 @@
top_delta="0"
value="Pixel Area"
width="130" />
+
+ <view_border
+ bevel_style="none"
+ follows="top|left"
+ height="0"
+ layout="topleft"
+ left="10"
+ name="lods_text_border"
+ top_pad="5"
+ width="185"/>
+
+ <text
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left="10"
+ name="lod_info_text"
+ text_color="EmphasisColor"
+ top_pad="10"
+ value="LOD INFO"
+ width="180" />
+ <text
+ follows="left|top"
+ halign="right"
+ height="16"
+ layout="topleft"
+ left="10"
+ name="high_lod_tris"
+ top_pad="3"
+ value="--"
+ width="45" />
+ <loading_indicator
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left="34"
+ name="high_lod_tris_loading_indicator"
+ top_delta="0"
+ width="16" />
+ <text
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left_pad="10"
+ name="high_lod_tris_label"
+ top_delta="0"
+ value="High triangles"
+ width="130" />
+ <text
+ follows="left|top"
+ halign="right"
+ height="16"
+ layout="topleft"
+ left="10"
+ name="medium_lod_tris"
+ top_pad="3"
+ value="--"
+ width="45" />
+ <loading_indicator
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left="34"
+ name="medium_lod_tris_loading_indicator"
+ top_delta="0"
+ width="16" />
+ <text
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left_pad="10"
+ name="medium_lod_tris_label"
+ top_delta="0"
+ value="Medium triangles"
+ width="130" />
+ <text
+ follows="left|top"
+ halign="right"
+ height="16"
+ layout="topleft"
+ left="10"
+ name="low_lod_tris"
+ top_pad="3"
+ value="--"
+ width="45" />
+ <loading_indicator
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left="34"
+ name="low_lod_tris_loading_indicator"
+ top_delta="0"
+ width="16" />
+ <text
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left_pad="10"
+ name="low_lod_tris_label"
+ top_delta="0"
+ value="Low triangles"
+ width="130" />
+ <text
+ follows="left|top"
+ halign="right"
+ height="16"
+ layout="topleft"
+ left="10"
+ name="lowest_lod_tris"
+ top_pad="3"
+ value="--"
+ width="45" />
+ <loading_indicator
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left="34"
+ name="lowest_lod_tris_loading_indicator"
+ top_delta="0"
+ width="16" />
+ <text
+ follows="left|top"
+ height="16"
+ layout="topleft"
+ left_pad="10"
+ name="lowest_lod_tris_label"
+ top_delta="0"
+ value="Lowest triangles"
+ width="130" />
</floater>
diff --git a/indra/newview/skins/default/xui/en/floater_preferences.xml b/indra/newview/skins/default/xui/en/floater_preferences.xml
index b7d992bcb4..b2cfd40d75 100644
--- a/indra/newview/skins/default/xui/en/floater_preferences.xml
+++ b/indra/newview/skins/default/xui/en/floater_preferences.xml
@@ -108,6 +108,12 @@
help_topic="preferences_audio_tab"
name="audio" />
<panel
+ class="panel_preference"
+ filename="panel_preferences_voice.xml"
+ label="Voice"
+ layout="topleft"
+ name="voice" />
+ <panel
class="panel_preference"
filename="panel_preferences_chat.xml"
label="Chat"
diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
index af8e882f10..83934f2132 100644
--- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
+++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
@@ -695,7 +695,7 @@
height="16"
increment="1"
initial_value="160"
- label="Sky:"
+ label="Post Process Quality:"
label_width="164"
layout="topleft"
left="420"
diff --git a/indra/newview/skins/default/xui/en/floater_snapshot.xml b/indra/newview/skins/default/xui/en/floater_snapshot.xml
index acdccdc03a..7f1a59217f 100644
--- a/indra/newview/skins/default/xui/en/floater_snapshot.xml
+++ b/indra/newview/skins/default/xui/en/floater_snapshot.xml
@@ -226,7 +226,6 @@
Filter:
</text>
<combo_box
- control_name="PhotoFilters"
follows="left|right|top"
name="filters_combobox"
tool_tip="Image filters"
diff --git a/indra/newview/skins/default/xui/en/floater_tools.xml b/indra/newview/skins/default/xui/en/floater_tools.xml
index 39762d324f..4cf577e13e 100644
--- a/indra/newview/skins/default/xui/en/floater_tools.xml
+++ b/indra/newview/skins/default/xui/en/floater_tools.xml
@@ -1559,9 +1559,7 @@ even though the user gets a free copy.
left_delta="0"
name="object_horizontal"
top_pad="10"
- width="95"
- font="DejaVu"
- font.size="LSmall" />
+ width="95" />
<menu_button
menu_filename="menu_copy_paste_pos.xml"
follows="top|left"
@@ -2651,9 +2649,7 @@ even though the user gets a free copy.
left="8"
name="object_horizontal"
top_pad="10"
- width="278"
- font="DejaVu"
- font.size="LSmall" />
+ width="278" />
<check_box
height="16"
label="Light"
diff --git a/indra/newview/skins/default/xui/en/floater_translation_settings.xml b/indra/newview/skins/default/xui/en/floater_translation_settings.xml
index 22dbd80cda..d1df8bf80f 100644
--- a/indra/newview/skins/default/xui/en/floater_translation_settings.xml
+++ b/indra/newview/skins/default/xui/en/floater_translation_settings.xml
@@ -228,7 +228,7 @@
height="20"
layout="topleft"
left_pad="10"
- max_length_chars="50"
+ max_length_chars="100"
top_delta="-4"
name="azure_api_key"
width="210" />
diff --git a/indra/newview/skins/default/xui/en/floater_world_map.xml b/indra/newview/skins/default/xui/en/floater_world_map.xml
index 4e64b09c1e..b00890de6c 100644
--- a/indra/newview/skins/default/xui/en/floater_world_map.xml
+++ b/indra/newview/skins/default/xui/en/floater_world_map.xml
@@ -637,7 +637,6 @@
Location:
</text>
<spinner
- control_name="Teleport_Coordinate_X"
decimal_digits="0"
follows="right|bottom"
height="23"
@@ -653,7 +652,6 @@
function="WMap.Coordinates" />
</spinner>
<spinner
- control_name="Teleport_Coordinate_Y"
decimal_digits="0"
follows="right|bottom"
height="23"
@@ -669,7 +667,6 @@
function="WMap.Coordinates" />
</spinner>
<spinner
- control_name="Teleport_Coordinate_Z"
decimal_digits="0"
follows="right|bottom"
height="23"
diff --git a/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml
index 1e10a984da..38d7f09445 100644
--- a/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml
+++ b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml
@@ -35,8 +35,8 @@
parameter="sort_friends_on_top" />
</menu_item_check>
<menu_item_check
- label="Show muted"
- name="show_muted">
+ label="Show blocked"
+ name="show_blocked">
<on_click
function="CallLog.Action"
parameter="show_blocked" />
diff --git a/indra/newview/skins/default/xui/en/menu_login.xml b/indra/newview/skins/default/xui/en/menu_login.xml
index 5fff9b7bc0..ec6be3e0c6 100644
--- a/indra/newview/skins/default/xui/en/menu_login.xml
+++ b/indra/newview/skins/default/xui/en/menu_login.xml
@@ -57,15 +57,6 @@
tear_off="true"
name="Help">
<menu_item_call
- label="Guidebook"
- name="How To"
- shortcut="F1">
- <menu_item_call.on_click
- function="Help.ToggleHowTo"
- parameter="" />
- </menu_item_call>
- <menu_item_separator/>
- <menu_item_call
label="Knowledge Base"
name="Knowledge Base">
<menu_item_call.on_click
@@ -107,7 +98,14 @@
name="Report Bug">
<menu_item_call.on_click
function="Advanced.ReportBug"/>
- </menu_item_call>
+ </menu_item_call>
+ <menu_item_call
+ label="Set UI Size to Default"
+ name="Set UI Size to Default"
+ shortcut="control|alt|shift|R">
+ <menu_item_call.on_click
+ function="View.DefaultUISize" />
+ </menu_item_call>
<menu_item_separator/>
<menu_item_call
label="About [APP_NAME]"
diff --git a/indra/newview/skins/default/xui/en/menu_object_icon.xml b/indra/newview/skins/default/xui/en/menu_object_icon.xml
index d43ce26e56..bf445b0d18 100644
--- a/indra/newview/skins/default/xui/en/menu_object_icon.xml
+++ b/indra/newview/skins/default/xui/en/menu_object_icon.xml
@@ -28,6 +28,14 @@
parameter="not_blocked" />
</menu_item_call>
<menu_item_call
+ label="Report Abuse..."
+ layout="topleft"
+ name="Report Abuse">
+ <menu_item_call.on_click
+ function="ObjectIcon.Action"
+ parameter="report_abuse" />
+ </menu_item_call>
+ <menu_item_call
label="Unblock"
layout="topleft"
name="Unblock">
diff --git a/indra/newview/skins/default/xui/en/menu_url_objectim.xml b/indra/newview/skins/default/xui/en/menu_url_objectim.xml
index fb6081e1fb..f72d2fdd66 100644
--- a/indra/newview/skins/default/xui/en/menu_url_objectim.xml
+++ b/indra/newview/skins/default/xui/en/menu_url_objectim.xml
@@ -23,6 +23,13 @@
<menu_item_call.on_click
function="Url.Unblock" />
</menu_item_call>
+ <menu_item_call
+ label="Report Abuse..."
+ layout="topleft"
+ name="Report Abuse">
+ <menu_item_call.on_click
+ function="Url.ReportAbuseObj" />
+ </menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 34b30e424d..39d2a9b994 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -44,6 +44,13 @@
function="Tools.Link"/>
</menu_item_call>
<menu_item_call
+ label="Visited places..."
+ name="Visited Places"
+ shortcut="control|shift|L">
+ <menu_item_call.on_click
+ function="Tools.Unlink"/>
+ </menu_item_call>
+ <menu_item_call
label="Picks..."
name="Picks">
<menu_item_call.on_click
@@ -1003,6 +1010,17 @@
parameter="region" />
</menu_item_check>
<menu_item_separator/>
+ <menu_item_check
+ label="Switch to Shared after teleport"
+ name="Switch to Shared after teleport">
+ <menu_item_check.on_click
+ function="ToggleControl"
+ parameter="SwitchToSharedEnvAfterTeleport" />
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="SwitchToSharedEnvAfterTeleport" />
+ </menu_item_check>
+ <menu_item_separator/>
<menu_item_call
label="My Environments..."
name="my_environs">
@@ -1030,7 +1048,15 @@
parameter="pause_clouds" />
</menu_item_check>
</menu>
-
+ <menu_item_check
+ label="Mute/unmute sound"
+ name="toggle_sound"
+ shortcut="control|alt|shift|M">
+ <menu_item_check.on_click
+ function="World.ToggleMasterAudio"/>
+ <menu_item_check.on_check
+ function="World.IsMasterAudioMuted"/>
+ </menu_item_check>
<!--
<menu
create_jump_keys="true"
@@ -1815,14 +1841,6 @@ function="World.EnvPreset"
label="Help"
name="Help"
tear_off="true">
- <menu_item_call
- label="Guidebook"
- name="How To"
- shortcut="F1">
- <menu_item_call.on_click
- function="Help.ToggleHowTo"
- parameter="" />
- </menu_item_call>
<!-- <menu_item_call
label="Tutorial"
name="Tutorial">
@@ -1830,7 +1848,6 @@ function="World.EnvPreset"
function="Floater.Show"
parameter="hud" />
</menu_item_call>-->
- <menu_item_separator/>
<menu_item_call
label="Knowledge Base"
name="Knowledge Base">
@@ -3523,6 +3540,16 @@ function="World.EnvPreset"
function="ToggleControl"
parameter="CollectFontVertexBuffers" />
</menu_item_check>
+ <menu_item_check
+ label="Collect UI Image Vertex Buffers"
+ name="Collect UI Image Vertex Buffers">
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="CollectUIImageVertexBuffers" />
+ <menu_item_check.on_click
+ function="ToggleControl"
+ parameter="CollectUIImageVertexBuffers" />
+ </menu_item_check>
<menu_item_separator />
<menu_item_check
label="Enable Shader Cache"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index d45dcfc87e..733734a0d2 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -425,6 +425,17 @@ Initialization with the Marketplace failed because of a system or network error.
</notification>
<notification
+ icon="alertmodal.tga"
+ name="CannotPasteFolderIntoSelf"
+ type="alertmodal">
+ You cannot paste a folder into itself or into one of its subfolders.
+ <tag>fail</tag>
+ <usetemplate
+ name="okbutton"
+ yestext="OK"/>
+ </notification>
+
+ <notification
icon="notifytip.tga"
name="InvalidKeystroke"
type="notifytip">
@@ -3950,7 +3961,6 @@ Local Data you are deleting is shared between multiple grids, are you sure you w
Your character will appear in a moment.
Use arrow keys to walk.
-Press the F1 key at any time for help or to learn more about [SECOND_LIFE].
Please choose the male or female avatar. You can change your mind later.
<usetemplate
name="okcancelbuttons"
@@ -6744,6 +6754,14 @@ Your trash is overflowing. This may cause problems logging in.
</notification>
<notification
+ icon="notifytip.tga"
+ name="InventoryLimitReachedAIS"
+ type="notifytip">
+Your inventory is experiencing issues. Please, contact support.
+ <tag>fail</tag>
+ </notification>
+
+ <notification
icon="alertmodal.tga"
name="InventoryLimitReachedAISAlert"
type="alertmodal">
@@ -7699,7 +7717,7 @@ Message from [NAME]:
type="notify">
<unique/>
This land has damage enabled.
-You can be hurt here. If you die, you will be teleported to your home location.
+You can be hurt here. If you die, you might be teleported to your home location, region telehub or parcel landing point.
</notification>
<notification
diff --git a/indra/newview/skins/default/xui/en/panel_cof_wearables.xml b/indra/newview/skins/default/xui/en/panel_cof_wearables.xml
index 9544042e94..37867b4a0e 100644
--- a/indra/newview/skins/default/xui/en/panel_cof_wearables.xml
+++ b/indra/newview/skins/default/xui/en/panel_cof_wearables.xml
@@ -26,10 +26,12 @@
title="Clothing">
<flat_list_view
allow_select="true"
+ allow_reorder="true"
follows="all"
height="10"
item_pad="3"
keep_selection_visible_on_reshape="true"
+ keep_scroll_pos="true"
layout="topleft"
left="0"
multi_select="true"
diff --git a/indra/newview/skins/default/xui/en/panel_group_roles.xml b/indra/newview/skins/default/xui/en/panel_group_roles.xml
index 868d54401e..90697d5e4b 100644
--- a/indra/newview/skins/default/xui/en/panel_group_roles.xml
+++ b/indra/newview/skins/default/xui/en/panel_group_roles.xml
@@ -565,6 +565,18 @@ things in this group. There&apos;s a broad variety of Abilities.
right="-1"
name="role_assigned_members"
top_pad="0" />
+ <text
+ type="string"
+ height="125"
+ layout="topleft"
+ follows="left|top"
+ left="5"
+ name="members_not_loaded"
+ visible="false"
+ top_delta="3"
+ width="300">
+ Members are not loaded
+ </text>
<check_box
height="15"
label="Reveal members"
diff --git a/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml b/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml
index 18dcc86649..eeb37eddcf 100644
--- a/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml
+++ b/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml
@@ -13,6 +13,7 @@
border="false"
bevel_style="none"
show_item_link_overlays="true"
+ preinitialize_views="false"
>
<scroll reserve_scroll_corner="false" />
</inbox_inventory_panel>
diff --git a/indra/newview/skins/default/xui/en/panel_landmark_info.xml b/indra/newview/skins/default/xui/en/panel_landmark_info.xml
index 19599efdc2..bd3f66115e 100644
--- a/indra/newview/skins/default/xui/en/panel_landmark_info.xml
+++ b/indra/newview/skins/default/xui/en/panel_landmark_info.xml
@@ -336,7 +336,7 @@
read_only="true"
spellcheck="true"
text_readonly_color="white"
- prevalidator="ascii_with_newline"
+ prevalidator="ascii_with_newline_no_pipe"
top_pad="5"
width="290"
wrap="true" />
diff --git a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml
index 96624e7aa2..4eafa09ac5 100644
--- a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml
+++ b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml
@@ -17,7 +17,7 @@
You don't have any outfits yet. Try [secondlife:///app/search/all/ Search]
</string>
<string name="no_matched_outfits_msg">
- Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search].
+ No matching items.
</string>
<text
type="string"
diff --git a/indra/newview/skins/default/xui/en/panel_outfits_list.xml b/indra/newview/skins/default/xui/en/panel_outfits_list.xml
index b38e2b2b50..b44d44e4ec 100644
--- a/indra/newview/skins/default/xui/en/panel_outfits_list.xml
+++ b/indra/newview/skins/default/xui/en/panel_outfits_list.xml
@@ -24,7 +24,7 @@
width="309">
<no_matched_tabs_text
name="no_matched_outfits_msg"
- value="Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search]."
+ value="No matching items."
v_pad="10"/>
<no_visible_tabs_text
name="no_outfits_msg"
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_setup.xml b/indra/newview/skins/default/xui/en/panel_preferences_setup.xml
index 2036ed75ca..d5f2f69df7 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_setup.xml
@@ -246,5 +246,41 @@
<button.commit_callback
function="Pref.Proxy" />
</button>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="10"
+ layout="topleft"
+ left="30"
+ name="os_hibernation_mode"
+ mouse_opaque="false"
+ top_pad="8"
+ width="300">
+ Prevent OS from hibernating if not Away:
+ </text>
+ <combo_box
+ control_name="OSHibernationMode"
+ follows="left|top"
+ height="23"
+ layout="topleft"
+ left_delta="50"
+ top_pad="10"
+ name="os_hibernation_combobox"
+ width="300"
+ tool_tip="Only prevents OS from sleeping if user is not away">
+ <combo_box.item
+ label="Can hibernate"
+ name="can_hibernate"
+ value="0" />
+ <combo_box.item
+ label="Can't hibernate, can turn screen off"
+ name="cant_hibernate"
+ value="1" />
+ <combo_box.item
+ label="Can't hibernate, can't turn screen off"
+ name="cant_hibernate_screen"
+ value="2" />
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
index 1dcbf4e666..18fd7fe2d3 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
@@ -35,7 +35,7 @@
name="System Volume"
show_text="false"
slider_label.halign="right"
- top="10"
+ top="12"
volume="true"
width="300">
<slider.commit_callback
@@ -316,7 +316,7 @@
follows="left|top"
layout="topleft"
left="23"
- top_delta="25"
+ top_delta="35"
name="Listen media from"
height="15"
width="180"
@@ -346,6 +346,7 @@
layout="topleft"
height="15"
left="23"
+ top_delta="32"
width="180"
name="media_autoplay_label"
halign="right">
@@ -379,6 +380,7 @@
layout="topleft"
height="15"
left="23"
+ top_delta="32"
width="180"
name="media_firstinteract_label"
halign="right">
@@ -435,11 +437,12 @@
enabled_control="AudioStreamingMedia"
value="true"
follows="left|top"
+ layout="topleft"
tool_tip="Uncheck this to hide media attached to other avatars nearby"
label="Play media attached to other avatars"
left="23"
width="15"
- top_delta="30"
+ top_delta="35"
height="15"/>
<check_box
name="media_huds_autoplay"
@@ -450,171 +453,7 @@
layout="topleft"
tool_tip="Uncheck this to make HUDs follow the standard media auto-play setting"
label="Auto-play media attached to your HUD"
- left="260"
- top_pad="-15"
+ top_delta="25"
width="15"
height="15"/>
- <text
- layout="topleft"
- follows="left"
- height="15"
- width="187"
- name="noise_suppression_label"
- left="23"
- top_delta="22"
- halign="right">
- Microphone Noise Suppression
- </text>
- <combo_box
- control_name="VoiceNoiseSuppressionLevel"
- enabled_control="EnableVoiceChat"
- follows="left|top"
- layout="topleft"
- left_delta="190"
- top_delta="-6"
- width="130"
- height="23"
- name="noise_suppression_combo">
- <item
- label="Off"
- name="noise_suppression_none"
- value="0"/>
- <item
- label="Low"
- name="noise_suppression_low"
- value="1"/>
- <item
- label="Moderate"
- name="noise_suppression_moderate"
- value="2"/>
- <item
- label="High"
- name="noise_suppression_high"
- value="3"/>
- <item
- label="Max"
- name="noise_suppression_max"
- value="4"/>
- </combo_box>
- <text
- type="string"
- length="1"
- follows="left|top"
- layout="topleft"
- left="23"
- top_delta="30"
- name="Listen from"
- width="180"
- height="15"
- halign="right">
- Hear voice from
- </text>
- <combo_box
- enabled_control="EnableVoiceChat"
- control_name="VoiceEarLocation"
- follows="left|top"
- layout="topleft"
- left_delta="190"
- top_delta="-6"
- width="130"
- height="23"
- name="ear_location_combo">
- <item
- label="Camera position"
- name="camera_position"
- value="0" />
- <item
- label="Avatar position"
- name="avatar_position"
- value="1" />
-</combo_box>
- <check_box
- control_name="LipSyncEnabled"
- enabled_control="EnableVoiceChat"
- follows="left|top"
- height="15"
- label="Move avatar lips when speaking"
- layout="topleft"
- left="20"
- name="enable_lip_sync"
- top_pad="8"
- width="237"/>
- <check_box
- control_name="VoiceEchoCancellation"
- enabled_control="EnableVoiceChat"
- height="15"
- tool_tip="Check to enable voice echo cancellation"
- label="Echo Cancellation"
- layout="topleft"
- left="275"
- name="enable_echo_cancellation"
- top_pad="-15"
- width="200"/>
- <check_box
- follows="top|left"
- enabled_control="EnableVoiceChat"
- control_name="PushToTalkToggle"
- height="15"
- label="Toggle speak on/off with toolbar button"
- layout="topleft"
- left="20"
- name="push_to_talk_toggle_check"
- width="245"
- tool_tip="When in toggle mode, press and release the trigger key ONCE to switch your microphone on or off. When not in toggle mode, the microphone broadcasts your voice only while the trigger is being held down."
- top_pad="5"/>
- <check_box
- control_name="VoiceAutomaticGainControl"
- enabled_control="EnableVoiceChat"
- height="15"
- tool_tip="Check to enable automatic gain control"
- label="Automatic Gain Control"
- layout="topleft"
- name="voice_automatic_gain_control"
- left="275"
- top_pad="-15"
- width="200"/>
- <check_box
- name="gesture_audio_play_btn"
- control_name="EnableGestureSounds"
- disabled_control="MuteAudio"
- value="true"
- follows="left|bottom|right"
- height="15"
- tool_tip="Check this to hear sounds from gestures"
- label="Play sounds from gestures"
- top_pad="5"
- left="20"/>
- <check_box
- control_name="VoiceVisualizerEnabled"
- enabled_control="EnableVoiceChat"
- height="15"
- tool_tip="Check to show voice dot indicator above avatars"
- label="Show voice dot above avatars"
- layout="topleft"
- name="voice_dot_visualizer"
- left="275"
- top_pad="-15"
- width="200"/>
- <button
- control_name="ShowDeviceSettings"
- enabled_control="EnableVoiceChat"
- follows="left|top"
- height="23"
- is_toggle="true"
- label="Voice Input/Output devices"
- layout="topleft"
- left="20"
- top_pad="1"
- name="device_settings_btn"
- width="200">
- </button>
- <panel
- layout="topleft"
- filename="panel_sound_devices.xml"
- visiblity_control="ShowDeviceSettings"
- name="device_settings_panel"
- top_pad="0"
- width="480"
- left="18"
- class="panel_voice_device_settings"/>
</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_voice.xml b/indra/newview/skins/default/xui/en/panel_preferences_voice.xml
new file mode 100644
index 0000000000..6d8e39e03c
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_preferences_voice.xml
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<panel
+ border="true"
+ follows="all"
+ height="438"
+ label="Voice"
+ layout="topleft"
+ left="102"
+ name="voice_panel"
+ top="1"
+ width="517">
+ <slider
+ control_name="AudioLevelVoice"
+ disabled_control="MuteAudio"
+ follows="left|top"
+ height="16"
+ increment="0.025"
+ initial_value="0.5"
+ label="Voice Chat"
+ label_width="120"
+ layout="topleft"
+ left="0"
+ top="19"
+ name="Voice Volume"
+ show_text="false"
+ slider_label.halign="right"
+ volume="true"
+ width="300">
+ <slider.commit_callback
+ function="Pref.setControlFalse"
+ parameter="MuteVoice" />
+ </slider>
+ <button
+ control_name="MuteVoice"
+ disabled_control="MuteAudio"
+ follows="top|left"
+ height="16"
+ image_selected="AudioMute_Off"
+ image_unselected="Audio_Off"
+ is_toggle="true"
+ layout="topleft"
+ left_pad="5"
+ name="mute_audio_voice"
+ tab_stop="false"
+ width="16" />
+ <check_box
+ label_text.halign="left"
+ follows="left|top"
+ height="16"
+ control_name ="EnableVoiceChat"
+ disabled_control="CmdLineDisableVoice"
+ label="Enabled"
+ layout="topleft"
+ top_delta="2"
+ left_pad="5"
+ name="enable_voice_check"
+ width="110"/>
+ <text
+ layout="topleft"
+ follows="left"
+ height="15"
+ width="187"
+ name="noise_suppression_label"
+ left="23"
+ top_delta="42"
+ halign="right">
+ Microphone Noise Suppression
+ </text>
+ <combo_box
+ control_name="VoiceNoiseSuppressionLevel"
+ enabled_control="EnableVoiceChat"
+ follows="left|top"
+ layout="topleft"
+ left_delta="190"
+ top_delta="-6"
+ width="130"
+ height="23"
+ name="noise_suppression_combo">
+ <item
+ label="Off"
+ name="noise_suppression_none"
+ value="0"/>
+ <item
+ label="Low"
+ name="noise_suppression_low"
+ value="1"/>
+ <item
+ label="Moderate"
+ name="noise_suppression_moderate"
+ value="2"/>
+ <item
+ label="High"
+ name="noise_suppression_high"
+ value="3"/>
+ <item
+ label="Max"
+ name="noise_suppression_max"
+ value="4"/>
+ </combo_box>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ layout="topleft"
+ left="23"
+ top_delta="35"
+ name="Listen from"
+ width="180"
+ height="15"
+ halign="right">
+ Hear voice from
+ </text>
+ <combo_box
+ enabled_control="EnableVoiceChat"
+ control_name="VoiceEarLocation"
+ follows="left|top"
+ layout="topleft"
+ left_delta="190"
+ top_delta="-6"
+ width="130"
+ height="23"
+ name="ear_location_combo">
+ <item
+ label="Camera position"
+ name="camera_position"
+ value="0" />
+ <item
+ label="Avatar position"
+ name="avatar_position"
+ value="1" />
+ </combo_box>
+ <check_box
+ control_name="LipSyncEnabled"
+ enabled_control="EnableVoiceChat"
+ follows="left|top"
+ height="15"
+ label="Move avatar lips when speaking"
+ layout="topleft"
+ left="20"
+ name="enable_lip_sync"
+ top_pad="16"
+ width="237"/>
+ <check_box
+ follows="top|left"
+ enabled_control="EnableVoiceChat"
+ control_name="PushToTalkToggle"
+ height="15"
+ label="Toggle speak on/off with toolbar button"
+ layout="topleft"
+ left="20"
+ name="push_to_talk_toggle_check"
+ width="245"
+ tool_tip="When in toggle mode, press and release the trigger key ONCE to switch your microphone on or off. When not in toggle mode, the microphone broadcasts your voice only while the trigger is being held down."
+ top_pad="9"/>
+ <check_box
+ name="gesture_audio_play_btn"
+ control_name="EnableGestureSounds"
+ disabled_control="MuteAudio"
+ value="true"
+ follows="left|bottom|right"
+ layout="topleft"
+ height="15"
+ tool_tip="Check this to hear sounds from gestures"
+ label="Play sounds from gestures"
+ top_pad="9"
+ left="20"/>
+ <check_box
+ control_name="VoiceEchoCancellation"
+ enabled_control="EnableVoiceChat"
+ height="15"
+ tool_tip="Check to enable voice echo cancellation"
+ label="Echo Cancellation"
+ layout="topleft"
+ left="20"
+ name="enable_echo_cancellation"
+ top_pad="9"
+ width="200"/>
+ <check_box
+ control_name="VoiceAutomaticGainControl"
+ enabled_control="EnableVoiceChat"
+ height="15"
+ tool_tip="Check to enable automatic gain control"
+ label="Automatic Gain Control"
+ layout="topleft"
+ name="voice_automatic_gain_control"
+ left="20"
+ top_pad="9"
+ width="200"/>
+ <check_box
+ control_name="VoiceVisualizerEnabled"
+ enabled_control="EnableVoiceChat"
+ height="15"
+ tool_tip="Check to show voice dot indicator above avatars"
+ label="Show voice dot above avatars"
+ layout="topleft"
+ name="voice_dot_visualizer"
+ left="20"
+ top_pad="9"
+ width="200"/>
+ <button
+ control_name="ShowDeviceSettings"
+ enabled_control="EnableVoiceChat"
+ follows="left|top"
+ height="23"
+ is_toggle="true"
+ label="Voice Input/Output devices"
+ layout="topleft"
+ left="20"
+ top_pad="21"
+ name="device_settings_btn"
+ width="200">
+ </button>
+ <panel
+ layout="topleft"
+ filename="panel_sound_devices.xml"
+ visiblity_control="ShowDeviceSettings"
+ name="device_settings_panel"
+ top_pad="0"
+ width="480"
+ left="18"
+ class="panel_voice_device_settings"/>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_script_ed.xml b/indra/newview/skins/default/xui/en/panel_script_ed.xml
index 2ccf379b8d..4b884e7eb6 100644
--- a/indra/newview/skins/default/xui/en/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/en/panel_script_ed.xml
@@ -14,7 +14,7 @@
</panel.string>
<panel.string
name="can_not_view">
- You can not view or edit this script, since it has been set as &quot;no copy&quot;. You need full permissions to view or edit a script inside an object.
+ You can not view or edit this script, since it has been set as &quot;no copy&quot; or &quot;no modify&quot;. You need these permissions to view or edit a script.
</panel.string>
<panel.string
name="public_objects_can_not_run">
diff --git a/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml b/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml
index 23bbf45e88..f39f8cb782 100644
--- a/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml
+++ b/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml
@@ -138,7 +138,9 @@
min_val_x="-30"
max_val_x="30"
min_val_y="-30"
- max_val_y="30"
+ max_val_y="30"
+ increment_x="0.01f"
+ increment_y="0.01f"
logarithmic="true"/>
<text
diff --git a/indra/newview/skins/default/xui/en/panel_sound_devices.xml b/indra/newview/skins/default/xui/en/panel_sound_devices.xml
index b858d0ed02..72fdf20eca 100644
--- a/indra/newview/skins/default/xui/en/panel_sound_devices.xml
+++ b/indra/newview/skins/default/xui/en/panel_sound_devices.xml
@@ -24,28 +24,6 @@
name="device_not_loaded">
Device not loaded
</string>
- <icon
- follows="left|top"
- height="18"
- image_name="Microphone_On"
- left_delta="-5"
- name="microphone_icon"
- mouse_opaque="false"
- top="7"
- layout="topleft"
- visible="true"
- width="18" />
- <icon
- follows="left|top"
- height="18"
- image_name="Parcel_Voice_Dark"
- layout="topleft"
- left_pad="220"
- name="speaker_icon"
- mouse_opaque="false"
- top_delta="0"
- visible="true"
- width="22" />
<text
type="string"
length="1"
@@ -53,8 +31,8 @@
follows="left|top"
height="16"
layout="topleft"
- left_pad="-240"
- top_delta="5"
+ left="18"
+ top="18"
name="Input"
width="60">
Input
@@ -64,7 +42,7 @@
control_name="VoiceInputAudioDevice"
follows="left|top"
layout="topleft"
- left_pad="0"
+ left_pad="25"
max_chars="128"
name="voice_input_device"
top_delta="-5"
@@ -76,9 +54,9 @@
follows="left|top"
height="15"
layout="topleft"
- left_pad="30"
name="Output"
- top_delta="5"
+ top_delta="35"
+ left="18"
width="60">
Output
</text>
@@ -87,7 +65,7 @@
height="23"
follows="left|top"
layout="topleft"
- left_pad="0"
+ left_pad="25"
max_chars="128"
name="voice_output_device"
top_delta="-4"
@@ -99,7 +77,7 @@
follows="left|top"
height="16"
layout="topleft"
- left_delta="-300"
+ left="18"
name="My volume label"
top_pad="14"
width="200">
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 5ff02de6f6..d0b4cfdb5b 100644
--- a/indra/newview/skins/default/xui/en/panel_tools_texture.xml
+++ b/indra/newview/skins/default/xui/en/panel_tools_texture.xml
@@ -153,9 +153,7 @@
left="8"
name="object_horizontal"
top_pad="4"
- width="278"
- font="DejaVu"
- font.size="LSmall" />
+ width="278" />
<text
type="string"
length="1"
@@ -806,11 +804,12 @@
height="19"
initial_value="0"
label="Horizontal scale"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="TexScaleU"
top_pad="5"
width="265"
@@ -821,11 +820,12 @@
height="19"
initial_value="0"
label="Vertical scale"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="TexScaleV"
width="265"
font="DejaVu"
@@ -866,11 +866,12 @@
height="19"
initial_value="0"
label="Horizontal offset"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-1"
max_val="1"
+ decimal_digits="5"
name="TexOffsetU"
width="265"
font="DejaVu"
@@ -880,11 +881,12 @@
height="19"
initial_value="0"
label="Vertical offset"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-1"
max_val="1"
+ decimal_digits="5"
name="TexOffsetV"
width="265"
font="DejaVu"
@@ -894,11 +896,12 @@
height="19"
initial_value="0"
label="Horizontal scale"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="bumpyScaleU"
top_delta="-115"
width="265"
@@ -909,11 +912,12 @@
height="19"
initial_value="0"
label="Vertical scale"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="bumpyScaleV"
width="265"
font="DejaVu"
@@ -940,11 +944,12 @@
height="19"
initial_value="0"
label="Horizontal offset"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-1"
max_val="1"
+ decimal_digits="5"
name="bumpyOffsetU"
width="265"
font="DejaVu"
@@ -954,11 +959,12 @@
height="19"
initial_value="0"
label="Vertical offset"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-1"
max_val="1"
+ decimal_digits="5"
name="bumpyOffsetV"
width="265"
font="DejaVu"
@@ -968,11 +974,12 @@
height="19"
initial_value="0"
label="Horizontal scale"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="shinyScaleU"
top_delta="-115"
width="265"
@@ -983,11 +990,12 @@
height="19"
initial_value="0"
label="Vertical scale"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="shinyScaleV"
width="265"
font="DejaVu"
@@ -1014,11 +1022,12 @@
height="19"
initial_value="0"
label="Horizontal offset"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-1"
max_val="1"
+ decimal_digits="5"
name="shinyOffsetU"
width="265"
font="DejaVu"
@@ -1028,11 +1037,12 @@
height="19"
initial_value="0"
label="Vertical offset"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-1"
max_val="1"
+ decimal_digits="5"
name="shinyOffsetV"
width="265"
font="DejaVu"
@@ -1081,11 +1091,12 @@
height="19"
initial_value="1"
label="Scale u"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="gltfTextureScaleU"
top_delta="34"
width="265"
@@ -1096,11 +1107,12 @@
height="19"
initial_value="1"
label="Scale v"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-10000"
max_val="10000"
+ decimal_digits="5"
name="gltfTextureScaleV"
width="265"
font="DejaVu"
@@ -1139,11 +1151,12 @@
height="19"
initial_value="0"
label="Offset u"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-999"
max_val="999"
+ decimal_digits="5"
name="gltfTextureOffsetU"
width="265"
font="DejaVu"
@@ -1153,11 +1166,12 @@
height="19"
initial_value="0"
label="Offset v"
- label_width="205"
+ label_width="195"
layout="topleft"
left="10"
min_val="-999"
max_val="999"
+ decimal_digits="5"
name="gltfTextureOffsetV"
width="265"
font="DejaVu"
diff --git a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
index 40a88d4121..5f97f27db6 100644
--- a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
@@ -212,7 +212,7 @@ TestString PleaseIgnore
Description:
</text>
<text_editor
- prevalidator="ascii_printable_no_pipe"
+ prevalidator="ascii_with_newline_no_pipe"
commit_on_focus_lost="true"
border_style="line"
border_thickness="1"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 0dbcfe9bfe..b6947f3258 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -206,6 +206,8 @@ If you feel this is an error, please contact support@secondlife.com</string>
<string name="AgentLostConnection">This region may be experiencing trouble. Please check your connection to the Internet.</string>
<string name="SavingSettings">Saving your settings...</string>
<string name="LoggingOut">Logging out...</string>
+ <string name="CompressingInventoryCache">Compressing inventory cache...</string>
+ <string name="ShutdownCleanup">Releasing resources...</string>
<string name="ShuttingDown">Shutting down...</string>
<string name="YouHaveBeenDisconnected">You have been disconnected from the region you were in.</string>
<string name="SentToInvalidRegion">You were sent to an invalid region.</string>
@@ -225,6 +227,7 @@ If you feel this is an error, please contact support@secondlife.com</string>
<string name="Toycamera">Toy Camera</string>
<!-- Tooltip -->
+ <string name="ReorderClothingTooltip">Drag to reorder layers, or use the arrows</string> <!-- Hint shown on clothing rows that can be dragged to reorder -->
<string name="TooltipPerson">Person</string><!-- Object under mouse pointer is an avatar -->
<string name="TooltipNoName">(no name)</string> <!-- No name on an object -->
<string name="TooltipOwner">Owner:</string> <!-- Owner name follows -->
@@ -598,7 +601,7 @@ http://secondlife.com/support for help fixing this problem.
<string name="Validator_ShouldBeDigitOrAlphaOrPunctNotSpace">Invalid character [NR]: '[CH]' (should only be a digit or an alpha-numeric ASCII character or a punctuation with no space)</string>
<string name="Validator_ShouldBeDigitNotSpace">Invalid character [NR]: '[CH]' (should only be a digit with no space)</string>
<string name="Validator_ShouldBeASCII">Invalid character [NR]: '[CH]' (should only be an ASCII character)</string>
- <string name="Validator_ShouldBeNewLineOrASCII">Invalid character [NR]: '[CH]' (should only be an ASCII character or a new line)</string>
+ <string name="Validator_ShouldBeNewLineOrASCIINoPipe">Character [CH] at position [NR] should be printable ASCII (excluding pipe) or newline</string>
<!-- LSL Usage Hover Tips -->
<!-- NOTE: For now these are set as translate="false", until DEV-40761 is implemented (to internationalize the rest of tooltips in the same window).
@@ -2325,7 +2328,7 @@ For AI Character: Get the closest navigable point to the point provided.
<string name="LeaveMouselook">Press ESC to return to World View</string>
<!-- inventory -->
- <string name="InventoryNoMatchingItems">Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search].</string>
+ <string name="InventoryNoMatchingItems">No matching items.</string>
<string name="InventoryNoMatchingRecentItems">Didn't find what you're looking for? Try [secondlife:///app/inventory/filters Show filters].</string>
<string name="InventoryNoMatchingFavorites">You haven't marked any items as favorites.</string>
<string name="PlacesNoMatchingItems">To add a place to your landmarks, click the star to the right of the location name.</string>
@@ -4209,7 +4212,6 @@ name="Command_360_Capture_Label">360 snapshot</string>
<string name="Command_Environments_Label">My Environments</string>
<string name="Command_Gestures_Label">Gestures</string>
<string name="Command_Grid_Status_Label">Grid status</string>
- <string name="Command_HowTo_Label">Guidebook</string>
<string name="Command_Inventory_Label">Inventory</string>
<string name="Command_Map_Label">Map</string>
<string name="Command_Marketplace_Label">Marketplace</string>
@@ -4242,7 +4244,6 @@ name="Command_360_Capture_Tooltip">Capture a 360 equirectangular image</string>
<string name="Command_Environments_Tooltip">My Environments</string>
<string name="Command_Gestures_Tooltip">Gestures for your avatar</string>
<string name="Command_Grid_Status_Tooltip">Show current Grid status</string>
- <string name="Command_HowTo_Tooltip">How to do common tasks</string>
<string name="Command_Inventory_Tooltip">View and use your belongings</string>
<string name="Command_Map_Tooltip">Map of the world</string>
<string name="Command_Marketplace_Tooltip">Go shopping</string>
diff --git a/indra/newview/skins/default/xui/en/widgets/flyout_button.xml b/indra/newview/skins/default/xui/en/widgets/flyout_button.xml
index e42bef4a45..c26c698fb2 100644
--- a/indra/newview/skins/default/xui/en/widgets/flyout_button.xml
+++ b/indra/newview/skins/default/xui/en/widgets/flyout_button.xml
@@ -4,9 +4,11 @@
image_selected="SegmentedBtn_Left_Selected"
image_unselected="SegmentedBtn_Left_Off"
image_disabled="SegmentedBtn_Left_Disabled"/>
- <combo_button image_selected="ComboButton_Selected"
- image_unselected="ComboButton_Off"
- image_disabled="ComboButton_Disabled"
- pad_right="6"
- tab_stop="false"/>
+ <drop_down_button image_selected="ComboButton_Selected"
+ image_unselected="ComboButton_Off"
+ image_disabled="ComboButton_Disabled"
+ image_pressed="ComboButton_Selected"
+ image_pressed_selected="ComboButton_Selected"
+ pad_right="6"
+ tab_stop="false"/>
</flyout_button>
diff --git a/indra/newview/skins/default/xui/es/floater_how_to.xml b/indra/newview/skins/default/xui/es/floater_how_to.xml
deleted file mode 100644
index 4a57dc3643..0000000000
--- a/indra/newview/skins/default/xui/es/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="CÓMO"/>
diff --git a/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml
index 9672f910ea..9686dc8c2b 100644
--- a/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="SCRIPT: SCRIPT NUEVO">
<floater.string name="not_allowed">
- No puedes ver ni editar este script. Ha sido configurado como &quot;no copiable&quot;. Necesitas todos los permisos para ver o editar un script que está dentro de un objeto.
+ No puedes ver ni editar este script. Ha sido configurado como &quot;no copiable&quot; o &quot;no modificable&quot;. Necesitas estos permisos para ver o editar un script.
</floater.string>
<floater.string name="script_running">
Ejecutándose
diff --git a/indra/newview/skins/default/xui/es/notifications.xml b/indra/newview/skins/default/xui/es/notifications.xml
index 739391b965..5dee9eb3a8 100644
--- a/indra/newview/skins/default/xui/es/notifications.xml
+++ b/indra/newview/skins/default/xui/es/notifications.xml
@@ -1119,7 +1119,6 @@ Puedes revisar tu conexión a Internet y volver a intentarlo en unos minutos, pu
Tu personaje aparecerá en un momento.
Para caminar, usa las teclas del cursor.
-En cualquier momento, puedes pulsar la tecla F1 para conseguir ayuda o para aprender más acerca de [SECOND_LIFE].
Por favor, elige el avatar masculino o femenino.
Puedes cambiar más adelante tu elección.
<usetemplate name="okcancelbuttons" notext="Mujer" yestext="Varón"/>
diff --git a/indra/newview/skins/default/xui/es/panel_preferences_setup.xml b/indra/newview/skins/default/xui/es/panel_preferences_setup.xml
index 36888850c4..27b3c407cf 100644
--- a/indra/newview/skins/default/xui/es/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/es/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
Configuración de proxy:
</text>
<button label="Ajustar la configuración de proxy" label_selected="Elegir" name="set_proxy"/>
+ <text name="os_hibernation_mode">
+ Evitar que el sistema entre en hibernación si no está Ausente:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Solo evita que el sistema entre en suspensión si el usuario no está ausente">
+ <combo_box.item label="Puede hibernar" name="can_hibernate"/>
+ <combo_box.item label="No puede hibernar, puede apagar la pantalla" name="cant_hibernate"/>
+ <combo_box.item label="No puede hibernar, no puede apagar la pantalla" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/es/panel_script_ed.xml b/indra/newview/skins/default/xui/es/panel_script_ed.xml
index 89e9304693..e19580533b 100644
--- a/indra/newview/skins/default/xui/es/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/es/panel_script_ed.xml
@@ -4,7 +4,7 @@
Cargando...
</panel.string>
<panel.string name="can_not_view">
- No puedes ver ni editar este script. Ha sido configurado como &quot;no copiable&quot;. Necesitas todos los permisos para ver o editar un script que está dentro de un objeto.
+ No puedes ver ni editar este script. Ha sido configurado como &quot;no copiable&quot; o &quot;no modificable&quot;. Necesitas estos permisos para ver o editar un script.
</panel.string>
<panel.string name="public_objects_can_not_run">
Los objetos públicos no pueden ejecutar scripts
diff --git a/indra/newview/skins/default/xui/es/sidepanel_item_info.xml b/indra/newview/skins/default/xui/es/sidepanel_item_info.xml
index 0734e9c417..c3f0a2995e 100644
--- a/indra/newview/skins/default/xui/es/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/es/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
Descripción:
</text>
- <line_editor name="LabelItemDesc" tool_tip="Cuando la opción «Mostrar Consejos al pasar el cursor en Todos los objetos» en los parámetros del visor esté seleccionada, cada vez que coloques el cursor del mouse sobre un objeto aparecerá su descripción. La descripción del prim está limitada a 127 bytes, cualquier string más larga quedará recortada."/>
+ <text_editor name="LabelItemDesc" tool_tip="Cuando la opción «Mostrar Consejos al pasar el cursor en Todos los objetos» en los parámetros del visor esté seleccionada, cada vez que coloques el cursor del mouse sobre un objeto aparecerá su descripción. La descripción del prim está limitada a 127 bytes, cualquier string más larga quedará recortada."/>
<text name="LabelCreatorTitle">
Creador:
</text>
diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml
index 86e454b83e..1bfab01108 100644
--- a/indra/newview/skins/default/xui/es/strings.xml
+++ b/indra/newview/skins/default/xui/es/strings.xml
@@ -5439,9 +5439,6 @@ Inténtalo incluyendo la ruta de acceso al editor entre comillas
<string name="Command_Grid_Status_Label">
Estado del Grid
</string>
- <string name="Command_HowTo_Label">
- Cómo
- </string>
<string name="Command_Inventory_Label">
Inventario
</string>
@@ -5538,9 +5535,6 @@ Inténtalo incluyendo la ruta de acceso al editor entre comillas
<string name="Command_Grid_Status_Tooltip">
Mostrar el estado actual del Grid
</string>
- <string name="Command_HowTo_Tooltip">
- Cómo hacer las tareas habituales
- </string>
<string name="Command_Inventory_Tooltip">
Ver y usar tus pertenencias
</string>
diff --git a/indra/newview/skins/default/xui/fr/floater_how_to.xml b/indra/newview/skins/default/xui/fr/floater_how_to.xml
deleted file mode 100644
index a414212ba0..0000000000
--- a/indra/newview/skins/default/xui/fr/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="AIDE RAPIDE"/>
diff --git a/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml
index a0d1197ea7..97644a5113 100644
--- a/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="SCRIPT : NOUVEAU SCRIPT">
<floater.string name="not_allowed">
- Ce script ne peut pas être copié, visualisé ou modifié. Pour visualiser ou modifier un script à l&apos;intérieur d&apos;un objet, vous devez avoir les droits requis.
+ Ce script ne peut pas être visualisé ou modifié, car il est défini comme « non copiable » ou « non modifiable ». Pour visualiser ou modifier un script, vous devez avoir ces permissions.
</floater.string>
<floater.string name="script_running">
Exécution en cours
diff --git a/indra/newview/skins/default/xui/fr/notifications.xml b/indra/newview/skins/default/xui/fr/notifications.xml
index 587c88faad..9c79f288c1 100644
--- a/indra/newview/skins/default/xui/fr/notifications.xml
+++ b/indra/newview/skins/default/xui/fr/notifications.xml
@@ -1109,7 +1109,6 @@ Vérifiez votre connexion Internet et réessayez dans quelques minutes, cliquez
Votre personnage va apparaître dans un moment.
Pour marcher, utilisez les flèches de direction.
-Appuyez sur F1 pour obtenir de l'aide ou en savoir plus sur [SECOND_LIFE].
Choisissez un avatar homme ou femme.
Vous pourrez revenir sur votre décision plus tard.
<usetemplate name="okcancelbuttons" notext="Femme" yestext="Homme"/>
diff --git a/indra/newview/skins/default/xui/fr/panel_preferences_setup.xml b/indra/newview/skins/default/xui/fr/panel_preferences_setup.xml
index c010fa9e90..60cbe93e6e 100644
--- a/indra/newview/skins/default/xui/fr/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/fr/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
Paramètres de proxy :
</text>
<button label="Définir" label_selected="Parcourir" name="set_proxy"/>
+ <text name="os_hibernation_mode">
+ Empêcher l’OS d’hiberner si vous n’êtes pas absent :
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Empêche seulement la mise en veille de l’OS si l’utilisateur n’est pas absent">
+ <combo_box.item label="Peut hiberner" name="can_hibernate"/>
+ <combo_box.item label="Ne peut pas hiberner, peut éteindre l’écran" name="cant_hibernate"/>
+ <combo_box.item label="Ne peut pas hiberner, ne peut pas éteindre l’écran" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/fr/panel_script_ed.xml b/indra/newview/skins/default/xui/fr/panel_script_ed.xml
index 0a33463cbb..f6bc55c189 100644
--- a/indra/newview/skins/default/xui/fr/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/fr/panel_script_ed.xml
@@ -4,7 +4,7 @@
Chargement...
</panel.string>
<panel.string name="can_not_view">
- Ce script ne peut pas être copié, visualisé ou modifié. Pour visualiser ou modifier un script à l&apos;intérieur d&apos;un objet, vous devez avoir les permissions requises.
+ Ce script ne peut pas être visualisé ou modifié, car il est défini comme « non copiable » ou « non modifiable ». Pour visualiser ou modifier un script, vous devez avoir ces permissions.
</panel.string>
<panel.string name="public_objects_can_not_run">
Les objets publics ne peuvent pas exécuter de scripts
diff --git a/indra/newview/skins/default/xui/fr/sidepanel_item_info.xml b/indra/newview/skins/default/xui/fr/sidepanel_item_info.xml
index caeb1f8589..47bd7d5949 100644
--- a/indra/newview/skins/default/xui/fr/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/fr/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
Description :
</text>
- <line_editor name="LabelItemDesc" tool_tip="Lorsque les utilisateurs sélectionnent &apos;Conseils de survol sur tous les objets&apos; dans les paramètres de la visionneuse, la description contextuelle de chaque objet apparaît sous le pointeur de la souris. La description de la prim est limitée à 127 octets, toute chaîne supérieure à cette limite sera tronquée."/>
+ <text_editor name="LabelItemDesc" tool_tip="Lorsque les utilisateurs sélectionnent &apos;Conseils de survol sur tous les objets&apos; dans les paramètres de la visionneuse, la description contextuelle de chaque objet apparaît sous le pointeur de la souris. La description de la prim est limitée à 127 octets, toute chaîne supérieure à cette limite sera tronquée."/>
<text name="LabelCreatorTitle">
Créateur :
</text>
diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml
index 4fb5167d67..4a7a337d4c 100644
--- a/indra/newview/skins/default/xui/fr/strings.xml
+++ b/indra/newview/skins/default/xui/fr/strings.xml
@@ -5538,9 +5538,6 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles
<string name="Command_Grid_Status_Label">
État de la grille
</string>
- <string name="Command_HowTo_Label">
- Aide rapide
- </string>
<string name="Command_Inventory_Label">
Inventaire
</string>
@@ -5637,9 +5634,6 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles
<string name="Command_Grid_Status_Tooltip">
Afficher l’état actuel de la grille
</string>
- <string name="Command_HowTo_Tooltip">
- Comment effectuer les opérations courantes
- </string>
<string name="Command_Inventory_Tooltip">
Afficher et utiliser vos possessions
</string>
diff --git a/indra/newview/skins/default/xui/it/floater_how_to.xml b/indra/newview/skins/default/xui/it/floater_how_to.xml
deleted file mode 100644
index 8f0e210571..0000000000
--- a/indra/newview/skins/default/xui/it/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="ISTRUZIONI"/>
diff --git a/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml
index c28df6c12e..84f668332e 100644
--- a/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="SCRIPT: NUOVO SCRIPT">
<floater.string name="not_allowed">
- Per impostazione, questo script non può essere visualizzato né modificato. Per visualizzare o modificare uno script all&apos;interno di un oggetto, devi avere i necessari diritti di modifica.
+ Questo script non può essere visualizzato né modificato, perché è impostato come &quot;non copiabile&quot; o &quot;non modificabile&quot;. Per visualizzare o modificare uno script, devi avere questi permessi.
</floater.string>
<floater.string name="script_running">
In esecuzione
diff --git a/indra/newview/skins/default/xui/it/notifications.xml b/indra/newview/skins/default/xui/it/notifications.xml
index f79cc1515b..284f947b18 100644
--- a/indra/newview/skins/default/xui/it/notifications.xml
+++ b/indra/newview/skins/default/xui/it/notifications.xml
@@ -1115,7 +1115,6 @@ Controlla la tua connessione Internet e riprova fra qualche minuto, oppure clicc
Il tuo avatar apparirà fra un attimo.
Usa le frecce per muoverti.
-Premi F1 in qualunque momento per la guida o per apprendere altre cose di [SECOND_LIFE].
Scegli un avatar maschile o femminile. Puoi sempre cambiare idea più tardi.
<usetemplate name="okcancelbuttons" notext="Femminile" yestext="Maschile"/>
</notification>
diff --git a/indra/newview/skins/default/xui/it/panel_preferences_setup.xml b/indra/newview/skins/default/xui/it/panel_preferences_setup.xml
index c9d90539e1..95ecf05617 100644
--- a/indra/newview/skins/default/xui/it/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/it/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
Impostazioni proxy:
</text>
<button label="Regola impostazioni proxy" label_selected="Sfoglia" name="set_proxy" width="160"/>
+ <text name="os_hibernation_mode">
+ Impedisci al sistema operativo di ibernarsi se non sei Assente:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Impedisce al sistema operativo di andare in sospensione solo se l'utente non è assente">
+ <combo_box.item label="Può ibernarsi" name="can_hibernate"/>
+ <combo_box.item label="Non può ibernarsi, può spegnere lo schermo" name="cant_hibernate"/>
+ <combo_box.item label="Non può ibernarsi, non può spegnere lo schermo" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/it/panel_script_ed.xml b/indra/newview/skins/default/xui/it/panel_script_ed.xml
index 3cbdadbac8..72e9b45321 100644
--- a/indra/newview/skins/default/xui/it/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/it/panel_script_ed.xml
@@ -4,7 +4,7 @@
Caricamento in corso...
</panel.string>
<panel.string name="can_not_view">
- Per impostazione, questo script non può essere visualizzato né modificato. Per visualizzare o modificare uno script all&apos;interno di un oggetto, devi avere i necessari diritti di modifica.
+ Questo script non può essere visualizzato né modificato, perché è impostato come &quot;non copiabile&quot; o &quot;non modificabile&quot;. Per visualizzare o modificare uno script, devi avere questi permessi.
</panel.string>
<panel.string name="public_objects_can_not_run">
Gli oggetti pubblici non possono eseguire gli script
diff --git a/indra/newview/skins/default/xui/it/sidepanel_item_info.xml b/indra/newview/skins/default/xui/it/sidepanel_item_info.xml
index 9d659bb50d..2941b1ba04 100644
--- a/indra/newview/skins/default/xui/it/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/it/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
Descrizione:
</text>
- <line_editor name="LabelItemDesc" tool_tip="Quando la dicitura &quot;Vedi suggerimenti su tutti gli oggetti&quot; è selezionata nelle impostazioni del Viewer, passando il mouse sopra un oggetto si vedrà la sua descrizione. La descrizione dei Prim è limitata a 127 bytes. Se più lunga, verrá troncata."/>
+ <text_editor name="LabelItemDesc" tool_tip="Quando la dicitura &quot;Vedi suggerimenti su tutti gli oggetti&quot; è selezionata nelle impostazioni del Viewer, passando il mouse sopra un oggetto si vedrà la sua descrizione. La descrizione dei Prim è limitata a 127 bytes. Se più lunga, verrá troncata."/>
<text name="LabelCreatorTitle">
Ideatore:
</text>
diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml
index 2ddb7d77d1..b4d6b2a315 100644
--- a/indra/newview/skins/default/xui/it/strings.xml
+++ b/indra/newview/skins/default/xui/it/strings.xml
@@ -5453,9 +5453,6 @@ Prova a racchiudere il percorso dell'editor in doppie virgolette.
<string name="Command_Grid_Status_Label">
Stato della griglia
</string>
- <string name="Command_HowTo_Label">
- Istruzioni
- </string>
<string name="Command_Inventory_Label">
Inventario
</string>
@@ -5552,9 +5549,6 @@ Prova a racchiudere il percorso dell'editor in doppie virgolette.
<string name="Command_Grid_Status_Tooltip">
Mostra stato griglia corrente
</string>
- <string name="Command_HowTo_Tooltip">
- Come eseguire le attività più comuni
- </string>
<string name="Command_Inventory_Tooltip">
Visualizza e usa le tue cose
</string>
diff --git a/indra/newview/skins/default/xui/ja/floater_how_to.xml b/indra/newview/skins/default/xui/ja/floater_how_to.xml
deleted file mode 100644
index 480fb4649c..0000000000
--- a/indra/newview/skins/default/xui/ja/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="ウェルカムアイランドガイドブック" filename="floater_web_content.xml"/> \ No newline at end of file
diff --git a/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml
index 770d7b4a13..0813922635 100644
--- a/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="スクリプト:新しいスクリプト">
<floater.string name="not_allowed">
- このスクリプトは「複製不可」のため、表示・編集することができません。オブジェクト内のスクリプトの表示・編集には、全権限が必要です。
+ このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。スクリプトを表示・編集するには、これらの権限が必要です。
</floater.string>
<floater.string name="script_running">
実行中
diff --git a/indra/newview/skins/default/xui/ja/notifications.xml b/indra/newview/skins/default/xui/ja/notifications.xml
index 9ec7a0de98..07d6c41213 100644
--- a/indra/newview/skins/default/xui/ja/notifications.xml
+++ b/indra/newview/skins/default/xui/ja/notifications.xml
@@ -2162,7 +2162,6 @@ https://wiki.secondlife.com/wiki/Adding_Spelling_Dictionaries
</form>
</notification>
<notification name="WelcomeChooseSex">まもなく、あなたのアバターが表示されます。矢印キーを使用して歩きます。
-ヘルプが必要なときや、[SECOND_LIFE]について知りたいときは、F1キーを押してください。
男性あるいは女性のアバターを選択してください。この設定は後で変更できます。
<tag>
confirm
diff --git a/indra/newview/skins/default/xui/ja/panel_preferences_setup.xml b/indra/newview/skins/default/xui/ja/panel_preferences_setup.xml
index 959f827a61..8940ca5bc9 100644
--- a/indra/newview/skins/default/xui/ja/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/ja/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
プロキシ設定:
</text>
<button label="プロキシ設定を調整" label_selected="参照" name="set_proxy"/>
+ <text name="os_hibernation_mode">
+ 離席中でない場合、OS の休止状態を防止:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="ユーザーが離席中でない場合にのみ、OS のスリープを防止します">
+ <combo_box.item label="休止状態を許可" name="can_hibernate"/>
+ <combo_box.item label="休止状態を禁止(画面オフは可)" name="cant_hibernate"/>
+ <combo_box.item label="休止状態を禁止(画面オフも不可)" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/ja/panel_script_ed.xml b/indra/newview/skins/default/xui/ja/panel_script_ed.xml
index 0861b5caf1..5cf04c7537 100644
--- a/indra/newview/skins/default/xui/ja/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/ja/panel_script_ed.xml
@@ -4,7 +4,7 @@
読み込んでいます…
</panel.string>
<panel.string name="can_not_view">
- このスクリプトは「コピー不可」のため、表示・編集することができません。オブジェクト内のスクリプトの表示・編集には、全権限が必要です。
+ このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。スクリプトを表示・編集するには、これらの権限が必要です。
</panel.string>
<panel.string name="public_objects_can_not_run">
公共オブジェクトで、スクリプトを実行することはできません。
diff --git a/indra/newview/skins/default/xui/ja/sidepanel_item_info.xml b/indra/newview/skins/default/xui/ja/sidepanel_item_info.xml
index 520ef848a0..b1ac5fd460 100644
--- a/indra/newview/skins/default/xui/ja/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/ja/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
説明:
</text>
- <line_editor name="LabelItemDesc" tool_tip="ビューアの設定で[すべてのオブジェクト上でヒントを表示]が選択されている場合、オブジェクトにマウスのポインタをかざすとそのオブジェクトの説明がポップアップ表示されます。プリムの説明は最大127バイトまでに制限されているため、それよりも長い文字列は切り捨てられます。"/>
+ <text_editor name="LabelItemDesc" tool_tip="ビューアの設定で[すべてのオブジェクト上でヒントを表示]が選択されている場合、オブジェクトにマウスのポインタをかざすとそのオブジェクトの説明がポップアップ表示されます。プリムの説明は最大127バイトまでに制限されているため、それよりも長い文字列は切り捨てられます。"/>
<text name="LabelCreatorTitle">
制作者:
</text>
diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml
index 7d618bc337..a67e58a42a 100644
--- a/indra/newview/skins/default/xui/ja/strings.xml
+++ b/indra/newview/skins/default/xui/ja/strings.xml
@@ -7143,9 +7143,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだã
<string name="Command_Grid_Status_Label">
グリッド状況
</string>
- <string name="Command_HowTo_Label">
- ハウツー
- </string>
<string name="Command_Inventory_Label">
インベントリ
</string>
@@ -7233,9 +7230,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだã
<string name="Command_Grid_Status_Tooltip">
現在のグリッドステータスを表示します。
</string>
- <string name="Command_HowTo_Tooltip">
- 一般的タスクの実行方法を表示します。
- </string>
<string name="Command_Inventory_Tooltip">
インベントリの内容を表示したり使用したりします。
</string>
diff --git a/indra/newview/skins/default/xui/pl/floater_how_to.xml b/indra/newview/skins/default/xui/pl/floater_how_to.xml
deleted file mode 100644
index 2c412de30a..0000000000
--- a/indra/newview/skins/default/xui/pl/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
-<floater name="floater_how_to" title="SAMOUCZEK" />
diff --git a/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml
index 1b3e0d0e74..0c59d90468 100644
--- a/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<floater name="script ed float" title="SKRYPT: NOWY SKRYPT">
<floater.string name="not_allowed">
- Nie posiadasz praw do podejrzenia lub edycji kodu tego skryptu, ponieważ ma on ustawione ograniczone zezwolenia. Musisz posiadać pełne prawa by móc zobaczyć lub edytować kod skryptu wewnątrz obiektu.
+ Nie możesz zobaczyć ani edytować tego skryptu, ponieważ ustawiono dla niego &quot;brak kopiowania&quot; lub &quot;brak modyfikacji&quot;. Aby zobaczyć lub edytować skrypt, potrzebujesz tych uprawnień.
</floater.string>
<floater.string name="script_running">
Włączony
diff --git a/indra/newview/skins/default/xui/pl/notifications.xml b/indra/newview/skins/default/xui/pl/notifications.xml
index ad9d2ecf1d..a8ea3c7c1a 100644
--- a/indra/newview/skins/default/xui/pl/notifications.xml
+++ b/indra/newview/skins/default/xui/pl/notifications.xml
@@ -1397,7 +1397,6 @@ Możesz sprawdzić swoje połączenie z Internetem i spróbować ponownie za kil
Twoja postać pojawi się za moment.
Używaj strzałek żeby się poruszać.
-Naciśnij F1 w dowolnej chwili po pomoc albo żeby dowiedzieć się więcej o [SECOND_LIFE].
Wybierz awatara właściwej płci. Ten wybór będzie można później zmienić.
<usetemplate name="okcancelbuttons" notext="Kobieta" yestext="Mężczyzna" />
</notification>
diff --git a/indra/newview/skins/default/xui/pl/panel_preferences_setup.xml b/indra/newview/skins/default/xui/pl/panel_preferences_setup.xml
index bd36f10509..7147f024cb 100644
--- a/indra/newview/skins/default/xui/pl/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/pl/panel_preferences_setup.xml
@@ -33,4 +33,12 @@
Ustawienia proxy:
</text>
<button label="Dostosuj ustawienia" label_selected="Przeglądaj" name="set_proxy" />
+ <text name="os_hibernation_mode">
+ Zapobiegaj hibernacji systemu, gdy użytkownik nie jest AFK:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Zapobiega usypianiu systemu tylko wtedy, gdy użytkownik nie jest AFK">
+ <combo_box.item label="Może hibernować" name="can_hibernate"/>
+ <combo_box.item label="Nie może hibernować, może wyłączyć ekran" name="cant_hibernate"/>
+ <combo_box.item label="Nie może hibernować, nie może wyłączyć ekranu" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/pl/panel_script_ed.xml b/indra/newview/skins/default/xui/pl/panel_script_ed.xml
index 828f1c571f..01b88613a0 100644
--- a/indra/newview/skins/default/xui/pl/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/pl/panel_script_ed.xml
@@ -4,7 +4,7 @@
Ładowanie...
</panel.string>
<panel.string name="can_not_view">
- Nie posiadasz praw do zobaczenia lub edycji kodu tego skryptu, ponieważ ustawione zostały na niego ograniczenia. Musisz posiadać pełne prawa by móc zobaczyć lub edytować kod skryptu w zawartości obiektu.
+ Nie możesz zobaczyć ani edytować tego skryptu, ponieważ ustawiono dla niego &quot;brak kopiowania&quot; lub &quot;brak modyfikacji&quot;. Aby zobaczyć lub edytować skrypt, potrzebujesz tych uprawnień.
</panel.string>
<panel.string name="public_objects_can_not_run">
Publiczne obiekty nie mogą uruchamiać skryptów
diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml
index 7a61878618..dc7ca53aaa 100644
--- a/indra/newview/skins/default/xui/pl/strings.xml
+++ b/indra/newview/skins/default/xui/pl/strings.xml
@@ -4992,9 +4992,6 @@ Spróbuj załączyć ścieżkę do edytora w cytowaniu.
<string name="Command_Grid_Status_Label">
Status świata
</string>
- <string name="Command_HowTo_Label">
- Samouczek
- </string>
<string name="Command_Inventory_Label">
Szafa
</string>
@@ -5076,9 +5073,6 @@ Spróbuj załączyć ścieżkę do edytora w cytowaniu.
<string name="Command_Grid_Status_Tooltip">
Pokaż obecny status świata
</string>
- <string name="Command_HowTo_Tooltip">
- Jak wykonywać zwyczajne rzeczy
- </string>
<string name="Command_Inventory_Tooltip">
Przeglądaj i używaj rzeczy, jakie należą do Ciebie
</string>
diff --git a/indra/newview/skins/default/xui/pt/floater_how_to.xml b/indra/newview/skins/default/xui/pt/floater_how_to.xml
deleted file mode 100644
index 15c4946cb0..0000000000
--- a/indra/newview/skins/default/xui/pt/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="COMO"/>
diff --git a/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml
index 635bc2d34b..a55dd1b406 100644
--- a/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="SCRIPT: NOVO SCRIPT">
<floater.string name="not_allowed">
- Você não pode ver ou editar o script, uma vez que está definido como &quot;não&quot; copiar. Você precisa de permissões para ver ou editar um script dentro de um objeto.
+ Você não pode ver ou editar este script, pois ele está definido como &quot;não copiável&quot; ou &quot;não modificável&quot;. Você precisa dessas permissões para ver ou editar um script.
</floater.string>
<floater.string name="script_running">
Executando
diff --git a/indra/newview/skins/default/xui/pt/floater_settings_debug.xml b/indra/newview/skins/default/xui/pt/floater_settings_debug.xml
index 83f7b24572..eefd407a3c 100644
--- a/indra/newview/skins/default/xui/pt/floater_settings_debug.xml
+++ b/indra/newview/skins/default/xui/pt/floater_settings_debug.xml
@@ -1,8 +1,13 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="settings_debug" title="DEBUG SETTINGS">
+<floater name="settings_debug" title="CONFIGURAÇÕES DE DEPURAÇÃO">
+ <filter_editor label="Digite o texto de busca" name="filter_input"/>
+ <scroll_list name="setting_list">
+ <scroll_list.columns label="Configuração" name="setting"/>
+ </scroll_list>
+ <text name="setting_name_txt">Nome da configuração de depuração</text>
<radio_group name="boolean_combo">
- <radio_item label="TRUE" name="TRUE" />
- <radio_item label="FALSE" name="FALSE" />
+ <radio_item label="VERDADEIRO" name="TRUE"/>
+ <radio_item label="FALSO" name="FALSE"/>
</radio_group>
<color_swatch label="Cor" name="val_color_swatch"/>
<spinner label="x" name="val_spinner_1"/>
@@ -10,4 +15,5 @@
<spinner label="x" name="val_spinner_3"/>
<spinner label="x" name="val_spinner_4"/>
<button label="Restaurar padrão" name="default_btn"/>
+ <check_box label="Mostrar apenas configurações alteradas" name="hide_default"/>
</floater>
diff --git a/indra/newview/skins/default/xui/pt/notifications.xml b/indra/newview/skins/default/xui/pt/notifications.xml
index a3220bca54..153109fc1b 100644
--- a/indra/newview/skins/default/xui/pt/notifications.xml
+++ b/indra/newview/skins/default/xui/pt/notifications.xml
@@ -1108,7 +1108,6 @@ Cheque sua conexão e tente em alguns minutos, clique na Ajuda para acessar o [S
Seu personagem irá aparecer num momento.
Use as teclas de seta para andar.
-Pressione a tecla F1 para ajuda ou aprender mais sobre [SECOND_LIFE].
Por favor, escolha se o seu avatar é feminino ou masculino. Você pode mudar de idéia depois.
<usetemplate name="okcancelbuttons" notext="Feminino" yestext="Masculino"/>
</notification>
diff --git a/indra/newview/skins/default/xui/pt/panel_preferences_setup.xml b/indra/newview/skins/default/xui/pt/panel_preferences_setup.xml
index 56fc225bc0..101f58488d 100644
--- a/indra/newview/skins/default/xui/pt/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/pt/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
Configurações de proxy:
</text>
<button label="Ajustar configurações de proxy" label_selected="Procurar" name="set_proxy" width="180"/>
+ <text name="os_hibernation_mode">
+ Impedir que o sistema entre em hibernação se não estiver Ausente:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Só impede que o sistema entre em suspensão se o utilizador não estiver ausente">
+ <combo_box.item label="Pode hibernar" name="can_hibernate"/>
+ <combo_box.item label="Não pode hibernar, pode desligar o ecrã" name="cant_hibernate"/>
+ <combo_box.item label="Não pode hibernar, não pode desligar o ecrã" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/pt/panel_script_ed.xml b/indra/newview/skins/default/xui/pt/panel_script_ed.xml
index c546adcf8b..8d33035d02 100644
--- a/indra/newview/skins/default/xui/pt/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/pt/panel_script_ed.xml
@@ -4,7 +4,7 @@
Carregando...
</panel.string>
<panel.string name="can_not_view">
- Você não pode ver ou editar o script, uma vez que está definido como &quot;não&quot; copiar &quot;. Você precisa de permissões para ver ou editar um script dentro de um objeto.
+ Você não pode ver ou editar este script, pois ele está definido como &quot;não copiável&quot; ou &quot;não modificável&quot;. Você precisa dessas permissões para ver ou editar um script.
</panel.string>
<panel.string name="public_objects_can_not_run">
Objetos públicos não podem executar scripts
diff --git a/indra/newview/skins/default/xui/pt/settings_comments.xml b/indra/newview/skins/default/xui/pt/settings_comments.xml
new file mode 100644
index 0000000000..22f6487847
--- /dev/null
+++ b/indra/newview/skins/default/xui/pt/settings_comments.xml
@@ -0,0 +1,3019 @@
+<?xml version="1.0" encoding="utf-8"?>
+<llsd>
+<map>
+ <key>360CaptureCameraFOV</key>
+ <string>Campo de visão da câmera WebGL que converte o cubemap em uma imagem equirretangular.</string>
+ <key>360CaptureDebugSaveImage</key>
+ <string>Se ativado, salva cada cube map como imagem, bem como a URL de dados JavaScript, para fins de depuração.</string>
+ <key>360CaptureHideAvatars</key>
+ <string>Se ativado, remove todos os avatares da captura 360.</string>
+ <key>360CaptureJPEGEncodeQuality</key>
+ <string>Valor de qualidade usado no codificador JPEG (0..100).</string>
+ <key>360CaptureNumRenderPasses</key>
+ <string>Número de vezes que a cena é renderizada ao tirar uma captura.</string>
+ <key>360CaptureOutputImageWidth</key>
+ <string>Largura da imagem 360 equirretangular de saída.</string>
+ <key>AFKTimeout</key>
+ <string>Tempo antes de definir automaticamente o modo AFK (ausente do teclado) em segundos (0=nunca). Valores válidos: 0, 120, 300, 600, 1800.</string>
+ <key>AbuseReportScreenshotDelay</key>
+ <string>Atraso antes de tirar a captura de tela para evitar artefatos de interface.</string>
+ <key>AckCollectTime</key>
+ <string>Tempo de coleta e agrupamento de mensagens de confirmação (ack).</string>
+ <key>ActiveFloaterTransparency</key>
+ <string>Transparência de floaters ativos (floaters com foco).</string>
+ <key>AdminMenu</key>
+ <string>Ativa o menu de administrador de depuração no menu principal. Nota: apenas permite exibir o menu; não concede privilégios de administrador.</string>
+ <key>AdvanceSnapshot</key>
+ <string>Exibir configurações de parâmetros avançados na interface de captura.</string>
+ <key>AgentPause</key>
+ <string>Solicitar ao simulador que pare de atualizar o agente enquanto ativado.</string>
+ <key>AlertedUnsupportedHardware</key>
+ <string>Define se há hardware não suportado e já foi exibida uma notificação.</string>
+ <key>AllowMultipleViewers</key>
+ <string>Permitir múltiplos viewers.</string>
+ <key>AllowSelectAvatar</key>
+ <string>Permite ao usuário selecionar e mover avatares; o movimento é local ao viewer, não se propaga ao servidor e também suprime atualizações de posição do avatar enquanto avatares estão selecionados.</string>
+ <key>AllowSelfImpostor</key>
+ <string>Permite que seu próprio tempo de renderização transforme seu avatar em impostor.</string>
+ <key>AllowTapTapHoldRun</key>
+ <string>Tocar duas vezes em uma tecla de direção e mantê-la pressionada faz o avatar correr.</string>
+ <key>AnalyzePerformance</key>
+ <string>Solicitar análise de desempenho para uma execução específica do viewer.</string>
+ <key>AnimateTextures</key>
+ <string>Ativar animação de texturas (depuração).</string>
+ <key>AnimatedObjectsAllowLeftClick</key>
+ <string>Permite interação com clique esquerdo em objetos animados. Impacto de desempenho incerto.</string>
+ <key>AnimatedObjectsMaxLegalOffset</key>
+ <string>Deslocamento visual máximo entre a posição do objeto e a posição renderizada.</string>
+ <key>AnimatedObjectsMaxLegalSize</key>
+ <string>Tamanho máximo da caixa delimitadora para a posição renderizada de objetos animados.</string>
+ <key>AnimationUploadFolder</key>
+ <string>Todos os uploads de animações serão armazenados neste diretório (UUID).</string>
+ <key>AppearanceCameraMovement</key>
+ <string>Ao entrar no modo de edição de aparência, a câmera aproxima a parte do avatar selecionada no momento.</string>
+ <key>ApplyColorImmediately</key>
+ <string>Pré-visualizar seleções no seletor de cores imediatamente.</string>
+ <key>ArrowKeysAlwaysMove</key>
+ <string>Enquanto o cursor está na caixa de entrada de chat, as setas continuam controlando seu avatar.</string>
+ <key>AuctionShowFence</key>
+ <string>Ao leiloar terreno, incluir o marcador de limite da parcela na captura.</string>
+ <key>AudioLevelAmbient</key>
+ <string>Nível de áudio dos sons do ambiente.</string>
+ <key>AudioLevelMaster</key>
+ <string>Nível de áudio mestre, ou volume geral.</string>
+ <key>AudioLevelMedia</key>
+ <string>Nível de áudio de filmes Quicktime.</string>
+ <key>AudioLevelMic</key>
+ <string>Nível de áudio da entrada de microfone.</string>
+ <key>AudioLevelMusic</key>
+ <string>Nível de áudio de música em streaming.</string>
+ <key>AudioLevelSFX</key>
+ <string>Nível de áudio dos efeitos sonoros no mundo.</string>
+ <key>AudioLevelUI</key>
+ <string>Nível de áudio dos efeitos sonoros da interface.</string>
+ <key>AudioLevelVoice</key>
+ <string>Nível de áudio do chat de voz.</string>
+ <key>AudioLevelWind</key>
+ <string>Nível de áudio do ruído do vento ao ficar parado.</string>
+ <key>AudioStreamingMedia</key>
+ <string>Ativar streaming.</string>
+ <key>AudioStreamingMusic</key>
+ <string>Ativar áudio em streaming.</string>
+ <key>AutoAcceptNewInventory</key>
+ <string>Aceitar automaticamente novos notecards/texturas/landmarks.</string>
+ <key>AutoDisengageMic</key>
+ <string>Desligar o microfone automaticamente ao encerrar chamadas de IM.</string>
+ <key>AutoLeveling</key>
+ <string>Manter a Flycam nivelada.</string>
+ <key>AutoLogin</key>
+ <string>Fazer login automaticamente usando a última combinação de usuário/senha.</string>
+ <key>AutoMimeDiscovery</key>
+ <string>Ativar a descoberta de tipo MIME de URLs de mídia pelo viewer.</string>
+ <key>AutoPilotLocksCamera</key>
+ <string>Manter a posição da câmera travada enquanto o avatar caminha até a posição selecionada.</string>
+ <key>AutoReplace</key>
+ <string>Substitui palavras-chave por uma palavra ou frase configurada.</string>
+ <key>AutoSnapshot</key>
+ <string>Atualizar a captura quando a câmera para de se mover, ou quando qualquer parâmetro muda.</string>
+ <key>AutoTuneFPS</key>
+ <string>Permitir que o viewer ajuste suas configurações para alcançar o FPS desejado.</string>
+ <key>AutoTuneImpostorByDistEnabled</key>
+ <string>Ativar/desativar o uso de MaxNonImpostor para limitar a renderização de avatares por distância.</string>
+ <key>AutoTuneImpostorFarAwayDistance</key>
+ <string>Avatares além deste alcance serão otimizados automaticamente.</string>
+ <key>AutoTuneLock</key>
+ <string>Quando ativado, o viewer mudará dinamicamente as configurações até que o ajuste automático seja explicitamente desativado.</string>
+ <key>AutoTuneRenderFarClipMin</key>
+ <string>A menor distância de desenho que o ajuste automático pode usar.</string>
+ <key>AutoTuneRenderFarClipTarget</key>
+ <string>A distância de desenho que o ajuste automático tentará alcançar.</string>
+ <key>AutomaticFly</key>
+ <string>Voar segurando a tecla de pulo ou usando o comando "Voar" (FALSE = voar apenas pelo comando "Voar").</string>
+ <key>AvatarAxisDeadZone0</key>
+ <string>Zona morta do eixo Z (movimento vertical) do avatar.</string>
+ <key>AvatarAxisDeadZone1</key>
+ <string>Zona morta do eixo X (movimento lateral) do avatar.</string>
+ <key>AvatarAxisDeadZone2</key>
+ <string>Zona morta do eixo Y (movimento para frente/trás) do avatar.</string>
+ <key>AvatarAxisDeadZone3</key>
+ <string>Zona morta da rolagem (roll) do avatar.</string>
+ <key>AvatarAxisDeadZone4</key>
+ <string>Zona morta da elevação (pitch) do avatar.</string>
+ <key>AvatarAxisDeadZone5</key>
+ <string>Zona morta da guinada (yaw) do avatar.</string>
+ <key>AvatarAxisScale0</key>
+ <string>Fator de escala do eixo Z (movimento vertical) do avatar.</string>
+ <key>AvatarAxisScale1</key>
+ <string>Fator de escala do eixo X (movimento lateral) do avatar.</string>
+ <key>AvatarAxisScale2</key>
+ <string>Fator de escala do eixo Y (movimento para frente/trás) do avatar.</string>
+ <key>AvatarAxisScale3</key>
+ <string>Fator de escala da rolagem (roll) do avatar.</string>
+ <key>AvatarAxisScale4</key>
+ <string>Fator de escala da elevação (pitch) do avatar.</string>
+ <key>AvatarAxisScale5</key>
+ <string>Fator de escala da guinada (yaw) do avatar.</string>
+ <key>AvatarBakedTextureUploadTimeout</key>
+ <string>Especifica o tempo máximo em segundos a aguardar antes de enviar suas texturas assadas para a aparência do avatar. Defina 0 para desativar e aguardar até que todas as texturas assadas estejam na resolução máxima.</string>
+ <key>AvatarExtentRefreshMaxPerBatch</key>
+ <string>Quantos avatares queremos tratar no total por lote (padrão é 5).</string>
+ <key>AvatarExtentRefreshPeriodBatch</key>
+ <string>Em quantos quadros distribuímos por padrão ao atualizar extensões (padrão é 4).</string>
+ <key>AvatarFeathering</key>
+ <string>Suavização do avatar (menos é mais suave).</string>
+ <key>AvatarHoverOffsetZ</key>
+ <string>Ajuste final da posição Z do avatar após todas as outras correções.</string>
+ <key>AvatarNameTagMode</key>
+ <string>Selecionar o modo da etiqueta de nome do avatar.</string>
+ <key>AvatarPhysics</key>
+ <string>Ativar física do avatar.</string>
+ <key>AvatarRotateThresholdFast</key>
+ <string>Ângulo entre a direção do avatar e a da câmera no qual o avatar gira para a mesma direção da câmera, ao se mover rápido (graus).</string>
+ <key>AvatarRotateThresholdSlow</key>
+ <string>Ângulo entre a direção do avatar e a da câmera no qual o avatar gira para a mesma direção da câmera, ao se mover devagar (graus).</string>
+ <key>AvatarSex</key>
+ <string>Sexo do avatar usado na interface de edição de aparência (0 = feminino, 1 = masculino).</string>
+ <key>AvatarSitRotation</key>
+ <string>Rotação real do avatar sentado usada na predefinição.</string>
+ <key>AvatarWelcomePack</key>
+ <string>Conteúdo do pacote de boas-vindas do avatar.</string>
+ <key>AzureTranslateAPIKey</key>
+ <string>Dados do serviço de tradução Azure para uso com a API do MS Azure Translator.</string>
+ <key>BackgroundYieldTime</key>
+ <string>Tempo a ceder a cada quadro para outros aplicativos quando o SL não é a janela em primeiro plano (milissegundos).</string>
+ <key>BasicUITooltips</key>
+ <string>Mostrar dicas para vários elementos de interface 2D como botões ou caixas de seleção; não suprime dicas de arrastar e soltar, no mundo, links ou mídia.</string>
+ <key>BatchSizeAIS3</key>
+ <string>Quantidade de pastas que o AIS empacota por requisição de subconjunto de categoria.</string>
+ <key>BlockAvatarAppearanceMessages</key>
+ <string>Ignora mensagens de aparência (para simular a Ruth).</string>
+ <key>BlockPeopleSortOrder</key>
+ <string>Especifica a ordem de classificação para pessoas recentes (0 = por nome, 1 = por tipo).</string>
+ <key>BrowserEnableJSObject</key>
+ <string>(AVISO: recurso avançado. Use se estiver ciente das implicações). Ativar ou desativar o objeto de ponte entre o viewer e o Javascript.</string>
+ <key>BrowserFileAccessFromFileUrls</key>
+ <string>Permitir acesso a arquivos locais via URLs de arquivo no navegador embutido.</string>
+ <key>BrowserIgnoreSSLCertErrors</key>
+ <string>APENAS PARA TESTES: dizer ao navegador web embutido para ignorar erros de certificado SSL.</string>
+ <key>BrowserJavascriptEnabled</key>
+ <string>Ativar Javascript no navegador web embutido?</string>
+ <key>BrowserPluginsEnabled</key>
+ <string>Ativar plugins web no navegador web embutido?</string>
+ <key>BrowserProxyAddress</key>
+ <string>Endereço para o Proxy Web.</string>
+ <key>BrowserProxyEnabled</key>
+ <string>Usar Proxy Web.</string>
+ <key>BrowserProxyPort</key>
+ <string>Porta para o Proxy Web.</string>
+ <key>BuildAxisDeadZone0</key>
+ <string>Zona morta do eixo Z (movimento vertical) de construção.</string>
+ <key>BuildAxisDeadZone1</key>
+ <string>Zona morta do eixo X (movimento lateral) de construção.</string>
+ <key>BuildAxisDeadZone2</key>
+ <string>Zona morta do eixo Y (movimento para frente/trás) de construção.</string>
+ <key>BuildAxisDeadZone3</key>
+ <string>Zona morta da rolagem (roll) de construção.</string>
+ <key>BuildAxisDeadZone4</key>
+ <string>Zona morta da elevação (pitch) de construção.</string>
+ <key>BuildAxisDeadZone5</key>
+ <string>Zona morta da guinada (yaw) de construção.</string>
+ <key>BuildAxisScale0</key>
+ <string>Fator de escala do eixo Z (movimento vertical) de construção.</string>
+ <key>BuildAxisScale1</key>
+ <string>Fator de escala do eixo X (movimento lateral) de construção.</string>
+ <key>BuildAxisScale2</key>
+ <string>Fator de escala do eixo Y (movimento para frente/trás) de construção.</string>
+ <key>BuildAxisScale3</key>
+ <string>Fator de escala da rolagem (roll) de construção.</string>
+ <key>BuildAxisScale4</key>
+ <string>Fator de escala da elevação (pitch) de construção.</string>
+ <key>BuildAxisScale5</key>
+ <string>Fator de escala da guinada (yaw) de construção.</string>
+ <key>BuildFeathering</key>
+ <string>Suavização de construção (menos é mais suave).</string>
+ <key>BulkChangeEveryoneCopy</key>
+ <string>Objetos alterados em massa podem ser copiados por todos.</string>
+ <key>BulkChangeIncludeAnimations</key>
+ <string>Alterações de permissão em massa afetam animações.</string>
+ <key>BulkChangeIncludeBodyParts</key>
+ <string>Alterações de permissão em massa afetam partes do corpo.</string>
+ <key>BulkChangeIncludeClothing</key>
+ <string>Alterações de permissão em massa afetam roupas.</string>
+ <key>BulkChangeIncludeGestures</key>
+ <string>Alterações de permissão em massa afetam gestos.</string>
+ <key>BulkChangeIncludeMaterials</key>
+ <string>Alterações de permissão em massa afetam materiais.</string>
+ <key>BulkChangeIncludeNotecards</key>
+ <string>Alterações de permissão em massa afetam notecards.</string>
+ <key>BulkChangeIncludeObjects</key>
+ <string>Alterações de permissão em massa afetam objetos.</string>
+ <key>BulkChangeIncludeScripts</key>
+ <string>Alterações de permissão em massa afetam scripts.</string>
+ <key>BulkChangeIncludeSettings</key>
+ <string>Alterações de permissão em massa afetam configurações de ambiente.</string>
+ <key>BulkChangeIncludeSounds</key>
+ <string>Alterações de permissão em massa afetam sons.</string>
+ <key>BulkChangeIncludeTextures</key>
+ <string>Alterações de permissão em massa afetam texturas.</string>
+ <key>BulkChangeNextOwnerCopy</key>
+ <string>Objetos alterados em massa podem ser copiados pelo próximo proprietário.</string>
+ <key>BulkChangeNextOwnerModify</key>
+ <string>Objetos alterados em massa podem ser modificados pelo próximo proprietário.</string>
+ <key>BulkChangeNextOwnerTransfer</key>
+ <string>Objetos alterados em massa podem ser revendidos ou doados pelo próximo proprietário.</string>
+ <key>BulkChangeShareWithGroup</key>
+ <string>Objetos alterados em massa são compartilhados com o grupo ativo no momento.</string>
+ <key>BulkUpload2KTextures</key>
+ <string>Upload em massa redimensiona texturas para 2K se verdadeiro, ou para 1K se falso.</string>
+ <key>CacheLocation</key>
+ <string>Controla a localização do cache em disco local.</string>
+ <key>CacheLocationTopFolder</key>
+ <string>Controla a pasta raiz do cache em disco local.</string>
+ <key>CacheSize</key>
+ <string>Controla a quantidade de espaço em disco reservada para cache local de arquivos, em MB.</string>
+ <key>CacheValidateCounter</key>
+ <string>Usado para distribuir a validação do cache.</string>
+ <key>CallLogSortOrder</key>
+ <string>Especifica a ordem de classificação do registro de chamadas (0 = por nome, 1 = por data).</string>
+ <key>CameraAngle</key>
+ <string>Ângulo do campo de visão da câmera (radianos).</string>
+ <key>CameraDoFResScale</key>
+ <string>Quanto reduzir a resolução da profundidade de campo. Faixa válida de 0,25 (um quarto da resolução) a 1,0 (resolução total).</string>
+ <key>CameraFNumber</key>
+ <string>Valor do número f da câmera para o efeito de profundidade de campo.</string>
+ <key>CameraFieldOfView</key>
+ <string>Campo de visão vertical da câmera para o efeito de profundidade de campo (em graus).</string>
+ <key>CameraFocalLength</key>
+ <string>Distância focal da câmera para o efeito de profundidade de campo (em milímetros).</string>
+ <key>CameraFocusTransitionTime</key>
+ <string>Quantos segundos a câmera leva para transitar entre distâncias focais.</string>
+ <key>CameraMaxCoF</key>
+ <string>Círculo de confusão máximo da câmera para o efeito de profundidade de campo.</string>
+ <key>CameraMouseWheelZoom</key>
+ <string>A câmera aproxima e afasta com a roda do mouse.</string>
+ <key>CameraOffset</key>
+ <string>Renderizar com deslocamento da câmera em relação ao frustum de visão (depuração de renderização).</string>
+ <key>CameraOffsetBuild</key>
+ <string>Posição padrão da câmera em relação ao ponto de foco ao entrar no modo de construção.</string>
+ <key>CameraOffsetRearView</key>
+ <string>Deslocamento inicial da câmera em relação ao avatar na visão traseira.</string>
+ <key>CameraOffsetScale</key>
+ <string>Escala o deslocamento padrão.</string>
+ <key>CameraOpacity</key>
+ <string>Opacidade do floater de controles da câmera.</string>
+ <key>CameraPosOnLogout</key>
+ <string>Posição da câmera no último logout (coordenadas globais).</string>
+ <key>CameraPositionSmoothing</key>
+ <string>Suaviza a posição da câmera ao longo do tempo.</string>
+ <key>CameraPreset</key>
+ <string>(Obsoleto) Posição predefinida da câmera - visão (0 - traseira, 1 - frontal, 2 - grupo).</string>
+ <key>CameraPresetType</key>
+ <string>Posição predefinida da câmera - visão (0 - traseira, 1 - frontal, 2 - grupo, 3 - personalizada).</string>
+ <key>CameraZoomFraction</key>
+ <string>Fração de zoom controlada pela roda do mouse.</string>
+ <key>CefVerboseLog</key>
+ <string>Ativar/desativar log detalhado do CEF.</string>
+ <key>CertStore</key>
+ <string>Especifica o repositório de certificados para verificação de confiança de certificados.</string>
+ <key>ChatAutocompleteGestures</key>
+ <string>Autocompletar gestos no chat próximo.</string>
+ <key>ChatBubbleOpacity</key>
+ <string>Opacidade do fundo do balão de chat (0,0 = totalmente transparente, 1,0 = totalmente opaco).</string>
+ <key>ChatFontSize</key>
+ <string>Tamanho do texto no console de chat (0 = pequeno, 1 = grande).</string>
+ <key>ChatFullWidth</key>
+ <string>O console de chat ocupa toda a largura da janela do SL.</string>
+ <key>ChatLoadGroupMaxMembers</key>
+ <string>Número máximo de membros ativos que mostraremos para um grupo sem resposta.</string>
+ <key>ChatOnlineNotification</key>
+ <string>Fornecer notificações quando amigos entram e saem do SL.</string>
+ <key>CheesyBeacon</key>
+ <string>Ativar efeitos de marcador exagerados.</string>
+ <key>ClickToWalk</key>
+ <string>(obsoleto) Clicar no mundo para caminhar até o local.</string>
+ <key>ClientSettingsFile</key>
+ <string>Nome do arquivo de configurações do cliente (por instalação).</string>
+ <key>CloseChatOnReturn</key>
+ <string>Fechar o chat após pressionar Enter.</string>
+ <key>CmdLineChannel</key>
+ <string>Nome do canal especificado na linha de comando.</string>
+ <key>CmdLineDisableVoice</key>
+ <string>Desativar voz.</string>
+ <key>CmdLineGridChoice</key>
+ <string>A escolha de grade do usuário ou endereço IP.</string>
+ <key>CmdLineHelperURI</key>
+ <string>Prefixo CGI web auxiliar especificado na linha de comando a ser usado.</string>
+ <key>CmdLineLoginLocation</key>
+ <string>Destino de inicialização solicitado na linha de comando.</string>
+ <key>CmdLineLoginURI</key>
+ <string>Servidor de login e prefixo CGI especificados na linha de comando a serem usados.</string>
+ <key>CmdLineSkipUpdater</key>
+ <string>Pular verificação do atualizador via linha de comando.</string>
+ <key>CmdLineUpdateService</key>
+ <string>Substituir a URL base para a consulta de atualização.</string>
+ <key>CollectFontVertexBuffers</key>
+ <string>Quando ativado, alguns elementos de interface usam buffers de cache gerados por fontes e os reutilizam. Quando desativado, é usado um cache geral com sobrecarga significativa de hash, mas que regenera os vértices a cada quadro, ficando sempre atualizado.</string>
+ <key>ColorSettingsHideDefault</key>
+ <string>Mostrar apenas configurações não padrão na lista de Configurações de Cor.</string>
+ <key>ComplexityChangesPopUpDelay</key>
+ <string>Atraso antes de o viewer mostrar novamente o aviso de complexidade do avatar.</string>
+ <key>ConnectAsGod</key>
+ <string>Fazer login como god se você tiver acesso de god.</string>
+ <key>ConnectionPort</key>
+ <string>Número da porta de conexão personalizada.</string>
+ <key>ConnectionPortEnabled</key>
+ <string>Usar a porta de conexão personalizada?</string>
+ <key>ConsoleBackgroundOpacity</key>
+ <string>Opacidade do console de chat (0,0 = totalmente transparente, 1,0 = totalmente opaco).</string>
+ <key>ConsoleBufferSize</key>
+ <string>Tamanho do histórico do console de chat (linhas de chat).</string>
+ <key>ConsoleMaxLines</key>
+ <string>Número máximo de linhas de texto de chat visíveis no console.</string>
+ <key>ConversationSortOrder</key>
+ <string>Especifica a chave de classificação das conversas.</string>
+ <key>ConversationsExpandMessagePaneFirst</key>
+ <string>Expandir o painel de mensagens ou o de conversas ao sair do modo compacto de Conversas.</string>
+ <key>ConversationsListPaneCollapsed</key>
+ <string>Armazena o estado expandido/recolhido do painel de lista de conversas no floater Conversas.</string>
+ <key>ConversationsListPaneWidth</key>
+ <string>Largura do painel de lista no floater Conversas.</string>
+ <key>ConversationsMessagePaneCollapsed</key>
+ <string>Armazena o estado expandido/recolhido do painel de mensagens no floater Conversas.</string>
+ <key>ConversationsMessagePaneWidth</key>
+ <string>Largura do painel de mensagens no floater Conversas.</string>
+ <key>ConversationsParticipantListCollapsed</key>
+ <string>Armazena o estado expandido/recolhido da lista de participantes do chat próximo.</string>
+ <key>CookiesEnabled</key>
+ <string>Aceitar cookies de sites?</string>
+ <key>CoroutineStackSize</key>
+ <string>Tamanho (em bytes) de cada pilha de corrotina.</string>
+ <key>CrashHostUrl</key>
+ <string>URL apontando para um manipulador de relatórios de crash; substitui a negociação de cluster para localizar o manipulador de crash.</string>
+ <key>CrashOnStartup</key>
+ <string>Crash solicitado pelo usuário na inicialização do viewer.</string>
+ <key>CreateToolCopyCenters</key>
+ <string>Copiar mantendo os centros alinhados ao usar a ferramenta de criação.</string>
+ <key>CreateToolCopyRotates</key>
+ <string>Copiar mantendo as rotações ao usar a ferramenta de criação.</string>
+ <key>CreateToolCopySelection</key>
+ <string>Copiar a seleção ao usar a ferramenta de criação.</string>
+ <key>CreateToolKeepSelected</key>
+ <string>Após usar a ferramenta de criação, manter a ferramenta de criação ativa.</string>
+ <key>CurlRequestTimeOut</key>
+ <string>Tempo máximo de inatividade de uma requisição curl antes de ser encerrada (requer reinício).</string>
+ <key>CurrentGrid</key>
+ <string>Grade selecionada no momento.</string>
+ <key>CurrentMapServerURL</key>
+ <string>URL do mapa-múndi da sessão atual.</string>
+ <key>Cursor3D</key>
+ <string>Tratar os valores do joystick como posições absolutas (não deltas).</string>
+ <key>DebugAnimatedObjects</key>
+ <string>Mostrar informações relacionadas a objetos animados.</string>
+ <key>DebugAvatarAppearanceMessage</key>
+ <string>Despejar vários arquivos XML ao tratar mensagens de aparência.</string>
+ <key>DebugAvatarAppearanceServiceURLOverride</key>
+ <string>URL a usar para requisições de texturas assadas; substitui o valor retornado pelo servidor de login.</string>
+ <key>DebugAvatarCompositeBaked</key>
+ <string>Colorir as malhas do avatar com base no estado assado/composto.</string>
+ <key>DebugAvatarExperimentalServerAppearanceUpdate</key>
+ <string>Experimentar enviar o cof_contents completo em vez do cof_version.</string>
+ <key>DebugAvatarJoints</key>
+ <string>Lista de juntas sobre as quais emitir informações adicionais de depuração.</string>
+ <key>DebugAvatarLocalTexLoadedTime</key>
+ <string>Exibir o tempo de carregamento das texturas locais do avatar.</string>
+ <key>DebugAvatarRezTime</key>
+ <string>Exibir os tempos para os avatares carregarem.</string>
+ <key>DebugBeaconLineWidth</key>
+ <string>Tamanho das linhas dos marcadores de depuração.</string>
+ <key>DebugForceAppearanceRequestFailure</key>
+ <string>Solicitar a versão de cof errada para testar o caminho de falha das requisições de atualização de aparência do servidor.</string>
+ <key>DebugHideEmptySystemFolders</key>
+ <string>Ocultar pastas de sistema vazias quando ativado.</string>
+ <key>DebugObjectLODs</key>
+ <string>Mostrar informações relacionadas aos cálculos de LOD para objetos anexados ou animados.</string>
+ <key>DebugPermissions</key>
+ <string>Registrar permissões dos itens de inventário selecionados.</string>
+ <key>DebugPluginDisableTimeout</key>
+ <string>Desativar o código que monitora plugins travados ou que sofreram crash.</string>
+ <key>DebugQualityPerformance</key>
+ <string>Permite alterar a qualidade de desempenho diretamente pelas configurações de depuração.</string>
+ <key>DebugSelectionLODs</key>
+ <string>Forçar a seleção a mostrar um LOD específico, -1 para desligado, 0 - mais baixo, 4 - alto.</string>
+ <key>DebugSession</key>
+ <string>Solicitar depuração para uma sessão específica do viewer.</string>
+ <key>DebugSettingsHideDefault</key>
+ <string>Mostrar apenas configurações não padrão na lista de Debug Settings.</string>
+ <key>DebugShowAvatarRenderInfo</key>
+ <string>Mostrar informações de custo de renderização do avatar.</string>
+ <key>DebugShowColor</key>
+ <string>Mostrar a cor sob o cursor.</string>
+ <key>DebugShowMemory</key>
+ <string>Mostrar a memória total alocada.</string>
+ <key>DebugShowRenderInfo</key>
+ <string>Mostrar estatísticas sobre a cena atual.</string>
+ <key>DebugShowRenderMatrices</key>
+ <string>Exibir os valores das matrizes de visão e projeção atuais.</string>
+ <key>DebugShowTextureInfo</key>
+ <string>Mostrar informações da textura de interesse.</string>
+ <key>DebugShowTime</key>
+ <string>Mostrar informações de tempo.</string>
+ <key>DebugShowUploadCost</key>
+ <string>Mostrar o custo de upload de malha.</string>
+ <key>DebugShowXUINames</key>
+ <string>Mostrar dicas com o caminho XUI do widget.</string>
+ <key>DebugStatModeSimAgentMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto pelo simulador com agentes (avatares) no painel de Estatísticas.</string>
+ <key>DebugStatModeSimFrameMsec</key>
+ <string>Modo de exibição da estatística de tempo total de quadro do simulador no painel de Estatísticas.</string>
+ <key>DebugStatModeSimImagesMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto pelo simulador com imagens/texturas no painel de Estatísticas.</string>
+ <key>DebugStatModeSimNetMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto pelo simulador com a rede no painel de Estatísticas.</string>
+ <key>DebugStatModeSimPumpIOMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto pelo simulador com operações de E/S (pump I/O) no painel de Estatísticas.</string>
+ <key>DebugStatModeSimScriptMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto pelo simulador executando scripts no painel de Estatísticas.</string>
+ <key>DebugStatModeSimSimOtherMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto pelo simulador com outras tarefas no painel de Estatísticas.</string>
+ <key>DebugStatModeSimSimPhysicsMsec</key>
+ <string>Modo de exibição da estatística de tempo total de física do simulador no painel de Estatísticas.</string>
+ <key>DebugStatModeSimSimPhysicsOtherMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto com outras tarefas de física no painel de Estatísticas.</string>
+ <key>DebugStatModeSimSimPhysicsShapeUpdateMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto atualizando formas de colisão da física no painel de Estatísticas.</string>
+ <key>DebugStatModeSimSimPhysicsStepMsec</key>
+ <string>Modo de exibição da estatística de tempo gasto no passo de simulação da física no painel de Estatísticas.</string>
+ <key>DebugStatModeSimSleepMsec</key>
+ <string>Modo de exibição da estatística de tempo de espera (sleep) do simulador por quadro no painel de Estatísticas.</string>
+ <key>DebugStatModeSimSpareMsec</key>
+ <string>Modo de exibição da estatística de tempo ocioso (sobressalente) do simulador por quadro no painel de Estatísticas.</string>
+ <key>DebugStatModeSimTotalUnackedBytes</key>
+ <string>Modo de exibição da estatística de total de bytes não confirmados (unacked) na rede no painel de Estatísticas.</string>
+ <key>DebugViews</key>
+ <string>Exibir informações de depuração para as views.</string>
+ <key>DebugWindowProc</key>
+ <string>Registrar mensagens do Windows.</string>
+ <key>DeepLTranslateAPIKey</key>
+ <string>Dados do serviço de tradução DeepL para uso com a API do DeepL Translator.</string>
+ <key>DefaultFemaleAvatar</key>
+ <string>Avatar feminino padrão.</string>
+ <key>DefaultLoginLocation</key>
+ <string>Destino de inicialização padrão (se não especificado na linha de comando).</string>
+ <key>DefaultMaleAvatar</key>
+ <string>Avatar masculino padrão.</string>
+ <key>DefaultUploadPermissionsConverted</key>
+ <string>As permissões de upload padrão foram convertidas em permissões de criação padrão.</string>
+ <key>DestinationGuideURL</key>
+ <string>Conteúdo do guia de destinos.</string>
+ <key>DisableAllRenderFeatures</key>
+ <string>Desativa todos os recursos de renderização.</string>
+ <key>DisableAllRenderTypes</key>
+ <string>Desativa todos os tipos de renderização.</string>
+ <key>DisableCameraConstraints</key>
+ <string>Desativar os limites normais impostos à câmera pela posição do avatar.</string>
+ <key>DisableCrashLogger</key>
+ <string>Não enviar relatório de crash ao servidor da Linden.</string>
+ <key>DisableExternalBrowser</key>
+ <string>Desativar a abertura de um navegador externo.</string>
+ <key>DisableLookAtAnimation</key>
+ <string>O avatar segue o cursor com os olhos; quando desativado, o avatar olha para a frente.</string>
+ <key>DisableTextHyperlinkActions</key>
+ <string>Desativar destaque e criação de links de URLs em caixas de texto XUI.</string>
+ <key>DiskCacheDirName</key>
+ <string>Nome do cache em disco (dentro do diretório padrão de cache em disco do Viewer).</string>
+ <key>DiskCachePercentOfTotal</key>
+ <string>Percentual do tamanho total do cache (definido por CacheSize) a usar para o cache em disco (ex.: armazenamento de assets, exclui texturas).</string>
+ <key>DiskCacheVersion</key>
+ <string>Número da versão do cache em disco.</string>
+ <key>DisplayTimecode</key>
+ <string>Exibir o timecode na tela.</string>
+ <key>Disregard128DefaultDrawDistance</key>
+ <string>Se deve usar o padrão automático de 128 de distância de desenho.</string>
+ <key>Disregard96DefaultDrawDistance</key>
+ <string>Se deve usar o padrão automático de 96 de distância de desenho.</string>
+ <key>DoNotDisturbModeResponse</key>
+ <string>Resposta automática a mensagens instantâneas no modo Não Perturbe.</string>
+ <key>DoNotDisturbResponseChanged</key>
+ <string>A mensagem do modo Não Perturbe do usuário difere do padrão?</string>
+ <key>DoubleClickAutoPilot</key>
+ <string>(Obsoleto) Ativar piloto automático com duplo clique.</string>
+ <key>DoubleClickShowWorldMap</key>
+ <string>Ativar duplo clique para mostrar o mapa-múndi a partir do minimapa.</string>
+ <key>DoubleClickTeleport</key>
+ <string>Ativar duplo clique para teletransportar onde permitido (afeta o minimapa e o painel de pessoas).</string>
+ <key>DynamicCameraStrength</key>
+ <string>Quanto a câmera atrasa em relação ao movimento do avatar (0 = nenhum, 30 = velocidade do avatar).</string>
+ <key>EditCameraMovement</key>
+ <string>Ao entrar no modo de construção, a câmera sobe acima do avatar.</string>
+ <key>EditLinkedParts</key>
+ <string>Selecionar partes individuais de objetos vinculados.</string>
+ <key>EffectScriptChatParticles</key>
+ <string>1 = comportamento normal, 0 = desativar a exibição das luzes giratórias quando scripts se comunicam.</string>
+ <key>EmulateCoreCount</key>
+ <string>Para depuração -- número de núcleos aos quais restringir o processo principal, ou 0 para sem limite. Requer reinício.</string>
+ <key>EnableAltZoom</key>
+ <string>Usar Alt+mouse para olhar e aproximar objetos.</string>
+ <key>EnableAppearance</key>
+ <string>Ativar a abertura da aparência a partir de link web.</string>
+ <key>EnableAvatarPay</key>
+ <string>Ativar o pagamento a outros avatares a partir de link web.</string>
+ <key>EnableAvatarShare</key>
+ <string>Ativar compartilhamento a partir de link web.</string>
+ <key>EnableButtonFlashing</key>
+ <string>Permitir que a interface pisque botões para chamar sua atenção.</string>
+ <key>EnableClassifieds</key>
+ <string>Ativar a criação de novos anúncios classificados a partir de link web.</string>
+ <key>EnableCollisionSounds</key>
+ <string>Reproduzir sons em colisões.</string>
+ <key>EnableDiscord</key>
+ <string>Quando ativado, conecta ao Discord para habilitar o Rich Presence.</string>
+ <key>EnableDiskCacheDebugInfo</key>
+ <string>Quando ativado, exibir informações adicionais de depuração do cache.</string>
+ <key>EnableGestureSounds</key>
+ <string>Reproduzir sons de gestos.</string>
+ <key>EnableGrab</key>
+ <string>Usar Ctrl+mouse para agarrar e manipular objetos.</string>
+ <key>EnableGroupChatPopups</key>
+ <string>Ativar pop-ups de chat de grupo recebidos.</string>
+ <key>EnableInventory</key>
+ <string>Ativar a abertura do inventário a partir de link web.</string>
+ <key>EnableLookAtTarget</key>
+ <string>Se deve ou não animar a cabeça do avatar e enviar alvos de olhar ao mover o cursor ou focar em objetos.</string>
+ <key>EnableMouselook</key>
+ <string>Permitir perspectiva em primeira pessoa e controle da câmera pelo mouse.</string>
+ <key>EnablePlaceProfile</key>
+ <string>Ativar a visualização do perfil do lugar a partir de link web.</string>
+ <key>EnableSelectionHints</key>
+ <string>Se deve ou não enviar dicas de edição para animar o braço ao editar um objeto.</string>
+ <key>EnableUIHints</key>
+ <string>Alterna os pop-ups de dicas da interface.</string>
+ <key>EnableVisualLeakDetector</key>
+ <string>Ativar o Visual Leak Detector.</string>
+ <key>EnableVoiceChat</key>
+ <string>Ativar conversa com outros residentes usando microfone.</string>
+ <key>EnvironmentPersistAcrossLogin</key>
+ <string>Manter as configurações de ambiente consistentes entre sessões.</string>
+ <key>EventURL</key>
+ <string>URL do site de eventos, exibida no floater de eventos.</string>
+ <key>EveryoneCopy</key>
+ <string>(obsoleto) Todos podem copiar os objetos recém-criados.</string>
+ <key>ExperienceSearchMaturity</key>
+ <string>Classificação máxima de conteúdo preferida pelo usuário na busca de Experiências (padrão: General).</string>
+ <key>ExternalEditor</key>
+ <string>Caminho do programa usado para editar scripts LSL e arquivos XUI, ex.: /usr/bin/gedit --new-window "%s".</string>
+ <key>FMODExDecodeBufferSize</key>
+ <string>Define o tamanho do buffer de decodificação de streaming (em milissegundos) para o FMOD Ex ou FMOD Studio.</string>
+ <key>FMODExProfilerEnable</key>
+ <string>Ativar ferramenta de profiler ao usar FMOD Ex ou FMOD Studio.</string>
+ <key>FakeInitialOutfitName</key>
+ <string>Fingir que este é o primeiro login e que o nome especificado foi escolhido.</string>
+ <key>FetchGroupChatHistory</key>
+ <string>Buscar mensagens recentes dos servidores de chat de grupo ao abrir uma janela de grupo.</string>
+ <key>FindLandArea</key>
+ <string>Ativa a filtragem dos resultados de busca de terrenos por área.</string>
+ <key>FindLandPrice</key>
+ <string>Ativa a filtragem dos resultados de busca de terrenos por preço.</string>
+ <key>FindOriginalOpenWindow</key>
+ <string>Define a ação para 'Encontrar original' e 'Mostrar no Inventário' (0 - mostra o item no Inventário principal, 1 - abre uma nova janela de pasta única).</string>
+ <key>FirstLoginThisInstall</key>
+ <string>Especifica que você não fez login com o viewer desde que realizou uma instalação limpa.</string>
+ <key>FirstName</key>
+ <string>Primeiro nome de login.</string>
+ <key>FirstPersonAvatarVisible</key>
+ <string>Exibir avatar e anexos abaixo do pescoço enquanto em mouselook.</string>
+ <key>FirstRunThisInstall</key>
+ <string>Especifica que você não executou o viewer desde que realizou uma instalação limpa.</string>
+ <key>FirstSelectedDisabledPopups</key>
+ <string>Retorna falso se não houver pop-up desativado selecionado na lista de pop-ups das preferências de floaters.</string>
+ <key>FirstSelectedEnabledPopups</key>
+ <string>Retorna falso se não houver pop-up ativado selecionado na lista de pop-ups das preferências de floaters.</string>
+ <key>FixedWeather</key>
+ <string>Os efeitos climáticos não mudam ao longo do tempo.</string>
+ <key>FlashCount</key>
+ <string>Número de piscadas do item. Requer reinício.</string>
+ <key>FlashPeriod</key>
+ <string>Período em que o item pisca (segundos). Requer reinício.</string>
+ <key>FloaterActiveSpeakersSortAscending</key>
+ <string>Se deve classificar em ordem crescente ou decrescente.</string>
+ <key>FloaterActiveSpeakersSortColumn</key>
+ <string>Nome da coluna pela qual classificar.</string>
+ <key>FloaterMapEast</key>
+ <string>Rótulo Leste do floater do mapa.</string>
+ <key>FloaterMapNorth</key>
+ <string>Rótulo Norte do floater do mapa.</string>
+ <key>FloaterMapNorthEast</key>
+ <string>Rótulo Nordeste do floater do mapa.</string>
+ <key>FloaterMapNorthWest</key>
+ <string>Rótulo Noroeste do floater do mapa.</string>
+ <key>FloaterMapSouth</key>
+ <string>Rótulo Sul do floater do mapa.</string>
+ <key>FloaterMapSouthEast</key>
+ <string>Rótulo Sudeste do floater do mapa.</string>
+ <key>FloaterMapSouthWest</key>
+ <string>Rótulo Sudoeste do floater do mapa.</string>
+ <key>FloaterMapWest</key>
+ <string>Rótulo Oeste do floater do mapa.</string>
+ <key>FloaterStatisticsRect</key>
+ <string>Retângulo do histórico de chat.</string>
+ <key>FlycamAbsolute</key>
+ <string>Tratar os valores da Flycam como posições absolutas (não deltas).</string>
+ <key>FlycamAxisDeadZone0</key>
+ <string>Zona morta do eixo Z (movimento vertical) da Flycam.</string>
+ <key>FlycamAxisDeadZone1</key>
+ <string>Zona morta do eixo X (movimento lateral) da Flycam.</string>
+ <key>FlycamAxisDeadZone2</key>
+ <string>Zona morta do eixo Y (movimento para frente/trás) da Flycam.</string>
+ <key>FlycamAxisDeadZone3</key>
+ <string>Zona morta da rolagem (roll) da Flycam.</string>
+ <key>FlycamAxisDeadZone4</key>
+ <string>Zona morta da elevação (pitch) da Flycam.</string>
+ <key>FlycamAxisDeadZone5</key>
+ <string>Zona morta da guinada (yaw) da Flycam.</string>
+ <key>FlycamAxisDeadZone6</key>
+ <string>Zona morta do zoom da Flycam.</string>
+ <key>FlycamAxisScale0</key>
+ <string>Fator de escala do eixo Z (movimento vertical) da Flycam.</string>
+ <key>FlycamAxisScale1</key>
+ <string>Fator de escala do eixo X (movimento lateral) da Flycam.</string>
+ <key>FlycamAxisScale2</key>
+ <string>Fator de escala do eixo Y (movimento para frente/trás) da Flycam.</string>
+ <key>FlycamAxisScale3</key>
+ <string>Fator de escala da rolagem (roll) da Flycam.</string>
+ <key>FlycamAxisScale4</key>
+ <string>Fator de escala da elevação (pitch) da Flycam.</string>
+ <key>FlycamAxisScale5</key>
+ <string>Fator de escala da guinada (yaw) da Flycam.</string>
+ <key>FlycamAxisScale6</key>
+ <string>Fator de escala do zoom da Flycam.</string>
+ <key>FlycamBuildModeScale</key>
+ <string>Fator de escala a aplicar aos movimentos da Flycam no modo de construção.</string>
+ <key>FlycamFeathering</key>
+ <string>Suavização da Flycam (menos é mais suave).</string>
+ <key>FlyingAtExit</key>
+ <string>Estava voando no último logout, então voar ao fazer login.</string>
+ <key>FocusOffsetRearView</key>
+ <string>Deslocamento inicial do ponto de foco em relação ao avatar para a predefinição de câmera Visão Traseira (o eixo x é para frente).</string>
+ <key>FocusPosOnLogout</key>
+ <string>Ponto de foco da câmera no último logout (coordenadas globais).</string>
+ <key>FolderAutoOpenDelay</key>
+ <string>Segundos antes de expandir automaticamente a pasta sob o mouse ao arrastar e soltar itens no inventário.</string>
+ <key>FontScreenDPI</key>
+ <string>Resolução da fonte; maior é maior (pixels por polegada).</string>
+ <key>ForceAddressSize</key>
+ <string>Forçar a atualização do Windows para o viewer de 32 bits ou 64 bits.</string>
+ <key>ForceAssetFail</key>
+ <string>Forçar a falha das buscas de wearables para este tipo de asset.</string>
+ <key>ForceLoginURL</key>
+ <string>Forçar uma URL específica para o conteúdo da página de login - usada se existir.</string>
+ <key>ForceShowGrid</key>
+ <string>Sempre mostrar a lista suspensa de grades na tela de login.</string>
+ <key>FreezeTime</key>
+ <string>Congela o tempo de renderização (animações e atualizações de cena pausadas).</string>
+ <key>FriendsListHideUsernames</key>
+ <string>Mostrar tanto o nome de exibição quanto o nome de usuário na lista de amigos.</string>
+ <key>FriendsListShowIcons</key>
+ <string>Mostrar/ocultar ícones de online e de todos os amigos na lista de amigos.</string>
+ <key>FriendsListShowPermissions</key>
+ <string>Mostrar/ocultar ícones de permissão na lista de amigos.</string>
+ <key>FriendsSortOrder</key>
+ <string>Especifica a ordem de classificação dos amigos (0 = por nome, 1 = por status online).</string>
+ <key>FullScreen</key>
+ <string>Executar uma sessão em tela cheia. Não suportado no MacOS.</string>
+ <key>FullScreenAspectRatio</key>
+ <string>Proporção de tela da exibição em tela cheia (largura / altura).</string>
+ <key>FullScreenAutoDetectAspectRatio</key>
+ <string>Detectar automaticamente a proporção de tela correta para exibição em tela cheia.</string>
+ <key>GLTFEnabled</key>
+ <string>Ativar suporte a GLTF. Definido como verdadeiro pelo simulador se o simulador ao qual você está conectado suportar upload de assets GLTF. AVISO: definir manualmente como verdadeiro ativará botões que podem esgotar seu saldo de L$ ao fazer upload implícito de texturas sem perguntar.</string>
+ <key>GenericErrorPageURL</key>
+ <string>URL a definir como propriedade no LLMediaControl para navegar caso a página finalize com um status HTTP 400-499.</string>
+ <key>GesturesEveryoneCopy</key>
+ <string>Todos podem copiar o gesto recém-criado.</string>
+ <key>GesturesMarketplaceURL</key>
+ <string>URL do Marketplace de Gestos.</string>
+ <key>GesturesNextOwnerCopy</key>
+ <string>Gestos recém-criados podem ser copiados pelo próximo proprietário.</string>
+ <key>GesturesNextOwnerModify</key>
+ <string>Gestos recém-criados podem ser modificados pelo próximo proprietário.</string>
+ <key>GesturesNextOwnerTransfer</key>
+ <string>Gestos recém-criados podem ser revendidos ou doados pelo próximo proprietário.</string>
+ <key>GesturesShareWithGroup</key>
+ <string>Gestos recém-criados são compartilhados com o grupo ativo no momento.</string>
+ <key>GoogleTranslateAPIKey</key>
+ <string>Chave da API do Google Translate.</string>
+ <key>GridCrossSections</key>
+ <string>Destacar seções transversais de prims com o plano de manipulação da grade.</string>
+ <key>GridDrawSize</key>
+ <string>Extensão visível da grade de encaixe 2D (metros).</string>
+ <key>GridMode</key>
+ <string>Quadro de referência da grade de encaixe (0 = mundo, 1 = local, 2 = objeto de referência).</string>
+ <key>GridOpacity</key>
+ <string>Opacidade das linhas da grade (0,0 = totalmente transparente, 1,0 = totalmente opaco).</string>
+ <key>GridResolution</key>
+ <string>Tamanho de um único passo da grade (metros).</string>
+ <key>GridStatusFloaterRect</key>
+ <string>Dimensões do floater de perfil web.</string>
+ <key>GridStatusRSS</key>
+ <string>URL que aponta para o RSS de Status da Grade do SL.</string>
+ <key>GridStatusUpdateDelay</key>
+ <string>Atraso do temporizador para atualizar o RSS de Status da Grade.</string>
+ <key>GridSubUnit</key>
+ <string>Exibir passos fracionários da grade, relativos ao tamanho da grade.</string>
+ <key>GridSubdivision</key>
+ <string>Número máximo de vezes para dividir uma unidade da grade de encaixe quando GridSubUnit é verdadeiro.</string>
+ <key>GroupListShowIcons</key>
+ <string>Mostrar/ocultar ícones de grupo na lista de grupos.</string>
+ <key>GroupMembersSortOrder</key>
+ <string>A ordem pela qual os membros do grupo serão classificados (name|donated|online).</string>
+ <key>GroupTitlesTagMode</key>
+ <string>Selecionar o modo de etiqueta de títulos de grupo: 0 - sem etiquetas de grupo, 1 - apenas a etiqueta do meu grupo, 2 - todas as etiquetas de grupo.</string>
+ <key>HUDScaleFactor</key>
+ <string>Escala dos anexos de HUD.</string>
+ <key>HeadlessClient</key>
+ <string>Executar em modo headless desativando a renderização GL, o teclado, etc.</string>
+ <key>HeightUnits</key>
+ <string>Determina quais unidades métricas são usadas: 1(TRUE) para metro e 0(FALSE) para pé.</string>
+ <key>HelpFloaterOpen</key>
+ <string>Mostrar o floater de Ajuda no login?</string>
+ <key>HelpURLFormat</key>
+ <string>Padrão de URL para a página de ajuda; os argumentos serão codificados; veja llviewerhelp.cpp:buildHelpURL para os argumentos.</string>
+ <key>HideSelectedObjects</key>
+ <string>Ocultar objetos selecionados.</string>
+ <key>HideUIControls</key>
+ <string>Ocultar todos os itens de menu e botões.</string>
+ <key>HighResSnapshot</key>
+ <string>Dobrar a resolução da captura em relação à resolução atual da janela.</string>
+ <key>HomeSidePanelURL</key>
+ <string>URL da página web a exibir no painel lateral Início.</string>
+ <key>HostID</key>
+ <string>Identificador de máquina para instâncias hospedadas do Second Life.</string>
+ <key>HoverHeightAffectsCamera</key>
+ <string>A visão da câmera é afetada pela configuração de altura de flutuação.</string>
+ <key>HttpPipelining</key>
+ <string>Se verdadeiro, o viewer tentará usar pipelining nas requisições HTTP.</string>
+ <key>HttpProxyType</key>
+ <string>Tipo de proxy a usar nas operações HTTP.</string>
+ <key>HttpRangeRequestsDisable</key>
+ <string>Se verdadeiro, o viewer não emitirá requisições GET com cabeçalhos 'Range:' para malhas e texturas. Pode resolver problemas com certos provedores e equipamentos de rede.</string>
+ <key>IMShowControlPanel</key>
+ <string>Mostrar o painel de controle de IM.</string>
+ <key>IMShowNamesForP2PConv</key>
+ <string>Ativar/desativar exibição de nomes no chat.</string>
+ <key>IMShowTime</key>
+ <string>Ativar/desativar exibição de carimbo de data/hora no chat.</string>
+ <key>IgnoreAllNotifications</key>
+ <string>Ignorar todas as notificações para que nunca precisemos da entrada do usuário nelas.</string>
+ <key>IgnoreFOVZoomForLODs</key>
+ <string>Ignorar o efeito de zoom (CTRL+0) ao calcular os LODs.</string>
+ <key>IgnorePixelDepth</key>
+ <string>Ignorar as configurações de profundidade de pixel.</string>
+ <key>ImagePipelineUseHTTP</key>
+ <string>Se TRUE, usar HTTP GET para buscar texturas do servidor.</string>
+ <key>ImporterDebugMode</key>
+ <string>Em 0 não faz nada; em 1 exporta dados de skinning perto do arquivo original; em 2 exporta dados de skinning e posições/pesos dos 5 primeiros modelos; em 3 exporta dados de skinning e modelos como llsd.</string>
+ <key>ImporterDebugVerboseLogging</key>
+ <string>Ativa saída de depuração para identificar com mais precisão as fontes de erros de importação. Aviso: a saída pode deixar a importação lenta em muitas máquinas.</string>
+ <key>ImporterLegacyMatching</key>
+ <string>Ativa correspondência de modelos baseada em índice.</string>
+ <key>ImporterModelLimit</key>
+ <string>Limita a quantidade de modelos gerados pelo importador (quando há mais de 8 faces) para arquivos dae e gltf.</string>
+ <key>ImporterPreprocessDAE</key>
+ <string>Ativa pré-processamento de arquivos DAE para corrigir alguns problemas relacionados ao ColladaDOM (como suporte a espaços em nomes e ids).</string>
+ <key>InBandwidth</key>
+ <string>Limite de largura de banda de entrada (bps).</string>
+ <key>InactiveFloaterTransparency</key>
+ <string>Transparência de floaters inativos (floaters sem foco).</string>
+ <key>IndirectMaxComplexity</key>
+ <string>Controla RenderAvatarMaxComplexity de forma não linear (não defina este valor).</string>
+ <key>IndirectMaxNonImpostors</key>
+ <string>Controla RenderAvatarMaxNonImpostors de forma não linear (não defina este valor).</string>
+ <key>InstallLanguage</key>
+ <string>Idioma passado pelo instalador (para a interface).</string>
+ <key>InstantMessageLogPath</key>
+ <string>Caminho para seus arquivos de log.</string>
+ <key>InterpolationPhaseOut</key>
+ <string>Segundos para eliminar gradualmente o movimento interpolado.</string>
+ <key>InterpolationTime</key>
+ <string>Por quanto tempo extrapolar o movimento do objeto após o último pacote recebido.</string>
+ <key>InventoryAddAttachmentBehavior</key>
+ <string>Define o comportamento ao pressionar Enter em um item de inventário.</string>
+ <key>InventoryAutoOpenDelay</key>
+ <string>Segundos antes de abrir automaticamente o inventário quando o mouse está sobre o botão de inventário ao arrastar e soltar itens.</string>
+ <key>InventoryDebugSimulateLateOpRate</key>
+ <string>Taxa na qual simulamos requisições de cópia/link que se completam tarde em algumas operações.</string>
+ <key>InventoryDebugSimulateOpFailureRate</key>
+ <string>Taxa na qual simulamos falhas de requisições de cópia/link em algumas operações.</string>
+ <key>InventoryDisplayInbox</key>
+ <string>Substituir a exibição da caixa de entrada de itens recebidos do inventário.</string>
+ <key>InventoryExposeFolderID</key>
+ <string>Permite copiar o id da pasta pelo menu de contexto.</string>
+ <key>InventoryFavoritesColorText</key>
+ <string>Renderizar itens favoritos usando InventoryFavoriteText como cor.</string>
+ <key>InventoryFavoritesUseHollowStar</key>
+ <string>Mostrar estrela perto de pastas que contêm favoritos.</string>
+ <key>InventoryFavoritesUseStar</key>
+ <string>Mostrar estrela perto de itens favoritados no inventário.</string>
+ <key>InventoryInboxHeight</key>
+ <string>Altura do painel de caixa de entrada do inventário no floater Inventário.</string>
+ <key>InventoryInboxToggleState</key>
+ <string>Armazena o estado aberto/fechado do painel Itens Recebidos do inventário.</string>
+ <key>InventoryLinking</key>
+ <string>Ativar a capacidade de criar links para pastas e itens via "Colar como link".</string>
+ <key>InventoryOutboxLogging</key>
+ <string>Ativar saída de depuração associada à Caixa de Saída do Comerciante.</string>
+ <key>InventoryOutboxMakeVisible</key>
+ <string>Ativar a exibição da Caixa de Saída e de Entrada do Comerciante no inventário para fins de depuração.</string>
+ <key>InventoryOutboxMaxFolderCount</key>
+ <string>Número máximo de subpastas permitidas em uma listagem na caixa de saída do comerciante.</string>
+ <key>InventoryOutboxMaxFolderDepth</key>
+ <string>Número máximo de níveis aninhados de subpastas permitidos em uma listagem na caixa de saída do comerciante.</string>
+ <key>InventoryOutboxMaxItemCount</key>
+ <string>Número máximo de itens permitidos em uma listagem na caixa de saída do comerciante.</string>
+ <key>InventoryOutboxMaxStockItemCount</key>
+ <string>Número máximo de itens permitidos em uma pasta de estoque.</string>
+ <key>InventoryShowFavoritesTab</key>
+ <string>Mostrar/ocultar a aba Favoritos no floater de Inventário.</string>
+ <key>InventoryShowRecentTab</key>
+ <string>Mostrar/ocultar a aba Recentes no floater de Inventário.</string>
+ <key>InventoryShowWornTab</key>
+ <string>Mostrar/ocultar a aba Em uso no floater de Inventário.</string>
+ <key>InventorySortOrder</key>
+ <string>Especifica a chave de classificação dos itens de inventário (+0 = nome, +1 = data, +2 = pastas sempre por nome, +4 = pastas de sistema no topo).</string>
+ <key>InventoryTrashMaxCapacity</key>
+ <string>Capacidade máxima da pasta Lixeira. O usuário será convidado a esvaziá-la quando excedida.</string>
+ <key>InvertMouse</key>
+ <string>Em mouselook, mover o mouse para cima olha para baixo e vice-versa (FALSE = mover para cima olha para cima).</string>
+ <key>JoystickAvatarEnabled</key>
+ <string>Ativa o joystick para controlar o movimento do avatar.</string>
+ <key>JoystickAxis0</key>
+ <string>Mapeamento de eixo de hardware da Flycam para o eixo interno 0 ([0, 5]).</string>
+ <key>JoystickAxis1</key>
+ <string>Mapeamento de eixo de hardware da Flycam para o eixo interno 1 ([0, 5]).</string>
+ <key>JoystickAxis2</key>
+ <string>Mapeamento de eixo de hardware da Flycam para o eixo interno 2 ([0, 5]).</string>
+ <key>JoystickAxis3</key>
+ <string>Mapeamento de eixo de hardware da Flycam para o eixo interno 3 ([0, 5]).</string>
+ <key>JoystickAxis4</key>
+ <string>Mapeamento de eixo de hardware da Flycam para o eixo interno 4 ([0, 5]).</string>
+ <key>JoystickAxis5</key>
+ <string>Mapeamento de eixo de hardware da Flycam para o eixo interno 5 ([0, 5]).</string>
+ <key>JoystickAxis6</key>
+ <string>Mapeamento de eixo de hardware da Flycam para o eixo interno 6 ([0, 5]).</string>
+ <key>JoystickBuildEnabled</key>
+ <string>Ativa o joystick para mover objetos editados.</string>
+ <key>JoystickDeviceUUID</key>
+ <string>ID do dispositivo preferido.</string>
+ <key>JoystickEnabled</key>
+ <string>Ativa a entrada por joystick.</string>
+ <key>JoystickFlycamEnabled</key>
+ <string>Ativa o joystick para controlar a Flycam.</string>
+ <key>JoystickInitialized</key>
+ <string>Se um joystick foi ou não detectado e inicializado.</string>
+ <key>JoystickMouselookYaw</key>
+ <string>Passar o yaw do joystick para scripts no modo mouselook.</string>
+ <key>JoystickRunThreshold</key>
+ <string>Limiar de entrada para iniciar a corrida.</string>
+ <key>Jpeg2000AdvancedCompression</key>
+ <string>Usar opções avançadas de compressão Jpeg2000 (precincts, blocos, ordenação, marcadores).</string>
+ <key>Jpeg2000BlocksSize</key>
+ <string>Tamanho dos blocos de codificação. Assumidos quadrados e iguais para todos os níveis. Deve ser potência de 2. Máx 64, Mín 4.</string>
+ <key>Jpeg2000PrecinctsSize</key>
+ <string>Tamanho dos precincts da imagem. Assumidos quadrados e iguais para todos os níveis. Deve ser potência de 2.</string>
+ <key>KeepAspectForSnapshot</key>
+ <string>Usar a janela inteira ao tirar a captura, independentemente do tamanho de imagem solicitado.</string>
+ <key>KeepAutoTuneLock</key>
+ <string>Quando ativado, o AutoTuneLock será mantido em todas as sessões seguintes.</string>
+ <key>KeepConversationLogTranscripts</key>
+ <string>Manter log e transcrições de conversas: 2 - ambos, 1 - logs, 0 - nenhum.</string>
+ <key>LSLFindCaseInsensitivity</key>
+ <string>Usar busca sem distinção de maiúsculas/minúsculas no editor LSL.</string>
+ <key>LSLFontSizeName</key>
+ <string>Tamanho da fonte do texto no editor LSL.</string>
+ <key>LSLHelpURL</key>
+ <string>URL que aponta para os arquivos de ajuda do LSL, com [LSL_STRING] correspondendo à função ou palavra-chave LSL referenciada.</string>
+ <key>LagMeterShrunk</key>
+ <string>Último estado grande/pequeno do medidor de lag.</string>
+ <key>LandBrushForce</key>
+ <string>Multiplicador da força do pincel de modificação de terreno.</string>
+ <key>LandBrushSize</key>
+ <string>Tamanho da região afetada ao usar a ferramenta de terraformação.</string>
+ <key>LandmarksSortedByDate</key>
+ <string>Reflete a ordem de classificação do painel de landmarks.</string>
+ <key>Language</key>
+ <string>Idioma do viewer.</string>
+ <key>LanguageIsPublic</key>
+ <string>Deixar outros residentes verem as informações do nosso idioma.</string>
+ <key>LastAppearanceTab</key>
+ <string>Última aba selecionada no floater de aparência.</string>
+ <key>LastFeatureVersion</key>
+ <string>[NÃO MODIFICAR] Número da versão da tabela de recursos para rastrear mudanças no sistema de renderização.</string>
+ <key>LastFindPanel</key>
+ <string>Controla qual operação de busca aparece por padrão ao clicar no botão "Buscar".</string>
+ <key>LastGPUString</key>
+ <string>[NÃO MODIFICAR] string anterior de id da GPU para rastrear mudanças de hardware.</string>
+ <key>LastInventoryInboxActivity</key>
+ <string>Última vez que a caixa de entrada de itens recebidos foi acessada pelo usuário.</string>
+ <key>LastLogoff</key>
+ <string>Último logout.</string>
+ <key>LastMediaSettingsTab</key>
+ <string>Última aba selecionada na janela de configurações de mídia.</string>
+ <key>LastName</key>
+ <string>Sobrenome de login.</string>
+ <key>LastPostcardRecipient</key>
+ <string>Último destinatário de cartão postal.</string>
+ <key>LastPrefTab</key>
+ <string>Última aba selecionada na janela de preferências.</string>
+ <key>LastRunVersion</key>
+ <string>Número da versão da última instância do viewer que você executou.</string>
+ <key>LastUIFeatureVersion</key>
+ <string>Número da versão de recursos de interface para rastrear notificações de recursos entre builds do viewer.</string>
+ <key>LeapCommand</key>
+ <string>Zero ou mais linhas de comando para executar programas do LLSD Event API Plugin.</string>
+ <key>LeapPlaybackEventsCommand</key>
+ <string>Linha de comando para usar o leap para iniciar a reprodução de gravações de eventos.</string>
+ <key>LeaveMouselook</key>
+ <string>Sair do modo mouselook com as teclas S ou Seta para Baixo enquanto sentado.</string>
+ <key>LetterKeysFocusChatBar</key>
+ <string>Quando teclas de caracteres imprimíveis (possivelmente com Shift) são pressionadas, a barra de chat recebe o foco.</string>
+ <key>LimitDragDistance</key>
+ <string>Limitar a translação do objeto pela ferramenta de mover.</string>
+ <key>LimitLookAtTarget</key>
+ <string>Se deve ou não limitar os alvos de olhar ao redor da cabeça do avatar antes de enviar.</string>
+ <key>LimitLookAtTargetDistance</key>
+ <string>Distância para limitar o alvo de olhar.</string>
+ <key>LimitSelectDistance</key>
+ <string>Não permitir a seleção de objetos além da distância máxima de seleção.</string>
+ <key>LinkReplaceBatchPauseTime</key>
+ <string>O tempo em segundos entre dois lotes em uma operação de substituição de link.</string>
+ <key>LinkReplaceBatchSize</key>
+ <string>O tamanho máximo de um lote em uma operação de substituição de link.</string>
+ <key>LipSyncAah</key>
+ <string>Loop de balbucio Aah (abertura da mandíbula).</string>
+ <key>LipSyncAahPowerTransfer</key>
+ <string>Curva de transferência da potência da interface de voz para a amplitude de sincronia labial aah.</string>
+ <key>LipSyncEnabled</key>
+ <string>0 desativa a sincronia labial, 1 ativa o loop de balbucio.</string>
+ <key>LipSyncOoh</key>
+ <string>Loop de balbucio Ooh (largura da boca).</string>
+ <key>LipSyncOohAahRate</key>
+ <string>Taxa de balbucio de Ooh e Aah (/seg).</string>
+ <key>LipSyncOohPowerTransfer</key>
+ <string>Curva de transferência da potência da interface de voz para a amplitude de sincronia labial ooh.</string>
+ <key>LocalCacheVersion</key>
+ <string>Número da versão do cache.</string>
+ <key>LocalFileSystemBrowsingEnabled</key>
+ <string>Ativar/desativar o acesso ao sistema de arquivos local via seletor de arquivos.</string>
+ <key>LocalTerrainAsset1</key>
+ <string>Se definido como um UUID não nulo, substitui o asset de terreno localmente para todas as regiões com assets de material. Assets de terreno locais não são visíveis para os outros. Lembre-se de que esta configuração de depuração pode ser temporária. Não dependa de sua existência em builds futuros do viewer.</string>
+ <key>LocalTerrainAsset2</key>
+ <string>Se definido como um UUID não nulo, substitui o asset de terreno localmente para todas as regiões com assets de material. Assets de terreno locais não são visíveis para os outros. Lembre-se de que esta configuração de depuração pode ser temporária. Não dependa de sua existência em builds futuros do viewer.</string>
+ <key>LocalTerrainAsset3</key>
+ <string>Se definido como um UUID não nulo, substitui o asset de terreno localmente para todas as regiões com assets de material. Assets de terreno locais não são visíveis para os outros. Lembre-se de que esta configuração de depuração pode ser temporária. Não dependa de sua existência em builds futuros do viewer.</string>
+ <key>LocalTerrainAsset4</key>
+ <string>Se definido como um UUID não nulo, substitui o asset de terreno localmente para todas as regiões com assets de material. Assets de terreno locais não são visíveis para os outros. Lembre-se de que esta configuração de depuração pode ser temporária. Não dependa de sua existência em builds futuros do viewer.</string>
+ <key>LocalTerrainPaintEnabled</key>
+ <string>Ativa o paintmap local se LocalTerrainAsset1, etc. estiverem definidos.</string>
+ <key>LocalTerrainTransform1OffsetU</key>
+ <string>Deslocamento horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 1), quando LocalTerrainAsset1 estiver definido.</string>
+ <key>LocalTerrainTransform1OffsetV</key>
+ <string>Deslocamento vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 1), quando LocalTerrainAsset1 estiver definido.</string>
+ <key>LocalTerrainTransform1Rotation</key>
+ <string>Rotação (em graus) da transformação de textura glTF KHR_texture_transform do terreno local (camada 1), quando LocalTerrainAsset1 estiver definido.</string>
+ <key>LocalTerrainTransform1ScaleU</key>
+ <string>Escala horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 1), quando LocalTerrainAsset1 estiver definido.</string>
+ <key>LocalTerrainTransform1ScaleV</key>
+ <string>Escala vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 1), quando LocalTerrainAsset1 estiver definido.</string>
+ <key>LocalTerrainTransform2OffsetU</key>
+ <string>Deslocamento horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 2), quando LocalTerrainAsset2 estiver definido.</string>
+ <key>LocalTerrainTransform2OffsetV</key>
+ <string>Deslocamento vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 2), quando LocalTerrainAsset2 estiver definido.</string>
+ <key>LocalTerrainTransform2Rotation</key>
+ <string>Rotação (em graus) da transformação de textura glTF KHR_texture_transform do terreno local (camada 2), quando LocalTerrainAsset2 estiver definido.</string>
+ <key>LocalTerrainTransform2ScaleU</key>
+ <string>Escala horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 2), quando LocalTerrainAsset2 estiver definido.</string>
+ <key>LocalTerrainTransform2ScaleV</key>
+ <string>Escala vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 2), quando LocalTerrainAsset2 estiver definido.</string>
+ <key>LocalTerrainTransform3OffsetU</key>
+ <string>Deslocamento horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 3), quando LocalTerrainAsset3 estiver definido.</string>
+ <key>LocalTerrainTransform3OffsetV</key>
+ <string>Deslocamento vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 3), quando LocalTerrainAsset3 estiver definido.</string>
+ <key>LocalTerrainTransform3Rotation</key>
+ <string>Rotação (em graus) da transformação de textura glTF KHR_texture_transform do terreno local (camada 3), quando LocalTerrainAsset3 estiver definido.</string>
+ <key>LocalTerrainTransform3ScaleU</key>
+ <string>Escala horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 3), quando LocalTerrainAsset3 estiver definido.</string>
+ <key>LocalTerrainTransform3ScaleV</key>
+ <string>Escala vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 3), quando LocalTerrainAsset3 estiver definido.</string>
+ <key>LocalTerrainTransform4OffsetU</key>
+ <string>Deslocamento horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 4), quando LocalTerrainAsset4 estiver definido.</string>
+ <key>LocalTerrainTransform4OffsetV</key>
+ <string>Deslocamento vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 4), quando LocalTerrainAsset4 estiver definido.</string>
+ <key>LocalTerrainTransform4Rotation</key>
+ <string>Rotação (em graus) da transformação de textura glTF KHR_texture_transform do terreno local (camada 4), quando LocalTerrainAsset4 estiver definido.</string>
+ <key>LocalTerrainTransform4ScaleU</key>
+ <string>Escala horizontal (coordenada U) da transformação de textura glTF KHR_texture_transform do terreno local (camada 4), quando LocalTerrainAsset4 estiver definido.</string>
+ <key>LocalTerrainTransform4ScaleV</key>
+ <string>Escala vertical (coordenada V) da transformação de textura glTF KHR_texture_transform do terreno local (camada 4), quando LocalTerrainAsset4 estiver definido.</string>
+ <key>LogChat</key>
+ <string>Obsoleto - esta configuração não é mais usada e não tem efeito.</string>
+ <key>LogChatIM</key>
+ <string>Obsoleto - esta configuração não é mais usada e não tem efeito.</string>
+ <key>LogChatTimestamp</key>
+ <string>Obsoleto - esta configuração não é mais usada e não tem efeito.</string>
+ <key>LogFileNamewithDate</key>
+ <string>Adicionar data aos logs de chat e IM no formato chat-AAAA-MM-DD e 'nome do arquivo IM'-AAAA-MM. Para ver logs antigos, vá em ..\Second Life\[nome de login].</string>
+ <key>LogInventoryDecline</key>
+ <string>Registrar no chat do sistema sempre que uma oferta de inventário for recusada.</string>
+ <key>LogMessages</key>
+ <string>Registrar tráfego de rede.</string>
+ <key>LogMetrics</key>
+ <string>Registrar métricas do viewer.</string>
+ <key>LogNearbyChat</key>
+ <string>Registrar mensagens do Chat Próximo em arquivo. Usado no lugar de LogChat, com valor padrão "1".</string>
+ <key>LogPerformance</key>
+ <string>Registrar análise de desempenho para uma execução específica do viewer.</string>
+ <key>LogShowHistory</key>
+ <string>Exibir histórico no log.</string>
+ <key>LogTextureDownloadsToSimulator</key>
+ <string>Enviar um resumo de informações de textura ao sim.</string>
+ <key>LogTextureDownloadsToViewerLog</key>
+ <string>Enviar detalhes de download de textura ao log do viewer.</string>
+ <key>LogTextureNetworkTraffic</key>
+ <string>Registrar tráfego de rede de texturas.</string>
+ <key>LogTimestamp</key>
+ <string>Carimbo de data/hora no log.</string>
+ <key>LogTimestampDate</key>
+ <string>Incluir data no carimbo de data/hora.</string>
+ <key>LogWearableAssetSave</key>
+ <string>Salvar cópia dos wearables salvos no diretório de log.</string>
+ <key>LoginContentVersion</key>
+ <string>Versão do conteúdo web da página de login a exibir.</string>
+ <key>LoginLocation</key>
+ <string>Preferência de local de login padrão ('last', 'home').</string>
+ <key>LoginPage</key>
+ <string>Página de autenticação de login.</string>
+ <key>LoginSRVTimeout</key>
+ <string>Duração em segundos do timeout da requisição SRV de login.</string>
+ <key>LosslessJ2CUpload</key>
+ <string>Usar compressão sem perdas para uploads de imagens pequenas.</string>
+ <key>MFAHash</key>
+ <string>Substituir o hash de estado MFA para autenticação.</string>
+ <key>MainWorkTime</key>
+ <string>Tempo máximo por quadro dedicado à fila de trabalho do loop principal (em milissegundos).</string>
+ <key>MainloopTimeoutDefault</key>
+ <string>Duração do timeout para detecção de travamento do loop principal durante teletransportes, login e logout, em segundos.</string>
+ <key>MainloopTimeoutStarted</key>
+ <string>Duração do timeout para detecção de travamento do loop principal quando logado e sem teletransportar, em segundos.</string>
+ <key>MapScale</key>
+ <string>Nível de zoom do mapa-múndi (pixels por região).</string>
+ <key>MapServerURL</key>
+ <string>Modelo de URL do mapa-múndi para localizar os ladrilhos do mapa.</string>
+ <key>MapShowEvents</key>
+ <string>Mostrar eventos no mapa-múndi.</string>
+ <key>MapShowGridCoords</key>
+ <string>Mostra/oculta as coordenadas da grade de cada região no mapa-múndi.</string>
+ <key>MapShowInfohubs</key>
+ <string>Mostrar infohubs no mapa-múndi.</string>
+ <key>MapShowLandForSale</key>
+ <string>Mostrar terrenos à venda no mapa-múndi.</string>
+ <key>MapShowPeople</key>
+ <string>Mostrar outros usuários no mapa-múndi.</string>
+ <key>MapShowTelehubs</key>
+ <string>Mostrar telehubs no mapa-múndi.</string>
+ <key>Marker</key>
+ <string>[NÃO USADO]</string>
+ <key>MarketplaceListingsLogging</key>
+ <string>Ativar saída de depuração associada à API de Listagens do Marketplace (SLM).</string>
+ <key>MarketplaceListingsSortOrder</key>
+ <string>Especifica a classificação das listagens do marketplace.</string>
+ <key>MarketplaceURL</key>
+ <string>URL do Marketplace.</string>
+ <key>MarketplaceURL_bodypartFemale</key>
+ <string>URL do Marketplace de Partes do Corpo Femininas.</string>
+ <key>MarketplaceURL_bodypartMale</key>
+ <string>URL do Marketplace de Partes do Corpo Masculinas.</string>
+ <key>MarketplaceURL_clothingFemale</key>
+ <string>URL do Marketplace de Roupas Femininas.</string>
+ <key>MarketplaceURL_clothingMale</key>
+ <string>URL do Marketplace de Roupas Masculinas.</string>
+ <key>MarketplaceURL_eyesFemale</key>
+ <string>URL do Marketplace de Olhos Femininos.</string>
+ <key>MarketplaceURL_eyesMale</key>
+ <string>URL do Marketplace de Olhos Masculinos.</string>
+ <key>MarketplaceURL_glovesFemale</key>
+ <string>URL do Marketplace de Luvas Femininas.</string>
+ <key>MarketplaceURL_glovesMale</key>
+ <string>URL do Marketplace de Luvas Masculinas.</string>
+ <key>MarketplaceURL_hairFemale</key>
+ <string>URL do Marketplace de Cabelos Femininos.</string>
+ <key>MarketplaceURL_hairMale</key>
+ <string>URL do Marketplace de Cabelos Masculinos.</string>
+ <key>MarketplaceURL_jacketFemale</key>
+ <string>URL do Marketplace de Jaquetas Femininas.</string>
+ <key>MarketplaceURL_jacketMale</key>
+ <string>URL do Marketplace de Jaquetas Masculinas.</string>
+ <key>MarketplaceURL_objectFemale</key>
+ <string>URL do Marketplace de Anexos Femininos.</string>
+ <key>MarketplaceURL_objectMale</key>
+ <string>URL do Marketplace de Anexos Masculinos.</string>
+ <key>MarketplaceURL_pantsFemale</key>
+ <string>URL do Marketplace de Calças Femininas.</string>
+ <key>MarketplaceURL_pantsMale</key>
+ <string>URL do Marketplace de Calças Masculinas.</string>
+ <key>MarketplaceURL_shapeFemale</key>
+ <string>URL do Marketplace de Formas Femininas.</string>
+ <key>MarketplaceURL_shapeMale</key>
+ <string>URL do Marketplace de Formas Masculinas.</string>
+ <key>MarketplaceURL_shirtFemale</key>
+ <string>URL do Marketplace de Camisas Femininas.</string>
+ <key>MarketplaceURL_shirtMale</key>
+ <string>URL do Marketplace de Camisas Masculinas.</string>
+ <key>MarketplaceURL_shoesFemale</key>
+ <string>URL do Marketplace de Sapatos Femininos.</string>
+ <key>MarketplaceURL_shoesMale</key>
+ <string>URL do Marketplace de Sapatos Masculinos.</string>
+ <key>MarketplaceURL_skinFemale</key>
+ <string>URL do Marketplace de Peles Femininas.</string>
+ <key>MarketplaceURL_skinMale</key>
+ <string>URL do Marketplace de Peles Masculinas.</string>
+ <key>MarketplaceURL_skirtFemale</key>
+ <string>URL do Marketplace de Saias Femininas.</string>
+ <key>MarketplaceURL_skirtMale</key>
+ <string>URL do Marketplace de Saias Masculinas.</string>
+ <key>MarketplaceURL_socksFemale</key>
+ <string>URL do Marketplace de Meias Femininas.</string>
+ <key>MarketplaceURL_socksMale</key>
+ <string>URL do Marketplace de Meias Masculinas.</string>
+ <key>MarketplaceURL_tattooFemale</key>
+ <string>URL do Marketplace de Tatuagens Femininas.</string>
+ <key>MarketplaceURL_tattooMale</key>
+ <string>URL do Marketplace de Tatuagens Masculinas.</string>
+ <key>MarketplaceURL_underpantsFemale</key>
+ <string>URL do Marketplace de Roupas Íntimas Femininas.</string>
+ <key>MarketplaceURL_underpantsMale</key>
+ <string>URL do Marketplace de Roupas Íntimas Masculinas.</string>
+ <key>MarketplaceURL_undershirtFemale</key>
+ <string>URL do Marketplace de Camisetas Íntimas Femininas.</string>
+ <key>MarketplaceURL_undershirtMale</key>
+ <string>URL do Marketplace de Camisetas Íntimas Masculinas.</string>
+ <key>MaterialsEveryoneCopy</key>
+ <string>Todos podem copiar o material GLTF recém-criado.</string>
+ <key>MaterialsNextOwnerCopy</key>
+ <string>O material GLTF recém-criado pode ser copiado pelo próximo proprietário.</string>
+ <key>MaterialsNextOwnerModify</key>
+ <string>O material GLTF recém-criado pode ser modificado pelo próximo proprietário.</string>
+ <key>MaterialsNextOwnerTransfer</key>
+ <string>O material GLTF recém-criado pode ser revendido ou doado pelo próximo proprietário.</string>
+ <key>MaterialsShareWithGroup</key>
+ <string>Materiais GLTF recém-criados são compartilhados com o grupo ativo no momento.</string>
+ <key>MaxAttachmentComplexity</key>
+ <string>Limite de peso de renderização do anexo.</string>
+ <key>MaxDragDistance</key>
+ <string>Distância máxima de translação permitida em uma única operação da ferramenta de mover (metros a partir do ponto inicial).</string>
+ <key>MaxFPS</key>
+ <string>Configuração OBSOLETA e NÃO UTILIZADA.</string>
+ <key>MaxHeapSize</key>
+ <string>Tamanho máximo do heap em builds de 32 bits (GB).</string>
+ <key>MaxHeapSize64</key>
+ <string>Tamanho máximo do heap em builds de 64 bits (GB).</string>
+ <key>MaxPersistentNotifications</key>
+ <string>Quantidade máxima de notificações persistentes.</string>
+ <key>MaxSelectDistance</key>
+ <string>Distância máxima de seleção permitida (metros a partir do avatar).</string>
+ <key>MaxWearableWaitTime</key>
+ <string>Máximo de segundos a aguardar a busca dos assets de wearables.</string>
+ <key>MePanelOpened</key>
+ <string>Indica que o painel Eu foi aberto pelo menos uma vez após a instalação do Viewer.</string>
+ <key>MediaAutoPlayHuds</key>
+ <string>Reproduzir automaticamente mídia de HUD.</string>
+ <key>MediaControlFadeTime</key>
+ <string>Tempo (em segundos) em que o controle de mídia desaparece.</string>
+ <key>MediaControlTimeout</key>
+ <string>Tempo (em segundos) para os controles de mídia desaparecerem sem atividade do mouse.</string>
+ <key>MediaEnablePopups</key>
+ <string>Se verdadeiro, ativa links direcionados e javascript na mídia para abrir novas janelas do navegador de mídia sem confirmação.</string>
+ <key>MediaFirstClickInteract</key>
+ <string>Esta configuração controla qual mídia (após carregada) não exige um primeiro clique para focar antes que a interação possa começar. Isso permite que cliques sejam passados diretamente à mídia, ignorando o requisito de clique de foco. É um campo de bits, com valores pré-calculados: Desativado=0; Apenas HUDs em uso=1; Objetos próprios=2; Objetos de amigos=4; Objetos de grupo=8; Objetos do dono do terreno=16; Qualquer objeto=32767; Todo MOAP=32768. Para detalhes completos, veja lltoolpie.h enum MediaFirstClickTypes.</string>
+ <key>MediaOnAPrimUI</key>
+ <string>Se deve ou não mostrar a interface de "compartilhamento de links".</string>
+ <key>MediaPerformanceManagerDebug</key>
+ <string>Se deve mostrar dados de depuração do gerenciador de desempenho de mídia na lista de mídia próxima.</string>
+ <key>MediaPluginDebugging</key>
+ <string>Ativar mensagens de depuração que podem ajudar a diagnosticar problemas de mídia (AVISO: pode reduzir o desempenho).</string>
+ <key>MediaRollOffMax</key>
+ <string>Distância na qual o volume da mídia é definido como 0.</string>
+ <key>MediaRollOffMin</key>
+ <string>Ajusta a distância na qual a atenuação da mídia começa.</string>
+ <key>MediaRollOffRate</key>
+ <string>Multiplicador para alterar a taxa de atenuação da mídia.</string>
+ <key>MediaShowOnOthers</key>
+ <string>Se deve ou não mostrar mídia em outros avatares.</string>
+ <key>MediaShowOutsideParcel</key>
+ <string>Se deve ou não mostrar mídia de fora da parcela atual.</string>
+ <key>MediaShowWithinParcel</key>
+ <string>Se deve ou não mostrar mídia dentro da parcela atual.</string>
+ <key>MediaSoundsEarLocation</key>
+ <string>Localização do ouvido virtual para mídia e sons.</string>
+ <key>MediaTentativeAutoPlay</key>
+ <string>Esta é uma flag tentativa que pode ser temporariamente desativada pelo usuário, até que ele se teletransporte.</string>
+ <key>MemoryLogFrequency</key>
+ <string>Segundos entre a exibição da memória no log (0 para nunca).</string>
+ <key>MemoryPrivatePoolEnabled</key>
+ <string>(Obsoleto) Ativar o gerenciamento de pool de memória privada.</string>
+ <key>MemoryPrivatePoolSize</key>
+ <string>(Obsoleto) Tamanho do pool de memória privada em MB (valor mínimo: 256).</string>
+ <key>MenuAccessKeyTime</key>
+ <string>Tempo (segundos) no qual a tecla de menu deve ser tocada para mover o foco para a barra de menu.</string>
+ <key>MenuSearch</key>
+ <string>Mostrar/ocultar o campo 'Buscar menus'.</string>
+ <key>Mesh2MaxConcurrentRequests</key>
+ <string>Número de conexões a usar para carregar malhas.</string>
+ <key>MeshBytesPerTriangle</key>
+ <string>Aproximação de bytes por triângulo a usar para determinar o custo de streaming de malha.</string>
+ <key>MeshEnabled</key>
+ <string>Expor a interface para a funcionalidade de malha (pode exigir reinício para ter efeito).</string>
+ <key>MeshImportUseSLM</key>
+ <string>Usar cópia em cache do último upload de um dae, se disponível, em vez de carregar o arquivo dae do zero.</string>
+ <key>MeshMaxConcurrentRequests</key>
+ <string>Número de conexões a usar para carregar malhas (sistema legado).</string>
+ <key>MeshMetaDataDiscount</key>
+ <string>Número de bytes a deduzir para metadados ao determinar o custo de streaming.</string>
+ <key>MeshMinimumByteSize</key>
+ <string>Número mínimo de bytes por bloco de LoD ao determinar o custo de streaming.</string>
+ <key>MeshTriangleBudget</key>
+ <string>Orçamento alvo de triângulos visíveis a usar ao estimar o custo de streaming.</string>
+ <key>MeshUploadFakeErrors</key>
+ <string>Forçar erros de upload (para testes).</string>
+ <key>MeshUploadLogXML</key>
+ <string>Log XML detalhado no upload de malha.</string>
+ <key>MeshUploadTimeOut</key>
+ <string>Tempo máximo em segundos para o llcurl executar uma requisição de upload de malha.</string>
+ <key>MeshUseGetMesh1</key>
+ <string>Se TRUE, usar a capacidade legada GetMesh para requisições de download de malha. Semidinâmico (lido nas travessias de região).</string>
+ <key>MeshUseHttpRetryAfter</key>
+ <string>Se TRUE, usar os cabeçalhos de resposta Retry-After ao reagendar uma requisição de malha que falha com status HTTP 503. Estático.</string>
+ <key>MigrateCacheDirectory</key>
+ <string>Verificar se há versão antiga do cache em disco para migrar para a localização atual.</string>
+ <key>MinObjectsForUnlinkConfirm</key>
+ <string>Quantidade mínima de objetos em um linkset para mostrar o diálogo de confirmação.</string>
+ <key>MinWindowHeight</key>
+ <string>Altura mínima da janela do viewer SL em pixels.</string>
+ <key>MinWindowWidth</key>
+ <string>Largura mínima da janela do viewer SL em pixels.</string>
+ <key>MiniMapAutoCenter</key>
+ <string>Centralizar o ponto focal do minimapa.</string>
+ <key>MiniMapPrimMaxRadius</key>
+ <string>Raio do maior prim a mostrar no minimapa. Aumentar além de 256 pode causar lag no cliente.</string>
+ <key>MiniMapRotate</key>
+ <string>Girar o mapa-múndi em miniatura na direção do avatar.</string>
+ <key>MiniMapScale</key>
+ <string>Nível de zoom do mapa-múndi em miniatura (pixels por região).</string>
+ <key>MiniMapShowPropertyLines</key>
+ <string>Se deve ou não mostrar os limites das parcelas no minimapa.</string>
+ <key>ModelUploadFolder</key>
+ <string>Todos os uploads de modelos serão armazenados neste diretório (UUID).</string>
+ <key>MouseMoon</key>
+ <string>Controlar a posição da lua com o mouse no modo mouselook (depuração do céu).</string>
+ <key>MouseSensitivity</key>
+ <string>Controla a responsividade do mouse no modo mouselook (fração ou múltiplo da sensibilidade padrão do mouse).</string>
+ <key>MouseSmooth</key>
+ <string>Suaviza o movimento do mouse no modo mouselook.</string>
+ <key>MouseSun</key>
+ <string>Controlar a posição do sol com o mouse no modo mouselook (depuração do céu).</string>
+ <key>MouseWarpMode</key>
+ <string>Controla o reposicionamento do mouse para o centro da tela durante o alt-zoom e o mouselook. Útil com certos dispositivos de entrada, programas de compartilhamento de mouse como Synergy ou ao rodar sob Parallels. 0 - automático, 1 - ligado, 2 - desligado.</string>
+ <key>MultiModeDoubleClickFolder</key>
+ <string>Define a ação do duplo clique em pasta na visão de múltiplas pastas (0 - expande e recolhe a pasta, 1 - abre uma nova janela, 2 - permanece no floater atual mas alterna para SFV).</string>
+ <key>MuteAmbient</key>
+ <string>Efeitos de som ambiente, como o ruído do vento, tocam em volume 0.</string>
+ <key>MuteAudio</key>
+ <string>Todo o áudio toca em volume 0 (o áudio em streaming ainda consome largura de banda, por exemplo).</string>
+ <key>MuteListLimit</key>
+ <string>Número máximo de entradas na lista de silenciados.</string>
+ <key>MuteMedia</key>
+ <string>A mídia toca em volume 0 (o áudio em streaming ainda consome largura de banda).</string>
+ <key>MuteMusic</key>
+ <string>A música toca em volume 0 (o áudio em streaming ainda consome largura de banda).</string>
+ <key>MuteSounds</key>
+ <string>Os efeitos sonoros tocam em volume 0.</string>
+ <key>MuteUI</key>
+ <string>Os efeitos sonoros da interface tocam em volume 0.</string>
+ <key>MuteVoice</key>
+ <string>A voz toca em volume 0 (o áudio em streaming ainda consome largura de banda).</string>
+ <key>MuteWhenMinimized</key>
+ <string>Silenciar o áudio quando a janela do SL está minimizada.</string>
+ <key>MyOutfitsAutofill</key>
+ <string>Sempre preencher automaticamente Minhas Roupas a partir da biblioteca quando vazias (caso contrário, acontece apenas uma vez).</string>
+ <key>NameTagDebugAVRezState</key>
+ <string>Mostrar o estado de carregamento do avatar na etiqueta de nome.</string>
+ <key>NameTagShowDisplayNames</key>
+ <string>Mostrar nomes de exibição nos rótulos de nome.</string>
+ <key>NameTagShowFriends</key>
+ <string>Destacar as etiquetas de nome dos seus amigos.</string>
+ <key>NameTagShowGroupTitles</key>
+ <string>Mostrar títulos de grupo nos rótulos de nome.</string>
+ <key>NameTagShowUsernames</key>
+ <string>Mostrar nomes de usuário nas etiquetas de nome dos avatares.</string>
+ <key>NametagOverWater</key>
+ <string>Renderizar a etiqueta de nome sobre a água transparente enquanto a câmera está acima da água.</string>
+ <key>NavBarShowCoordinates</key>
+ <string>Mostrar coordenadas na barra de navegação.</string>
+ <key>NavBarShowParcelProperties</key>
+ <string>Mostrar ícones de propriedade da parcela na barra de navegação.</string>
+ <key>NavigationBarRatio</key>
+ <string>Proporção entre a largura do painel de navegação e a largura total da pilha de navegação.</string>
+ <key>NearMeRange</key>
+ <string>Raio de busca para avatares próximos.</string>
+ <key>NearbyChatIsNotCollapsed</key>
+ <string>Salvar estado expandido/recolhido do chat próximo entre sessões.</string>
+ <key>NearbyChatIsNotTornOff</key>
+ <string>Salvar estado destacado do chat próximo entre sessões.</string>
+ <key>NearbyListHideUsernames</key>
+ <string>Mostrar tanto o nome de exibição quanto o nome de usuário na lista de pessoas próximas.</string>
+ <key>NearbyListShowIcons</key>
+ <string>Mostrar/ocultar ícones de pessoas na lista de pessoas próximas.</string>
+ <key>NearbyListShowMap</key>
+ <string>Mostrar/ocultar o mapa acima da lista de pessoas próximas.</string>
+ <key>NearbyPeopleSortOrder</key>
+ <string>Especifica a ordem de classificação de pessoas próximas (0 = por nome, 3 = por distância, 4 = mais recentes).</string>
+ <key>NearbyToastFadingTime</key>
+ <string>Número de segundos em que um toast de chat próximo desaparece.</string>
+ <key>NearbyToastLifeTime</key>
+ <string>Número de segundos em que um toast de chat próximo existe.</string>
+ <key>NewCacheLocation</key>
+ <string>Alterar a localização do cache em disco local para este.</string>
+ <key>NewCacheLocationTopFolder</key>
+ <string>Alterar a pasta raiz do cache em disco local para esta.</string>
+ <key>NewObjectCreationThrottle</key>
+ <string>Número máximo de novos objetos criados por quadro, -1 para desativar este limite.</string>
+ <key>NewObjectCreationThrottleDelayTime</key>
+ <string>Tempo em segundos para o NewObjectCreationThrottle entrar em vigor após a tela de progresso ser removida.</string>
+ <key>NextLoginLocation</key>
+ <string>Local para fazer login nesta sessão - definido pela linha de comando ou pelo painel de login, limpo após um login bem-sucedido.</string>
+ <key>NextOwnerCopy</key>
+ <string>(obsoleto) Objetos recém-criados podem ser copiados pelo próximo proprietário.</string>
+ <key>NextOwnerModify</key>
+ <string>(obsoleto) Objetos recém-criados podem ser modificados pelo próximo proprietário.</string>
+ <key>NextOwnerTransfer</key>
+ <string>(obsoleto) Objetos recém-criados podem ser revendidos ou doados pelo próximo proprietário.</string>
+ <key>NoAudio</key>
+ <string>Desativar reprodução de áudio.</string>
+ <key>NoHardwareProbe</key>
+ <string>Desativar a sondagem de hardware.</string>
+ <key>NoInventoryLibrary</key>
+ <string>(Obsoleto) Não solicitar a biblioteca de inventário.</string>
+ <key>NoPreload</key>
+ <string>Desativar o pré-carregamento de som e imagem.</string>
+ <key>NoQuickTime</key>
+ <string>Desativar o QuickTime para uma execução específica do viewer.</string>
+ <key>NoVerifySSLCert</key>
+ <string>Não verificar os pares SSL.</string>
+ <key>NonInteractive</key>
+ <string>Executar em um modo semi-headless em que apenas o login e o logout precisam funcionar.</string>
+ <key>NonvisibleObjectsInMemoryTime</key>
+ <string>Número de quadros que objetos não visíveis permanecem na memória antes de serem removidos. 0 significa máximo.</string>
+ <key>NotMovingHintTimeout</key>
+ <string>Número de segundos a aguardar o residente se mover antes de exibir a dica de movimento.</string>
+ <key>NotecardsEveryoneCopy</key>
+ <string>Todos podem copiar o notecard recém-criado.</string>
+ <key>NotecardsNextOwnerCopy</key>
+ <string>Notecards recém-criados podem ser copiados pelo próximo proprietário.</string>
+ <key>NotecardsNextOwnerModify</key>
+ <string>Notecards recém-criados podem ser modificados pelo próximo proprietário.</string>
+ <key>NotecardsNextOwnerTransfer</key>
+ <string>Notecards recém-criados podem ser revendidos ou doados pelo próximo proprietário.</string>
+ <key>NotecardsShareWithGroup</key>
+ <string>Notecards recém-criados são compartilhados com o grupo ativo no momento.</string>
+ <key>NotificationChannelHeightRatio</key>
+ <string>Proporção entre o canal de notificação e a visão do mundo (0,0 - sempre mostrar 1 notificação, 1,0 - proporção máxima).</string>
+ <key>NotificationChannelRightMargin</key>
+ <string>Espaço entre os toasts e a borda direita da área onde eles podem aparecer.</string>
+ <key>NotificationConferenceIMOptions</key>
+ <string>Especifica como a interface responde a notificações de IM de conferência. Valores permitidos: [openconversations,toast,flash,noaction].</string>
+ <key>NotificationFriendIMOptions</key>
+ <string>Especifica como a interface responde a notificações de IM de amigos. Valores permitidos: [openconversations,toast,flash,noaction].</string>
+ <key>NotificationGroupChatOptions</key>
+ <string>Especifica como a interface responde a notificações de chat de grupo. Valores permitidos: [openconversations,toast,flash,noaction].</string>
+ <key>NotificationNearbyChatOptions</key>
+ <string>Especifica como a interface responde a notificações de chat próximo. Valores permitidos: [openconversations,toast,flash,noaction].</string>
+ <key>NotificationNonFriendIMOptions</key>
+ <string>Especifica como a interface responde a notificações de IM de não amigos. Valores permitidos: [openconversations,toast,flash,noaction].</string>
+ <key>NotificationObjectIMOptions</key>
+ <string>Especifica como a interface responde a notificações de IM de objetos. Valores permitidos: [openconversations,toast,flash,noaction].</string>
+ <key>NotificationTipToastLifeTime</key>
+ <string>Número de segundos em que um toast de dica de notificação existe.</string>
+ <key>NotificationToastLifeTime</key>
+ <string>Número de segundos em que um toast de notificação existe.</string>
+ <key>NotifyMoneyChange</key>
+ <string>Exibir notificações pop-up para todas as transações de L$.</string>
+ <key>NotifyMoneyReceived</key>
+ <string>Exibir notificações pop-up ao receber L$.</string>
+ <key>NotifyMoneySpend</key>
+ <string>Exibir notificações pop-up ao gastar L$.</string>
+ <key>NotifyTipDuration</key>
+ <string>Tempo em que as dicas de notificação permanecem na tela (segundos).</string>
+ <key>NumSessions</key>
+ <string>Número de logins bem-sucedidos no Second Life.</string>
+ <key>NvAPICreateApplicationProfile</key>
+ <string>Criar perfil de aplicativo NVIDIA para configurações otimizadas.</string>
+ <key>OSHibernationMode</key>
+ <string>Se deve impedir que o SO hiberne. 0 - pode hibernar; 1 - não pode hibernar, pode desligar a tela; 2 - não pode hibernar, não pode desligar a tela.</string>
+ <key>ObjectCacheEnabled</key>
+ <string>Ativar o cache de objetos.</string>
+ <key>ObjectCostHighColor</key>
+ <string>Cor para objeto com alto custo de objeto.</string>
+ <key>ObjectCostHighThreshold</key>
+ <string>Limiar a partir do qual o custo do objeto é considerado alto (exibido em vermelho).</string>
+ <key>ObjectCostLowColor</key>
+ <string>Cor para objeto com baixo custo de objeto.</string>
+ <key>ObjectCostMidColor</key>
+ <string>Cor para objeto com custo de objeto médio.</string>
+ <key>ObjectInspectorTooltipDelay</key>
+ <string>Segundos antes de exibir a dica do inspetor de objetos.</string>
+ <key>ObjectsEveryoneCopy</key>
+ <string>Todos podem copiar o objeto recém-criado.</string>
+ <key>ObjectsNextOwnerCopy</key>
+ <string>Objetos recém-criados podem ser copiados pelo próximo proprietário.</string>
+ <key>ObjectsNextOwnerModify</key>
+ <string>Objetos recém-criados podem ser modificados pelo próximo proprietário.</string>
+ <key>ObjectsNextOwnerTransfer</key>
+ <string>Objetos recém-criados podem ser revendidos ou doados pelo próximo proprietário.</string>
+ <key>ObjectsShareWithGroup</key>
+ <string>Objetos recém-criados são compartilhados com o grupo ativo no momento.</string>
+ <key>ObscureBalanceInStatusBar</key>
+ <string>Se verdadeiro, o saldo será mostrado como '*'.</string>
+ <key>OctreeAlphaDistanceFactor</key>
+ <string>Multiplicador na distância de objetos alpha para determinar o tamanho do nó da octree. Os dois primeiros parâmetros estão atualmente sem uso. O terceiro é a distância na qual realizar a ordenação detalhada de alpha.</string>
+ <key>OctreeAttachmentSizeFactor</key>
+ <string>Multiplicador no tamanho do anexo para determinar o tamanho do nó da octree.</string>
+ <key>OctreeDistanceFactor</key>
+ <string>Multiplicador na distância para determinar o tamanho do nó da octree.</string>
+ <key>OctreeMaxNodeCapacity</key>
+ <string>Número máximo de elementos a armazenar em um único nó da octree.</string>
+ <key>OctreeMinimumNodeSize</key>
+ <string>Tamanho mínimo de qualquer nó da octree.</string>
+ <key>OctreeStaticObjectSizeFactor</key>
+ <string>Multiplicador no tamanho de objetos estáticos para determinar o tamanho do nó da octree.</string>
+ <key>OpenDebugStatAdvanced</key>
+ <string>Expandir a exibição de estatísticas avançadas de desempenho.</string>
+ <key>OpenDebugStatBasic</key>
+ <string>Expandir a exibição de estatísticas básicas de desempenho.</string>
+ <key>OpenDebugStatMemory</key>
+ <string>Expandir a exibição de estatísticas de uso de memória.</string>
+ <key>OpenDebugStatNet</key>
+ <string>Expandir a exibição de estatísticas de desempenho de rede.</string>
+ <key>OpenDebugStatPathfinding</key>
+ <string>Expandir a exibição de estatísticas de desempenho de pathfinding.</string>
+ <key>OpenDebugStatPhysicsDetails</key>
+ <string>Expandir a exibição de estatísticas de detalhes de física.</string>
+ <key>OpenDebugStatRender</key>
+ <string>Expandir a exibição de estatísticas de desempenho de renderização.</string>
+ <key>OpenDebugStatSim</key>
+ <string>Expandir a exibição de estatísticas de desempenho do simulador.</string>
+ <key>OpenDebugStatSimTime</key>
+ <string>Expandir a exibição de estatísticas de tempo do simulador.</string>
+ <key>OpenDebugStatSimTimeDetails</key>
+ <string>Expandir a exibição de estatísticas de detalhes de tempo do simulador.</string>
+ <key>OpenDebugStatTexture</key>
+ <string>Expandir a exibição de estatísticas de desempenho de textura.</string>
+ <key>OpenDebugStatVoice</key>
+ <string>Expandir a exibição de estatísticas de voz (WebRTC).</string>
+ <key>OpenDebugStatVoiceIncoming</key>
+ <string>Expandir a exibição de estatísticas de áudio recebido (voz).</string>
+ <key>OpenDebugStatVoiceOutgoing</key>
+ <string>Expandir a exibição de estatísticas de áudio enviado (voz).</string>
+ <key>OpenIMOnVoice</key>
+ <string>Abrir a janela de IM correspondente ao conectar a uma chamada de voz.</string>
+ <key>OpenSidePanelsInFloaters</key>
+ <string>Se verdadeiro, sempre abrirá o conteúdo do painel lateral em um floater.</string>
+ <key>OutBandwidth</key>
+ <string>Limite de largura de banda de saída (bps).</string>
+ <key>OutfitGallerySortOrder</key>
+ <string>Classificação da galeria: 0 - ordenar roupas por nome, 1 - imagens primeiro, 2 - favoritos primeiro.</string>
+ <key>OutfitListFilterFullList</key>
+ <string>0 - mostrar apenas correspondências. 1 - mostrar todos os itens da roupa desde que a roupa ou um item dentro dela corresponda.</string>
+ <key>OutfitListSortOrder</key>
+ <string>Como a lista de roupas no floater do avatar é classificada. 0 - por nome, 1 - favoritos no topo.</string>
+ <key>OutfitOperationsTimeout</key>
+ <string>Timeout para operações relacionadas a roupas.</string>
+ <key>OverflowToastHeight</key>
+ <string>Altura de um toast de transbordamento.</string>
+ <key>OverlayTitle</key>
+ <string>Controla a mensagem de marca d'água exibida na tela quando "ShowOverlayTitle" está ativado (uma palavra, sublinhados viram espaços).</string>
+ <key>PBRUploadFolder</key>
+ <string>Todos os uploads PBR serão armazenados neste diretório (UUID).</string>
+ <key>PTTCurrentlyEnabled</key>
+ <string>Usar o modo Push to Talk.</string>
+ <key>PacketDropPercentage</key>
+ <string>Percentual de pacotes descartados pelo cliente.</string>
+ <key>ParcelMediaAutoPlayEnable</key>
+ <string>Reproduzir automaticamente a mídia da parcela quando disponível. 0 - Não reproduzir; 1 - Reproduzir; 2 - Perguntar.</string>
+ <key>ParticipantListShowIcons</key>
+ <string>Mostrar/ocultar ícones de pessoas na lista de participantes.</string>
+ <key>PathfindingAmbiance</key>
+ <string>Ambiência das exibições iluminadas da navmesh de pathfinding.</string>
+ <key>PathfindingBoundaryEdge</key>
+ <string>Cor de uma borda limítrofe (não atravessável) ao exibir a navmesh de pathfinding.</string>
+ <key>PathfindingConnectedEdge</key>
+ <string>Cor de uma borda conectada (atravessável) ao exibir a navmesh de pathfinding.</string>
+ <key>PathfindingExclusion</key>
+ <string>Cor dos volumes de exclusão ao exibir os tipos de objeto da navmesh de pathfinding.</string>
+ <key>PathfindingFaceColor</key>
+ <string>Cor das faces ao exibir a visão padrão da navmesh de pathfinding.</string>
+ <key>PathfindingHeatColorBase</key>
+ <string>Cor do valor menos caminhável ao exibir a navmesh de pathfinding como mapa de calor.</string>
+ <key>PathfindingHeatColorMax</key>
+ <string>Cor do valor mais caminhável ao exibir a navmesh de pathfinding como mapa de calor.</string>
+ <key>PathfindingLineOffset</key>
+ <string>Deslocamento de profundidade dos contornos de volume na exibição de pathfinding.</string>
+ <key>PathfindingLineWidth</key>
+ <string>Largura dos contornos de volume na exibição da navmesh de pathfinding.</string>
+ <key>PathfindingMaterial</key>
+ <string>Cor dos volumes de material ao exibir os tipos de objeto da navmesh de pathfinding.</string>
+ <key>PathfindingNavMeshClear</key>
+ <string>Cor de fundo ao exibir a navmesh de pathfinding.</string>
+ <key>PathfindingObstacle</key>
+ <string>Cor dos objetos de obstáculo estático ao exibir os tipos de objeto da navmesh de pathfinding.</string>
+ <key>PathfindingRetrieveNeighboringRegion</key>
+ <string>Baixar uma região vizinha ao visualizar uma navmesh de pathfinding (o valor padrão 99 significa não baixar vizinhos).</string>
+ <key>PathfindingTestPathColor</key>
+ <string>Cor do caminho de teste de pathfinding quando o caminho é válido.</string>
+ <key>PathfindingTestPathInvalidEndColor</key>
+ <string>Cor do ponto final da ferramenta de teste de pathfinding quando o caminho é inválido.</string>
+ <key>PathfindingTestPathValidEndColor</key>
+ <string>Cor do ponto final da ferramenta de teste de pathfinding quando o caminho é válido.</string>
+ <key>PathfindingWalkable</key>
+ <string>Cor dos objetos caminháveis ao exibir os tipos de objeto da navmesh de pathfinding.</string>
+ <key>PathfindingWaterColor</key>
+ <string>Cor do plano de água ao exibir a navmesh de pathfinding.</string>
+ <key>PathfindingXRayOpacity</key>
+ <string>Opacidade das linhas de raio-x na exibição de pathfinding.</string>
+ <key>PathfindingXRayTint</key>
+ <string>Quanto escurecer/clarear as linhas de raio-x na exibição de pathfinding.</string>
+ <key>PathfindingXRayWireframe</key>
+ <string>Renderizar o raio-x da navmesh de pathfinding como wireframe.</string>
+ <key>PerAccountSettingsFile</key>
+ <string>Nome do arquivo de configurações persistidas do cliente (por usuário).</string>
+ <key>PerfStatsCaptureEnabled</key>
+ <string>Ativar/desativar dados de tempo de renderização para dar suporte ao autotune.</string>
+ <key>PermissionsCautionEnabled</key>
+ <string>Quando ativado, altera o tratamento de requisições de permissão de script para ajudar a evitar a concessão acidental de certas permissões, como a de débito.</string>
+ <key>PermissionsCautionNotifyBoxHeight</key>
+ <string>Altura das mensagens de notificação do estilo de cautela.</string>
+ <key>PickerContextOpacity</key>
+ <string>Controla a opacidade geral do frustum de contexto que conecta os seletores de cor e textura às suas amostras.</string>
+ <key>PicksPerSecondMouseMoving</key>
+ <string>Com que frequência realizar seleções de hover enquanto o mouse se move (seleções por segundo).</string>
+ <key>PicksPerSecondMouseStationary</key>
+ <string>Com que frequência realizar seleções de hover enquanto o mouse está parado (seleções por segundo).</string>
+ <key>PieMenuLineWidth</key>
+ <string>Largura das linhas na exibição do menu de pizza (pixels).</string>
+ <key>PingInterpolate</key>
+ <string>Extrapolar a posição do objeto ao longo do vetor de velocidade com base no atraso de ping.</string>
+ <key>PitchFromMousePosition</key>
+ <string>Faixa vertical na qual a cabeça do avatar acompanha a posição do mouse (graus de rotação da cabeça do topo da janela até a base).</string>
+ <key>PlainTextChatHistory</key>
+ <string>Ativar/desativar o estilo de histórico de chat em texto simples.</string>
+ <key>PlayChatAnim</key>
+ <string>Seu avatar reproduz a animação de fala sempre que você fala, grita ou sussurra algo no chat próximo.</string>
+ <key>PlaySoundChatMention</key>
+ <string>Reproduz um som quando você é mencionado em um chat.</string>
+ <key>PlaySoundConferenceIM</key>
+ <string>Reproduz um som quando um IM de conferência é recebido.</string>
+ <key>PlaySoundFriendIM</key>
+ <string>Reproduz um som quando o IM de um amigo é recebido.</string>
+ <key>PlaySoundGroupChatIM</key>
+ <string>Reproduz um som quando um IM de chat de grupo é recebido.</string>
+ <key>PlaySoundIncomingVoiceCall</key>
+ <string>Reproduz um som ao receber uma chamada de voz.</string>
+ <key>PlaySoundInventoryOffer</key>
+ <string>Reproduz um som ao receber uma oferta de inventário.</string>
+ <key>PlaySoundNearbyChatIM</key>
+ <string>Reproduz um som quando um IM de chat próximo é recebido.</string>
+ <key>PlaySoundNewConversation</key>
+ <string>Reproduz um som ao ter uma nova conversa.</string>
+ <key>PlaySoundNonFriendIM</key>
+ <string>Reproduz um som quando o IM de um não amigo é recebido.</string>
+ <key>PlaySoundObjectIM</key>
+ <string>Reproduz um som quando um IM de um objeto é recebido.</string>
+ <key>PlaySoundTeleportOffer</key>
+ <string>Reproduz um som ao receber uma oferta de teletransporte.</string>
+ <key>PlayTypingAnim</key>
+ <string>Seu avatar reproduz a animação de digitação sempre que você digita na barra de chat.</string>
+ <key>PluginAttachDebuggerToPlugins</key>
+ <string>Se verdadeiro, anexar uma sessão de depurador a cada processo de plugin ao ser iniciado.</string>
+ <key>PluginInstancesCPULimit</key>
+ <string>Quantidade total de uso de CPU dos plugins antes que os plugins no mundo comecem a ser rebaixados para a prioridade "slideshow". Defina 0 para desativar esta verificação.</string>
+ <key>PluginInstancesLow</key>
+ <string>Limite do número de plugins de mídia no mundo que rodarão com prioridade "baixa".</string>
+ <key>PluginInstancesNormal</key>
+ <string>Limite do número de plugins de mídia no mundo que rodarão com prioridade "normal" ou superior.</string>
+ <key>PluginInstancesTotal</key>
+ <string>Limite rígido do número de plugins que serão instanciados ao mesmo tempo para mídia no mundo.</string>
+ <key>PluginUseReadThread</key>
+ <string>Usar uma thread separada para ler mensagens recebidas dos plugins.</string>
+ <key>PoolSizeAIS</key>
+ <string>Tamanho do pool de corrotinas para o AIS.</string>
+ <key>PoolSizeAssetStorage</key>
+ <string>Tamanho do pool de corrotinas para requisições de AssetStorage.</string>
+ <key>PoolSizeUpload</key>
+ <string>Tamanho do pool de corrotinas para upload.</string>
+ <key>PostFirstLoginIntroURL</key>
+ <string>URL da apresentação de introdução após o primeiro login de novos usuários.</string>
+ <key>PostFirstLoginIntroViewed</key>
+ <string>Flag que indica se o usuário viu a apresentação de introdução após o primeiro login de novos usuários.</string>
+ <key>PrecachingDelay</key>
+ <string>Atraso ao fazer login para carregar o mundo antes de exibi-lo (segundos).</string>
+ <key>PreferredBrowserBehavior</key>
+ <string>Usar o navegador do sistema para qualquer link (0), usar o navegador embutido para links do SL e o do sistema para os demais (1) ou usar apenas o navegador embutido (2).</string>
+ <key>PreferredMaturity</key>
+ <string>Configuração do nível de maturidade preferido pelo usuário (constantes em indra_constants.h).</string>
+ <key>PresetCameraActive</key>
+ <string>Nome da preferência selecionada no momento.</string>
+ <key>PresetGraphicActive</key>
+ <string>Nome da preferência selecionada no momento.</string>
+ <key>PreviewAmbientColor</key>
+ <string>Cor ambiente da renderização de pré-visualização.</string>
+ <key>PreviewDiffuse0</key>
+ <string>Cor difusa da luz de pré-visualização 0.</string>
+ <key>PreviewDiffuse1</key>
+ <string>Cor difusa da luz de pré-visualização 1.</string>
+ <key>PreviewDiffuse2</key>
+ <string>Cor difusa da luz de pré-visualização 2.</string>
+ <key>PreviewDirection0</key>
+ <string>Direção da luz 0 para a renderização de pré-visualização.</string>
+ <key>PreviewDirection1</key>
+ <string>Direção da luz 1 para a renderização de pré-visualização.</string>
+ <key>PreviewDirection2</key>
+ <string>Direção da luz 2 para a renderização de pré-visualização.</string>
+ <key>PreviewSpecular0</key>
+ <string>Cor especular da luz de pré-visualização 0.</string>
+ <key>PreviewSpecular1</key>
+ <string>Cor especular da luz de pré-visualização 1.</string>
+ <key>PreviewSpecular2</key>
+ <string>Cor especular da luz de pré-visualização 2.</string>
+ <key>PreviousInstallChecked</key>
+ <string>Se o viewer verificou a instalação anterior no mesmo canal para o NSIS.</string>
+ <key>PreviousScreenshotForReport</key>
+ <string>Usar captura de tela anterior para denúncia de abuso.</string>
+ <key>PrimMediaControlsUseHoverControlSet</key>
+ <string>Se passar o mouse sobre a mídia de um prim usa os controles mínimos de "hover" ou o conjunto de controles definido.</string>
+ <key>PrimMediaDragNDrop</key>
+ <string>Ativar arrastar e soltar de URLs sobre faces de prim.</string>
+ <key>PrimMediaMasterEnabled</key>
+ <string>Se a Mídia em um Prim está ou não ativada.</string>
+ <key>PrimMediaMaxRetries</key>
+ <string>Número máximo de tentativas para consultas de mídia.</string>
+ <key>PrimMediaMaxRoundRobinQueueSize</key>
+ <string>Número máximo de objetos para os quais o viewer atualizará a mídia continuamente.</string>
+ <key>PrimMediaMaxSortedQueueSize</key>
+ <string>Número máximo de objetos para os quais o viewer carregará mídia inicialmente.</string>
+ <key>PrimMediaRequestQueueDelay</key>
+ <string>Atraso do temporizador para buscar mídia da fila (em segundos).</string>
+ <key>PrimMediaRetryTimerDelay</key>
+ <string>Atraso do temporizador para repetir consultas de mídia (em segundos).</string>
+ <key>PrimTextMaxDrawDistance</key>
+ <string>Distância máxima de desenho além da qual PRIM_TEXT não será renderizado.</string>
+ <key>ProbeHardwareOnStartup</key>
+ <string>Consultar a configuração de hardware atual na inicialização do aplicativo.</string>
+ <key>PurgeCacheOnNextStartup</key>
+ <string>Limpar o cache de arquivos local na próxima vez que o viewer for executado.</string>
+ <key>PurgeCacheOnStartup</key>
+ <string>Limpar o cache de arquivos local toda vez que o viewer for executado.</string>
+ <key>PurgeDiskCacheOnStartup</key>
+ <string>Se deve ou não limpar o cache em disco por LRU durante a inicialização na thread principal.</string>
+ <key>PushToTalkButton</key>
+ <string>(Obsoleto) Qual botão ou tecla é usado para push-to-talk.</string>
+ <key>PushToTalkToggle</key>
+ <string>Se o botão da barra de ferramentas de push-to-talk deve se comportar como alternância.</string>
+ <key>QAMode</key>
+ <string>Ativar recursos de teste.</string>
+ <key>QAModeEventHostPort</key>
+ <string>(Obsoleto) Porta na qual o lleventhost deve escutar.</string>
+ <key>QAModeFakeSystemFolderIssues</key>
+ <string>Simula problemas de pastas de sistema no inventário.</string>
+ <key>QAModeMetrics</key>
+ <string>Ativa recursos de QA (logging, ciclagem mais rápida) para o coletor de métricas.</string>
+ <key>QAModeTermCode</key>
+ <string>Em LL_ERRS, encerrar com este código em vez da caixa de mensagem do SO.</string>
+ <key>QuickBuyCurrency</key>
+ <string>Alternar entre o floater de compra de moeda baseado em HTML e a versão XUI legada.</string>
+ <key>QuietSnapshotsToDisk</key>
+ <string>Tirar capturas para o disco sem reproduzir animação ou som.</string>
+ <key>QuitAfterSeconds</key>
+ <string>A duração permitida antes de encerrar.</string>
+ <key>QuitOnLoginActivated</key>
+ <string>Encerrar se a página de login for ativada (usado quando o login automático está ligado e os usuários não devem poder fazer login manualmente).</string>
+ <key>RadioLandBrushAction</key>
+ <string>Última operação de modificação de terreno selecionada (0 = nivelar, 1 = elevar, 2 = baixar, 3 = suavizar, 4 = irregular, 5 = reverter).</string>
+ <key>RecentItemsSortOrder</key>
+ <string>Especifica a chave de classificação dos itens de inventário recentes (+0 = nome, +1 = data, +2 = pastas sempre por nome, +4 = pastas de sistema no topo).</string>
+ <key>RecentJumpThresholdSecs</key>
+ <string>Segundos após a entrada de pulo durante os quais a animação de finalização do pouso é suprimida para evitar interromper pulos sucessivos rápidos.</string>
+ <key>RecentListShowIcons</key>
+ <string>Mostrar/ocultar ícones de pessoas na lista de recentes.</string>
+ <key>RecentPeopleSortOrder</key>
+ <string>Especifica a ordem de classificação de pessoas recentes (0 = por nome, 2 = mais recentes).</string>
+ <key>RectangleSelectInclusive</key>
+ <string>Selecionar objetos que tenham ao menos um vértice dentro do retângulo de seleção.</string>
+ <key>RegInClient</key>
+ <string>Experimental: incorporar o registro na tela de login.</string>
+ <key>RegionCheckTextureHeights</key>
+ <string>Não permitir que o usuário defina alturas baixas maiores que as altas.</string>
+ <key>RegionCrossingInterpolationTime</key>
+ <string>Por quanto tempo extrapolar o movimento do objeto após cruzar regiões.</string>
+ <key>RegionTextureSize</key>
+ <string>Dimensões da textura de terreno (potência de 2).</string>
+ <key>RememberPassword</key>
+ <string>Manter a senha (de forma criptografada) para o próximo login.</string>
+ <key>RememberUser</key>
+ <string>Manter o nome de usuário para o próximo login.</string>
+ <key>RenderAnimateRes</key>
+ <string>Animar o surgimento (rez) de prims.</string>
+ <key>RenderAnisotropic</key>
+ <string>Renderizar texturas usando filtragem anisotrópica.</string>
+ <key>RenderAppleUseMultGL</key>
+ <string>Se queremos usar OpenGL multithread em hardware Apple (requer reinício do SL).</string>
+ <key>RenderAttachedLights</key>
+ <string>Renderizar prims com luz que estão anexados a avatares.</string>
+ <key>RenderAttachedParticles</key>
+ <string>Renderizar sistemas de partículas que estão anexados a avatares.</string>
+ <key>RenderAutoHideSurfaceAreaLimit</key>
+ <string>Área de superfície máxima de um conjunto de objetos próximos no mundo antes de ocultar geometria automaticamente para evitar sobrecarga do sistema.</string>
+ <key>RenderAutoMaskAlphaDeferred</key>
+ <string>Usar máscaras alpha onde apropriado no Modelo de Iluminação Avançada.</string>
+ <key>RenderAutoMaskAlphaNonDeferred</key>
+ <string>Usar máscaras alpha onde apropriado quando não estiver usando o Modelo de Iluminação Avançada.</string>
+ <key>RenderAutoMuteLogging</key>
+ <string>Mostrar informações extras nos logs do viewer sobre custos de renderização de avatares.</string>
+ <key>RenderAutoMuteRenderWeightLimit</key>
+ <string>OBSOLETO. Esta configuração foi renomeada para RenderAvatarMaxNonImpostors.</string>
+ <key>RenderAutoMuteSurfaceAreaLimit</key>
+ <string>Área de superfície máxima dos anexos antes que um avatar seja renderizado como um impostor simples (para não usar este limite, defina como zero ou defina RenderAvatarMaxComplexity como zero).</string>
+ <key>RenderAvatarCloth</key>
+ <string>Controla se as roupas do avatar de sistema usam tecido ondulante.</string>
+ <key>RenderAvatarComplexityMode</key>
+ <string>0 - o limite de complexidade se aplica a todos, 1 - sempre mostrar amigos, 2 - mostrar apenas amigos.</string>
+ <key>RenderAvatarFriendsOnly</key>
+ <string>Quando ativado, oculta todos os avatares que não sejam amigos. Não afeta avatares de controle no mundo (animeshes), nem você mesmo.</string>
+ <key>RenderAvatarLODFactor</key>
+ <string>Controla o nível de detalhe dos avatares (multiplicador para a área de tela atual ao calcular o nível de detalhe).</string>
+ <key>RenderAvatarMaxART</key>
+ <string>Limite de tempo de renderização em microssegundos (0,0 = sem limite).</string>
+ <key>RenderAvatarMaxComplexity</key>
+ <string>Complexidade máxima de renderização do avatar.</string>
+ <key>RenderAvatarMaxNonImpostors</key>
+ <string>Número máximo de avatares a renderizar totalmente de uma vez; acima deste limite, usa renderização por impostor (renderização simplificada com atualizações menos frequentes), reduzindo o lag do cliente.</string>
+ <key>RenderAvatarMaxVisible</key>
+ <string>OBSOLETO e NÃO UTILIZADO. Veja RenderAvatarMaxNonImpostors.</string>
+ <key>RenderAvatarPhysicsLODFactor</key>
+ <string>Controla o nível de detalhe da física do avatar (como a física dos seios).</string>
+ <key>RenderBakeSunlight</key>
+ <string>Assar a luz do sol nos vertex buffers para objetos estáticos.</string>
+ <key>RenderBalanceInSnapshot</key>
+ <string>Exibir o saldo de L$ na captura.</string>
+ <key>RenderBufferVisualization</key>
+ <string>Envia um buffer selecionado para a tela. -1 = buffer de renderização final. 0 = Albedo, 1 = Especular/ORM, 2 = Normal, 3 = Emissivo, 4 = Luminância do olho, 5 = Luminância FXAA/Textura de borda SMAA, 6 = Pesos de mistura SMAA.</string>
+ <key>RenderBumpmapMinDistanceSquared</key>
+ <string>Distância máxima na qual renderizar primitivas com bumpmap (distância em metros, ao quadrado).</string>
+ <key>RenderCASSharpness</key>
+ <string>Nível de nitidez a aplicar via Contrast Adaptive Sharpening (0,0 (desligado) - 1,0).</string>
+ <key>RenderCPUBasis</key>
+ <string>Velocidade de clock de CPU de referência a usar para enviesar a classe da GPU (em MHz).</string>
+ <key>RenderCanUseGLTFPBROpaqueShaders</key>
+ <string>O hardware tem suporte para shaders de cena GLTF.</string>
+ <key>RenderCanUseTerrainBakeShaders</key>
+ <string>O hardware tem suporte para shaders de Terrain Bake.</string>
+ <key>RenderClass1MemoryBandwidth</key>
+ <string>Largura de banda de memória na qual usar Classe 1 por padrão, em gigabytes por segundo. Usado como base para classes superiores.</string>
+ <key>RenderComplexityColorMax</key>
+ <string>Configuração obsoleta não utilizada.</string>
+ <key>RenderComplexityColorMid</key>
+ <string>Configuração obsoleta não utilizada.</string>
+ <key>RenderComplexityColorMin</key>
+ <string>Configuração obsoleta não utilizada.</string>
+ <key>RenderComplexityStaticMax</key>
+ <string>Configuração obsoleta não utilizada.</string>
+ <key>RenderComplexityThreshold</key>
+ <string>Configuração obsoleta não utilizada.</string>
+ <key>RenderCompressTextures</key>
+ <string>Ativar compressão de texturas em implementações OpenGL 3.0 e posteriores (EXPERIMENTAL, requer reinício).</string>
+ <key>RenderCubeMap</key>
+ <string>Se podemos ou não renderizar o cube map.</string>
+ <key>RenderDebugAlphaMask</key>
+ <string>Testar os cortes de máscara alpha.</string>
+ <key>RenderDebugGLSession</key>
+ <string>Ativar depuração GL estrita no início da próxima sessão.</string>
+ <key>RenderDebugNormalScale</key>
+ <string>Escala das normais na exibição de depuração.</string>
+ <key>RenderDebugPipeline</key>
+ <string>Ativar depuração estrita do pipeline.</string>
+ <key>RenderDebugTextureBind</key>
+ <string>Ativar teste de desempenho de bind de textura.</string>
+ <key>RenderDefaultProbeUpdatePeriod</key>
+ <string>Quando RenderReflectionProbeLevel é 0, tempo em segundos a aguardar entre atualizações do mapa de reflexão.</string>
+ <key>RenderDeferred</key>
+ <string>Renderização diferida.</string>
+ <key>RenderDeferredAlphaSoften</key>
+ <string>Escalar para suavizar superfícies alpha (para partículas suaves).</string>
+ <key>RenderDeferredAtmospheric</key>
+ <string>Executar o shader atmosférico no renderizador diferido.</string>
+ <key>RenderDeferredBlurLight</key>
+ <string>Executar o shader de suavização de sombras no renderizador diferido.</string>
+ <key>RenderDeferredDisplayGamma</key>
+ <string>Expoente da rampa de gama para correção final antes do gama de exibição.</string>
+ <key>RenderDeferredNoise</key>
+ <string>Escalar de ruído para esconder banding na renderização diferida.</string>
+ <key>RenderDeferredSSAO</key>
+ <string>Executar o shader de oclusão de ambiente em espaço de tela no renderizador diferido.</string>
+ <key>RenderDeferredSpotShadowBias</key>
+ <string>Valor de viés para sombras de spot (previne shadow acne).</string>
+ <key>RenderDeferredSpotShadowOffset</key>
+ <string>Valor de deslocamento para sombras de spot (previne shadow acne).</string>
+ <key>RenderDeferredSun</key>
+ <string>Executar o shader de luz solar no renderizador diferido.</string>
+ <key>RenderDeferredSunWash</key>
+ <string>Quanto as luzes locais são apagadas pelo sol.</string>
+ <key>RenderDeferredTreeShadowBias</key>
+ <string>Valor de viés para sombras de árvores (previne shadow acne).</string>
+ <key>RenderDeferredTreeShadowOffset</key>
+ <string>Valor de deslocamento para sombras de árvores (previne shadow acne).</string>
+ <key>RenderDelayCreation</key>
+ <string>Limitar a criação de drawables.</string>
+ <key>RenderDelayVBUpdate</key>
+ <string>Atrasar as atualizações dos vertex buffers até pouco antes de renderizar.</string>
+ <key>RenderDepthOfField</key>
+ <string>Se deve usar o efeito de profundidade de campo quando o Modelo de Iluminação Avançada está ativado.</string>
+ <key>RenderDepthOfFieldInEditMode</key>
+ <string>Se deve usar o efeito de profundidade de campo no modo de edição.</string>
+ <key>RenderDepthPrePass</key>
+ <string>EXPERIMENTAL: preparar o buffer de profundidade com geometria de prim simples antes de renderizar com texturas.</string>
+ <key>RenderDesaturateIrradiance</key>
+ <string>Dessaturar a irradiância para remover o tom azulado.</string>
+ <key>RenderDiffuseLuminanceScale</key>
+ <string>Ajuste de luminância para superfícies difusas para auxiliar o comportamento de autoexposição.</string>
+ <key>RenderDisablePostProcessing</key>
+ <string>Desativar mapeamento de tons e correção de exposição quando o floater de construção está aberto (para artistas desenvolvendo materiais).</string>
+ <key>RenderDisableVintageMode</key>
+ <string>Ativa recursos adicionais do pipeline de renderização em máquinas mais novas, como HDR e texturas emissivas em conteúdo PBR.</string>
+ <key>RenderDownScaleMethod</key>
+ <string>Método a usar para reduzir imagens. 0 - FBO, 1 - PBO.</string>
+ <key>RenderDynamicExposureCoefficient</key>
+ <string>Coeficiente de luminância para exposição dinâmica.</string>
+ <key>RenderDynamicLOD</key>
+ <string>Ajustar dinamicamente o nível de detalhe.</string>
+ <key>RenderEdgeDepthCutoff</key>
+ <string>Corte para a diferença de profundidade que constitui uma borda.</string>
+ <key>RenderEdgeNormCutoff</key>
+ <string>Corte para a diferença de normal que constitui uma borda.</string>
+ <key>RenderEnableEmissiveBuffer</key>
+ <string>Ativar o buffer emissivo no gbuffer. Só deve ser desativado no modo GL3.</string>
+ <key>RenderExposure</key>
+ <string>Valor de exposição a enviar ao mapeador de tons.</string>
+ <key>RenderFSAASamples</key>
+ <string>Qualidade do antialiasing: 0 = Baixa, 1 = Média, 2 = Alta, 3 = Ultra.</string>
+ <key>RenderFSAAType</key>
+ <string>Tipo de antialiasing a usar: 0 = Nenhum, 1 = FXAA, 2 = SMAA.</string>
+ <key>RenderFarClip</key>
+ <string>Clipe de distância de renderização.</string>
+ <key>RenderFlexTimeFactor</key>
+ <string>Controla o nível de detalhe de objetos flexíveis (multiplicador para o tempo gasto processando objetos flex).</string>
+ <key>RenderFogRatio</key>
+ <string>OBSOLETO - Distância da câmera onde a névoa atinge densidade máxima (fração ou múltiplo da distância de far clip).</string>
+ <key>RenderGLContextCoreProfile</key>
+ <string>Não usar um contexto OpenGL de perfil de compatibilidade. Requer reinício.</string>
+ <key>RenderGLMultiThreadedMedia</key>
+ <string>Permitir que o OpenGL use múltiplos contextos de renderização para reproduzir mídia (pode reduzir engasgos de quadros, não funciona bem com drivers Intel).</string>
+ <key>RenderGLMultiThreadedTextures</key>
+ <string>Permitir que o OpenGL use múltiplos contextos de renderização para carregar texturas (pode reduzir engasgos de quadros, não funciona bem com drivers Intel).</string>
+ <key>RenderGamma</key>
+ <string>OBSOLETO - Define o expoente de gama para o renderizador.</string>
+ <key>RenderGammaFull</key>
+ <string>Usar correção de gama totalmente controlável, em vez da correção de gama mais rápida e fixa de 2.</string>
+ <key>RenderGlow</key>
+ <string>Renderizar o efeito de pós-processamento de bloom.</string>
+ <key>RenderGlowHDR</key>
+ <string>Ativar HDR para o mapa de brilho.</string>
+ <key>RenderGlowIterations</key>
+ <string>Número de vezes para iterar o brilho (mais alto = mais largo e suave, porém mais lento).</string>
+ <key>RenderGlowLumWeights</key>
+ <string>Pesos de cada canal de cor a usar no cálculo da luminância (devem somar 1,0).</string>
+ <key>RenderGlowMaxExtractAlpha</key>
+ <string>Valor máximo de alpha do brilho para extração de brilho no auto-glow.</string>
+ <key>RenderGlowMinLuminance</key>
+ <string>Intensidade mínima de luminância necessária para considerar um objeto brilhante o suficiente para brilhar automaticamente.</string>
+ <key>RenderGlowNoise</key>
+ <string>Ativa o ruído de brilho (dithering). Reduz o banding do brilho em certos casos.</string>
+ <key>RenderGlowResolutionPow</key>
+ <string>Resolução do mapa de brilho em potência de dois.</string>
+ <key>RenderGlowStrength</key>
+ <string>Força aditiva do brilho.</string>
+ <key>RenderGlowWarmthAmount</key>
+ <string>Quantidade de extração de calor a usar (em vez de extração de luminância). 0 = luminância, 1,0 = calor.</string>
+ <key>RenderGlowWarmthWeights</key>
+ <string>Peso de cada canal de cor usado antes de encontrar o calor máximo.</string>
+ <key>RenderGlowWidth</key>
+ <string>Tamanho da amostra de brilho (mais alto = mais largo e suave, mas eventualmente mais pixelado).</string>
+ <key>RenderHDREnabled</key>
+ <string>Ativar renderização HDR.</string>
+ <key>RenderHDRIExposure</key>
+ <string>Ajuste de exposição do HDRI ao pré-visualizar um HDRI. As unidades são EV. Valores razoáveis seriam de -10 a 10.</string>
+ <key>RenderHDRIIrradianceOnly</key>
+ <string>Usar o céu HDRI apenas para o mapa de irradiância quando RenderHDRISplitScreen é 0.</string>
+ <key>RenderHDRIRotation</key>
+ <string>Rotação (em graus) do ambiente ao pré-visualizar um HDRI.</string>
+ <key>RenderHDRISplitScreen</key>
+ <string>Qual percentual da tela renderizar usando HDRI vs. céu EEP.</string>
+ <key>RenderHDRSkySunlightScale</key>
+ <string>Fator de ajuste da escala de luz solar para combinar com o viewer pré-PBR quando o HDR está ativado.</string>
+ <key>RenderHUDInSnapshot</key>
+ <string>Exibir anexos de HUD na captura.</string>
+ <key>RenderHUDObjectsWarning</key>
+ <string>O viewer avisará o usuário sobre HUDs contendo objetos demais se a contagem de objetos estiver acima deste valor.</string>
+ <key>RenderHUDOversizedTexturesWarning</key>
+ <string>Quantas texturas de tamanho 1024 * 1024 ou maior um HUD pode conter antes de notificar o usuário.</string>
+ <key>RenderHUDParticles</key>
+ <string>Exibir sistemas de partículas em anexos de HUD (experimental).</string>
+ <key>RenderHUDTexturesMemoryWarning</key>
+ <string>O viewer avisará o usuário sobre texturas de HUD usando memória acima deste valor (em bytes).</string>
+ <key>RenderHUDTexturesWarning</key>
+ <string>O viewer avisará o usuário sobre HUDs contendo texturas demais se a contagem de texturas estiver acima deste valor.</string>
+ <key>RenderHeroProbeConservativeUpdateMultiplier</key>
+ <string>Quantas atualizações de probe aguardar até atualizar as faces que não estão diretamente voltadas para a câmera. Atua como multiplicador. Ex.: quadros na periferia da câmera atualizam a cada 3 atualizações, vs. os diretamente voltados para a câmera, que atualizam a cada atualização.</string>
+ <key>RenderHeroProbeDistance</key>
+ <string>Distância em metros até a qual os hero probes renderizam.</string>
+ <key>RenderHeroProbeResolution</key>
+ <string>Resolução para renderizar os hero probes usados em espelhos, água, etc.</string>
+ <key>RenderHeroProbeUpdateRate</key>
+ <string>Quantos quadros aguardar até renderizar o probe. Ex.: quadro sim, quadro não (1), a cada dois quadros (2), a cada três quadros (3) etc.</string>
+ <key>RenderHiDPI</key>
+ <string>Ativar suporte para telas HiDPI, como Retina (APENAS macOS, requer reinício).</string>
+ <key>RenderHiddenSelections</key>
+ <string>Mostrar linhas de seleção em objetos que estão atrás de outros objetos.</string>
+ <key>RenderHideGroupTitle</key>
+ <string>Não mostrar meu título de grupo no meu rótulo de nome.</string>
+ <key>RenderHighMemMinDiscardDecrement</key>
+ <string>Decremento mínimo do viés de descarte se houver excesso de memória de textura disponível.</string>
+ <key>RenderHighlightBrightness</key>
+ <string>Brilho dos destaques de mouseover.</string>
+ <key>RenderHighlightColor</key>
+ <string>Cor dos destaques de mouseover.</string>
+ <key>RenderHighlightFadeTime</key>
+ <string>Tempo de transição dos destaques de mouseover.</string>
+ <key>RenderHighlightSelections</key>
+ <string>Mostrar contornos de seleção nos objetos.</string>
+ <key>RenderHighlightThickness</key>
+ <string>Espessura dos destaques de mouseover.</string>
+ <key>RenderHoverGlowEnable</key>
+ <string>OBSOLETO --- Mostrar efeito de brilho ao passar o mouse sobre objetos interativos.</string>
+ <key>RenderInitError</key>
+ <string>Ocorreu um erro ao inicializar o GL.</string>
+ <key>RenderLightRadius</key>
+ <string>Renderizar o raio das luzes selecionadas.</string>
+ <key>RenderLocalLightCount</key>
+ <string>Número de luzes locais a renderizar.</string>
+ <key>RenderLowMemMinDiscardIncrement</key>
+ <string>Incremento mínimo do viés de descarte se a memória de textura disponível ficar baixa.</string>
+ <key>RenderMaxNodeSize</key>
+ <string>Tamanho máximo dos dados de vértice de um único nó (em KB).</string>
+ <key>RenderMaxOpenGLVersion</key>
+ <string>Versão máxima do OpenGL a tentar usar (mínimo 3.1, máximo 4.6). Requer reinício. Apenas Windows.</string>
+ <key>RenderMaxPartCount</key>
+ <string>Número máximo de partículas a exibir na tela.</string>
+ <key>RenderMaxTextureIndex</key>
+ <string>Índice máximo de textura a usar para renderização de textura indexada.</string>
+ <key>RenderMaxTextureResolution</key>
+ <string>Resolução máxima de textura a baixar para texturas não impulsionadas.</string>
+ <key>RenderMaxVBOSize</key>
+ <string>Tamanho máximo de um vertex buffer (em KB).</string>
+ <key>RenderMaxVRAMBudget</key>
+ <string>Quantidade máxima de memória de textura a reservar (em MB), ou 0 para detecção automática.</string>
+ <key>RenderMinFreeMainMemoryThreshold</key>
+ <string>Se a memória física livre disponível estiver abaixo deste valor, as texturas são reduzidas agressivamente.</string>
+ <key>RenderMinimumLODTriangleCount</key>
+ <string>Limiar de contagem de triângulos no qual a geração automática de LOD para.</string>
+ <key>RenderMirrors</key>
+ <string>Renderiza espelhos em tempo real.</string>
+ <key>RenderNameFadeDuration</key>
+ <string>Intervalo de tempo durante o qual os nomes dos avatares desaparecem (segundos).</string>
+ <key>RenderNameShowSelf</key>
+ <string>Exibir o próprio nome acima do avatar.</string>
+ <key>RenderNameShowTime</key>
+ <string>Desaparecer os nomes dos avatares após o tempo especificado (segundos).</string>
+ <key>RenderNoAlpha</key>
+ <string>Desativar a renderização de objetos alpha (renderizar todos os objetos alpha como máscaras alpha).</string>
+ <key>RenderNormalMapScale</key>
+ <string>Escalar aplicado ao mapa de altura ao gerar mapas de normais.</string>
+ <key>RenderNsightDebugSupport</key>
+ <string>Desativar recursos que impedem o uso do nVidia nSight com o SL. Requer reinício.</string>
+ <key>RenderObjectBump</key>
+ <string>OBSOLETO (apenas TRUE é suportado) Mostrar bumpmapping em primitivas.</string>
+ <key>RenderOcclusionTimeout</key>
+ <string>Número máximo de quadros a aguardar em uma consulta de oclusão antes de lê-la de volta à força.</string>
+ <key>RenderParcelSelection</key>
+ <string>Exibir o contorno da parcela selecionada.</string>
+ <key>RenderPerformanceTest</key>
+ <string>Desativar a renderização de tudo, exceto o conteúdo no mundo, para testes de desempenho.</string>
+ <key>RenderPreferStreamDraw</key>
+ <string>Usar GL_STREAM_DRAW no lugar de GL_DYNAMIC_DRAW.</string>
+ <key>RenderQualityPerformance</key>
+ <string>Quais configurações gráficas você escolheu. Não use esta configuração para alterar a qualidade diretamente pelas configurações de depuração.</string>
+ <key>RenderReflectionDetail</key>
+ <string>OBSOLETO -- use RenderTransparentWater e RenderReflectionProbeDetail -- Detalhe do passe de renderização de reflexão.</string>
+ <key>RenderReflectionProbeAmbiance</key>
+ <string>Quanto os probes de reflexão contribuem para a luz ambiente.</string>
+ <key>RenderReflectionProbeCount</key>
+ <string>Número de probes a renderizar. Máximo de 256. Ajusta para a potência de 2 mais próxima.</string>
+ <key>RenderReflectionProbeDetail</key>
+ <string>Detalhe das reflexões. (-1 - Desativado, 0 - Apenas estático, 1 - Estático + Dinâmico, 2 - Tempo real).</string>
+ <key>RenderReflectionProbeDrawDistance</key>
+ <string>Far clip da câmera a usar ao atualizar os probes de reflexão.</string>
+ <key>RenderReflectionProbeDynamicAllocation</key>
+ <string>Ativar alocação dinâmica de probes de reflexão. -1 significa sem alocação dinâmica. Define um buffer a alocar quando ocorre uma alocação dinâmica.</string>
+ <key>RenderReflectionProbeLevel</key>
+ <string>Controle dos probes de reflexão. 0 - desativar (um probe para todos), 1 - apenas probes manuais, 2 - manual + terreno/água, 3 - Manual + Terreno/água + objetos.</string>
+ <key>RenderReflectionProbeMaxLocalLightAmbiance</key>
+ <string>Ambiência efetiva máxima de probe para luzes locais.</string>
+ <key>RenderReflectionProbeResolution</key>
+ <string>Resolução dos mapas de radiância dos probes de reflexão (requer reinício). Será definida para a próxima potência de dois mais alta, limitada a [64, 512]. Note que alterar este valor pode consumir uma quantidade enorme de memória de vídeo.</string>
+ <key>RenderReflectionProbeShowTransparent</key>
+ <string>Mostrar probes de reflexão na visão de depuração de transparência.</string>
+ <key>RenderReflectionProbeVolumes</key>
+ <string>Renderizar os volumes de influência dos probes de reflexão.</string>
+ <key>RenderReflectionRes</key>
+ <string>Resolução do mapa de reflexão.</string>
+ <key>RenderReflectionsEnabled</key>
+ <string>Ativar/desativar probes de reflexão.</string>
+ <key>RenderReservedTextureIndices</key>
+ <string>Quantidade de índices de textura a reservar para mapas de sombra e reflexão ao usar renderização de textura indexada. Provavelmente só deve ser definido na tela de login.</string>
+ <key>RenderResolutionDivisor</key>
+ <string>Divisor para renderizar a cena 3D em resolução reduzida.</string>
+ <key>RenderSSAOEffect</key>
+ <string>Multiplicador para (1) valor e (2) saturação (definição HSV), para áreas totalmente ocluídas. Mistura com a cor original para áreas parcialmente ocluídas. (O terceiro componente não é utilizado).</string>
+ <key>RenderSSAOFactor</key>
+ <string>Fator de sensibilidade de oclusão para a oclusão de ambiente (maior é mais).</string>
+ <key>RenderSSAOIrradianceMax</key>
+ <string>Fator máximo para a entrada de irradiância no SSAO.</string>
+ <key>RenderSSAOIrradianceScale</key>
+ <string>Fator de escala para a entrada de irradiância no SSAO.</string>
+ <key>RenderSSAOMaxScale</key>
+ <string>Raio máximo de tela para amostragem (pixels).</string>
+ <key>RenderSSAOScale</key>
+ <string>Fator de escala para a área a amostrar em busca de oclusores (pixels a 1 metro de distância, variando inversamente com a distância).</string>
+ <key>RenderScreenSpaceReflectionAdaptiveStepMultiplier</key>
+ <string>Multiplicador para escalar o passo adaptativo.</string>
+ <key>RenderScreenSpaceReflectionDepthRejectBias</key>
+ <string>Viés contra o buffer de profundidade antes de rejeitar uma amostra.</string>
+ <key>RenderScreenSpaceReflectionDistanceBias</key>
+ <string>Viés de distância a aplicar ao rejeitar uma amostra potencial.</string>
+ <key>RenderScreenSpaceReflectionGlossySamples</key>
+ <string>Número máximo de amostras a aplicar para SSR brilhante.</string>
+ <key>RenderScreenSpaceReflectionIterations</key>
+ <string>Número de vezes que o algoritmo de ray march executa para encontrar um possível acerto.</string>
+ <key>RenderScreenSpaceReflectionRayStep</key>
+ <string>Quão grande é o passo entre cada execução.</string>
+ <key>RenderScreenSpaceReflections</key>
+ <string>Renderiza reflexões em espaço de tela para considerar melhor objetos dinâmicos com probes de reflexão.</string>
+ <key>RenderShaderCacheEnabled</key>
+ <string>Ativar cache de shaders binários.</string>
+ <key>RenderShaderCacheVersion</key>
+ <string>Hash de versão atual do cache de shaders.</string>
+ <key>RenderShaderLODThreshold</key>
+ <string>Fração da distância de desenho que define a troca para um LOD de shader diferente.</string>
+ <key>RenderShaderLightingMaxLevel</key>
+ <string>Nível máximo de iluminação a usar no shader (classe 3 é o padrão, 2 é menos luzes, 1 é apenas sol/lua. Contorna bugs do compilador de shaders em certas plataformas).</string>
+ <key>RenderShaderParticleThreshold</key>
+ <string>Fração da distância de desenho a partir da qual não usar shader em partículas.</string>
+ <key>RenderShadowBias</key>
+ <string>Valor de viés para sombras (previne shadow acne).</string>
+ <key>RenderShadowBiasError</key>
+ <string>Escala de erro para o viés de sombra (com base na altitude).</string>
+ <key>RenderShadowBlurDistFactor</key>
+ <string>Escalar de distância para o desfoque de sombra.</string>
+ <key>RenderShadowBlurSamples</key>
+ <string>Número de amostras a tomar em cada passe de desfoque de sombra (faixa de 1-16). O número real de amostras é valor * 2 - 1.</string>
+ <key>RenderShadowBlurSize</key>
+ <string>Escala do kernel de suavização de sombra.</string>
+ <key>RenderShadowDetail</key>
+ <string>Detalhe das sombras.</string>
+ <key>RenderShadowErrorCutoff</key>
+ <string>Valor de erro de corte para usar projeção ortográfica em vez de perspectiva.</string>
+ <key>RenderShadowFOVCutoff</key>
+ <string>FOV de corte para usar projeção ortográfica em vez de perspectiva.</string>
+ <key>RenderShadowGaussian</key>
+ <string>Coeficientes gaussianos para os dois passes de desfoque de sombra/SSAO (componente z não utilizado).</string>
+ <key>RenderShadowNoise</key>
+ <string>Magnitude do ruído nas amostras de sombra.</string>
+ <key>RenderShadowOffset</key>
+ <string>Valor de deslocamento para sombras (previne shadow acne).</string>
+ <key>RenderShadowOffsetError</key>
+ <string>Escala de erro para o deslocamento de sombra (com base na altitude).</string>
+ <key>RenderShadowProjExponent</key>
+ <string>Expoente aplicado à transição entre projeções de sombra ortográfica e em perspectiva com base no ângulo de visão e no vetor de luz.</string>
+ <key>RenderShadowProjOffset</key>
+ <string>Quanto escalar a distância até a origem virtual da projeção em perspectiva da sombra.</string>
+ <key>RenderShadowResolutionScale</key>
+ <string>Escala da resolução do mapa de sombra vs. resolução da tela (apenas valores positivos são permitidos).</string>
+ <key>RenderShadowSlopeThreshold</key>
+ <string>Valor de inclinação de corte para pontos que afetam a geração de sombra em perspectiva.</string>
+ <key>RenderShadowSplitExponent</key>
+ <string>Distâncias de divisão do plano de near clip para os frusta do mapa de sombra (x=perspectiva, y=ortográfica, z=taxa de transição).</string>
+ <key>RenderShadowSplits</key>
+ <string>Quantidade de divisões do mapa de sombra a renderizar (0 - 3).</string>
+ <key>RenderSkyAmbientScale</key>
+ <string>Fator de ajuste da escala de ambiente para combinar com o viewer pré-PBR.</string>
+ <key>RenderSkyAutoAdjustAmbientScale</key>
+ <string>Quanto escalar o ambiente ao autoajustar céus legados.</string>
+ <key>RenderSkyAutoAdjustBlueDensityScale</key>
+ <string>Valor de escala do Blue Horizon a usar ao autoajustar céus legados.</string>
+ <key>RenderSkyAutoAdjustBlueHorizonScale</key>
+ <string>Valor de escala do Blue Horizon a usar ao autoajustar céus legados.</string>
+ <key>RenderSkyAutoAdjustHDRScale</key>
+ <string>Valor de escala HDR a usar ao autoajustar céus legados.</string>
+ <key>RenderSkyAutoAdjustLegacy</key>
+ <string>Se verdadeiro, autoajustar céus legados (aqueles sem valor de ambiência de probe) para aproveitar os probes e o HDR. Este é o botão de "opt-out" para HDR e mapeamento de tons quando combinado com uma configuração de céu anterior ao PBR.</string>
+ <key>RenderSkyAutoAdjustProbeAmbiance</key>
+ <string>Valor de ambiência de probe ao autoajustar céus legados.</string>
+ <key>RenderSkyAutoAdjustSunColorScale</key>
+ <string>Escalar da cor do sol ao autoajustar céus legados.</string>
+ <key>RenderSkySunlightScale</key>
+ <string>Fator de ajuste da escala de luz solar para combinar com o viewer pré-PBR quando o HDR está desativado.</string>
+ <key>RenderSnapshotNoPost</key>
+ <string>Desativar mapeamento de tons e correção de exposição quando a captura está sendo renderizada.</string>
+ <key>RenderSpecularExponent</key>
+ <string>Expoente especular para gerar o mapa especular.</string>
+ <key>RenderSpecularPrecision</key>
+ <string>Forçar LUT de ponto flutuante de 32 bits.</string>
+ <key>RenderSpecularResX</key>
+ <string>Resolução do mapa especular.</string>
+ <key>RenderSpecularResY</key>
+ <string>Resolução do mapa especular.</string>
+ <key>RenderSpotLightsInNondeferred</key>
+ <string>Se deve suportar projetores como spotlights quando o Modelo de Iluminação Avançada está desativado.</string>
+ <key>RenderSpotShadowBias</key>
+ <string>Valor de viés para sombras (previne shadow acne).</string>
+ <key>RenderSpotShadowOffset</key>
+ <string>Valor de deslocamento para sombras (previne shadow acne).</string>
+ <key>RenderSunDynamicRange</key>
+ <string>Define quanto por cento o sol é mais brilhante que as luzes pontuais locais (1,0 = 100% mais brilhante. O valor não deve ser menor que 0).</string>
+ <key>RenderTerrainDetail</key>
+ <string>Detalhe aplicado à texturização do terreno (0 = nenhum, 1 = completo).</string>
+ <key>RenderTerrainLODFactor</key>
+ <string>Controla o nível de detalhe do terreno (multiplicador para a área de tela atual ao calcular o nível de detalhe).</string>
+ <key>RenderTerrainPBRDetail</key>
+ <string>Nível de detalhe para terreno PBR. 0 é o detalhe completo. Valores negativos removem recursos de renderização, de acordo com a especificação GLTF quando possível, o que reduz o número de binds de textura.</string>
+ <key>RenderTerrainPBREnabled</key>
+ <string>Ativar recursos de terreno PBR.</string>
+ <key>RenderTerrainPBRNormalsEnabled</key>
+ <string>EXPERIMENTAL: alterar a geração de normais para terreno PBR.</string>
+ <key>RenderTerrainPBRPlanarSampleCount</key>
+ <string>De quantos planos UV amostrar as texturas do terreno PBR. 1 é "plano", 3 é mapeamento triplanar (também conhecido como box mapping).</string>
+ <key>RenderTerrainPBRScale</key>
+ <string>Escala da textura de detalhe do terreno PBR (metros).</string>
+ <key>RenderTerrainPBRTransformsEnabled</key>
+ <string>EXPERIMENTAL: ativar transformações de textura do terreno PBR.</string>
+ <key>RenderTerrainPBRTriplanarBlendFactor</key>
+ <string>Valores mais altos criam transições mais nítidas, mas têm maior probabilidade de produzir artefatos.</string>
+ <key>RenderTerrainScale</key>
+ <string>Escala da textura de detalhe do terreno (metros).</string>
+ <key>RenderTextureVRAMDivisor</key>
+ <string>Divisor para a quantidade máxima de VRAM que o viewer usará para texturas. 1 = toda a VRAM. Usado em conjunto com RenderMaxVRAMBudget.</string>
+ <key>RenderTonemapMix</key>
+ <string>Mistura entre cores lineares e mapeadas por tom (0,0 (Linear) - 1,0 (Mapeado por tom)).</string>
+ <key>RenderTonemapType</key>
+ <string>Qual mapeador de tons usar: 0 = Khronos Neutral, 1 = ACES.</string>
+ <key>RenderTrackerBeacon</key>
+ <string>Exibir seta de rastreamento e marcador para o avatar alvo, destino de teletransporte.</string>
+ <key>RenderTransparentWater</key>
+ <string>Renderizar a água como transparente. Definir como falso renderiza a água como opaca com uma textura simples aplicada.</string>
+ <key>RenderTreeLODFactor</key>
+ <string>Controla o nível de detalhe da vegetação (multiplicador para a área de tela atual ao calcular o nível de detalhe).</string>
+ <key>RenderUIBuffer</key>
+ <string>Armazenar em cache a renderização da interface em um buffer alinhado à tela.</string>
+ <key>RenderUIInSnapshot</key>
+ <string>Exibir a interface do usuário na captura.</string>
+ <key>RenderUnloadedAvatar</key>
+ <string>Mostrar avatares que ainda não terminaram de carregar.</string>
+ <key>RenderUseAdvancedAtmospherics</key>
+ <string>Usar atmosféricos pré-computados sofisticados e afins.</string>
+ <key>RenderUseExposureSkySettings</key>
+ <string>Usar as configurações de exposição do céu em vez de derivar da escala HDR.</string>
+ <key>RenderUseFarClip</key>
+ <string>Se falso, o culling de frustum ignorará o plano de far clip.</string>
+ <key>RenderUseStreamVBO</key>
+ <string>Usar VBOs para buffers de stream.</string>
+ <key>RenderUseTriStrips</key>
+ <string>OBSOLETO - agora sempre assumido como falso - Usar tiras de triângulos para renderizar prims.</string>
+ <key>RenderUseVAO</key>
+ <string>[EXPERIMENTAL] Usar GL Vertex Array Objects.</string>
+ <key>RenderVBOEnable</key>
+ <string>Usar GL Vertex Buffer Objects.</string>
+ <key>RenderVBOMappingDisable</key>
+ <string>Desativar VBO glMapBufferARB.</string>
+ <key>RenderVSyncEnable</key>
+ <string>Atualizar quadros entre as varreduras de exibição (FALSE = atualizar quadros o mais rápido possível).</string>
+ <key>RenderVolumeLODFactor</key>
+ <string>Controla o nível de detalhe das primitivas (multiplicador para a área de tela atual ao calcular o nível de detalhe).</string>
+ <key>RenderWater</key>
+ <string>Renderizar água.</string>
+ <key>RenderWaterMaterials</key>
+ <string>As reflexões planares da água incluem a renderização de materiais.</string>
+ <key>RenderWaterMipNormal</key>
+ <string>Usar mip maps para o mapa de normais da água.</string>
+ <key>RenderWaterRefResolution</key>
+ <string>Resolução da reflexão planar da água.</string>
+ <key>ReplaySession</key>
+ <string>Solicitar a reprodução de um arquivo de piloto gravado anteriormente.</string>
+ <key>ReportBugURL</key>
+ <string>URL usada para registrar bugs a partir do viewer.</string>
+ <key>RequestFullRegionCache</key>
+ <string>Se definido, pedir ao sim para enviar o cache completo de objetos da região. Precisa reiniciar o viewer.</string>
+ <key>ResetUIScaleOnFirstRun</key>
+ <string>Redefine o fator de escala da interface na primeira execução devido à mudança no comportamento de escala da tela.</string>
+ <key>RestoreCameraPosOnLogin</key>
+ <string>Redefinir a posição da câmera para a localização no logout.</string>
+ <key>RevokePermsOnStopAnimation</key>
+ <string>Limpar as permissões de animação ao escolher "Parar de me animar".</string>
+ <key>RotateRight</key>
+ <string>Faz o agente girar para a direita.</string>
+ <key>RotationStep</key>
+ <string>Todas as rotações pela ferramenta de rotação são restritas a múltiplos desta unidade (graus).</string>
+ <key>RunMultipleThreads</key>
+ <string>Se TRUE, manter as threads de segundo plano ativas durante a renderização.</string>
+ <key>SLURLDragNDrop</key>
+ <string>Ativar arrastar e soltar de SLURLs no viewer.</string>
+ <key>SLURLPassToOtherInstance</key>
+ <string>Passar a execução para instâncias anteriores do viewer se houver uma slurl fornecida.</string>
+ <key>SLURLTeleportDirectly</key>
+ <string>Clicar em uma slurl teletransporta você diretamente em vez de abrir o painel de lugares.</string>
+ <key>SafeMode</key>
+ <string>Redefinir preferências, executar em modo de segurança.</string>
+ <key>SaveMinidump</key>
+ <string>Salvar minidump para depuração do desenvolvedor em caso de crash.</string>
+ <key>ScaleShowAxes</key>
+ <string>Mostrar o indicador do eixo de escala selecionado ao escalonar.</string>
+ <key>ScaleStretchTextures</key>
+ <string>Esticar as texturas junto com o objeto ao escalonar.</string>
+ <key>ScaleUniform</key>
+ <string>Escalonar os objetos selecionados uniformemente em torno do centro da seleção.</string>
+ <key>SceneLoadFrontPixelThreshold</key>
+ <string>em pixels, todos os objetos no frustum de visão cuja área de tela seja maior que este limiar serão carregados.</string>
+ <key>SceneLoadHighMemoryBound</key>
+ <string>em MB, quando o uso total de memória estiver acima deste limiar, um mínimo de objetos invisíveis é mantido na memória.</string>
+ <key>SceneLoadLowMemoryBound</key>
+ <string>em MB, quando o uso total de memória estiver acima deste limiar, começar a reduzir os objetos invisíveis mantidos na memória.</string>
+ <key>SceneLoadMinRadius</key>
+ <string>em metros, todos os objetos (visíveis ou invisíveis) dentro deste raio permanecerão carregados na memória.</string>
+ <key>SceneLoadRearMaxRadiusFraction</key>
+ <string>uma fração da distância de desenho além da qual todos os objetos fora do frustum de visão serão descarregados, independentemente do limiar de pixels.</string>
+ <key>SceneLoadRearPixelThreshold</key>
+ <string>em pixels, todos os objetos fora do frustum de visão cuja área de tela seja maior que este limiar permanecerão carregados.</string>
+ <key>SceneLoadingMonitorEnabled</key>
+ <string>Ativar o monitor de carregamento de cena se definido.</string>
+ <key>SceneLoadingMonitorPixelDiffThreshold</key>
+ <string>Quantidade de pixels alterados necessária para considerar a cena ainda em carregamento (raiz quadrada da fração de pixels na tela).</string>
+ <key>SceneLoadingMonitorSampleTime</key>
+ <string>Tempo entre amostras de tela ao monitorar o carregamento da cena (segundos).</string>
+ <key>ScriptDialogLimitations</key>
+ <string>Limita a quantidade de diálogos por script (0 - por objeto, 1 - por canal, 2 - por canal para anexos, 3 - por canal para HUDs, 4 - sem restrição para HUDs).</string>
+ <key>ScriptHelpFollowsCursor</key>
+ <string>A janela de ajuda de scripts atualiza o conteúdo com base no texto sob o cursor no editor de scripts.</string>
+ <key>ScriptToastButtonWidth</key>
+ <string>Largura padrão dos botões no toast de diálogo de script.</string>
+ <key>ScriptsCanShowUI</key>
+ <string>Permitir que chamadas LSL (como LLMapDestination) abram a interface do viewer.</string>
+ <key>ScriptsEveryoneCopy</key>
+ <string>Todos podem copiar o script recém-criado.</string>
+ <key>ScriptsNextOwnerCopy</key>
+ <string>Scripts recém-criados podem ser copiados pelo próximo proprietário.</string>
+ <key>ScriptsNextOwnerModify</key>
+ <string>Scripts recém-criados podem ser modificados pelo próximo proprietário.</string>
+ <key>ScriptsNextOwnerTransfer</key>
+ <string>Scripts recém-criados podem ser revendidos ou doados pelo próximo proprietário.</string>
+ <key>ScriptsShareWithGroup</key>
+ <string>Scripts recém-criados são compartilhados com o grupo ativo no momento.</string>
+ <key>SearchFromAddressBar</key>
+ <string>Permite inserir consultas de busca na barra de endereço de navegação.</string>
+ <key>SearchURL</key>
+ <string>URL do site de busca, exibida no floater Buscar.</string>
+ <key>SelectInvisibleObjects</key>
+ <string>Selecionar objetos invisíveis.</string>
+ <key>SelectMovableOnly</key>
+ <string>Selecionar apenas objetos que você pode mover.</string>
+ <key>SelectOwnedOnly</key>
+ <string>Selecionar apenas objetos que você possui.</string>
+ <key>SelectReflectionProbes</key>
+ <string>Selecionar probes de reflexão.</string>
+ <key>SelectionHighlightAlpha</key>
+ <string>Opacidade do destaque de seleção (0,0 = totalmente transparente, 1,0 = totalmente opaco).</string>
+ <key>SelectionHighlightAlphaTest</key>
+ <string>Valor de alpha abaixo do qual os pixels são exibidos na linha de destaque de seleção (0,0 = mostrar todos os pixels, 1,0 = não mostrar nenhum pixel).</string>
+ <key>SelectionHighlightThickness</key>
+ <string>Espessura da linha de destaque de seleção (fração da distância de visão).</string>
+ <key>SelectionHighlightUAnim</key>
+ <string>Taxa na qual a textura anima na direção U na linha de destaque de seleção (fração de textura por segundo).</string>
+ <key>SelectionHighlightUScale</key>
+ <string>Escala da exibição da textura na linha de destaque de seleção (fração do tamanho da textura).</string>
+ <key>SelectionHighlightVAnim</key>
+ <string>Taxa na qual a textura anima na direção V na linha de destaque de seleção (fração de textura por segundo).</string>
+ <key>SelectionHighlightVScale</key>
+ <string>Escala da exibição da textura na linha de destaque de seleção (fração do tamanho da textura).</string>
+ <key>ServerChoice</key>
+ <string>[NÃO MODIFICAR] Controla a qual grade você se conecta.</string>
+ <key>SessionSettingsFile</key>
+ <string>Configurações aplicadas por sessão (não salvas).</string>
+ <key>SettingsNextOwnerModify</key>
+ <string>A configuração de ambiente recém-criada pode ser modificada pelo próximo proprietário.</string>
+ <key>SettingsNextOwnerTransfer</key>
+ <string>A configuração de ambiente recém-criada pode ser revendida ou doada pelo próximo proprietário.</string>
+ <key>ShareWithGroup</key>
+ <string>(obsoleto) Objetos recém-criados são compartilhados com o grupo ativo no momento.</string>
+ <key>ShowAdultClassifieds</key>
+ <string>Exibir resultados de classificados marcados como adultos.</string>
+ <key>ShowAdultEvents</key>
+ <string>Exibir resultados de eventos marcados como adultos.</string>
+ <key>ShowAdultLand</key>
+ <string>Exibir resultados de vendas de terreno marcadas como adultas.</string>
+ <key>ShowAdultSims</key>
+ <string>Exibir resultados de buscar lugares ou populares que estejam em sims adultos.</string>
+ <key>ShowAllObjectHoverTip</key>
+ <string>Mostrar dica descritiva quando o mouse passa sobre objetos não interativos e interativos.</string>
+ <key>ShowAxes</key>
+ <string>Renderizar o sistema de coordenadas na sua posição.</string>
+ <key>ShowBanLines</key>
+ <string>Mostrar bordas de banimento/acesso no mundo, 0 - não mostrar, 1 - mostrar em colisão, 2 - mostrar por proximidade.</string>
+ <key>ShowBetaGrids</key>
+ <string>Exibir as grades beta no controle de seleção de grade.</string>
+ <key>ShowBlockedConvHistory</key>
+ <string>Especifica se agentes bloqueados serão mostrados no registro de chamadas.</string>
+ <key>ShowBuildButton</key>
+ <string>Mostra/oculta o botão de construir na bandeja inferior.</string>
+ <key>ShowCameraButton</key>
+ <string>Mostra/oculta o botão de visão na bandeja inferior.</string>
+ <key>ShowConsoleWindow</key>
+ <string>Mostrar o log em uma janela separada do SO.</string>
+ <key>ShowCrosshairs</key>
+ <string>Exibir a mira no modo mouselook.</string>
+ <key>ShowDebugConsole</key>
+ <string>Exibir console de depuração.</string>
+ <key>ShowDeviceSettings</key>
+ <string>Mostrar configurações de dispositivo.</string>
+ <key>ShowDiscordActivityDetails</key>
+ <string>Quando ativado, mostrar o nome do avatar no Discord Rich Presence.</string>
+ <key>ShowDiscordActivityState</key>
+ <string>Quando ativado, mostrar a localização no Discord Rich Presence.</string>
+ <key>ShowEventRecorderMenuItems</key>
+ <string>Se as opções de menu do Gravador de Eventos - Iniciar / Parar gravação - devem aparecer no menu Desenvolver (atualmente).</string>
+ <key>ShowFavoritesOnLogin</key>
+ <string>Define se os favoritos do último usuário logado serão salvos ao sair e exibidos na tela de login.</string>
+ <key>ShowGestureButton</key>
+ <string>Mostra/oculta o botão de gesto na bandeja inferior.</string>
+ <key>ShowHelpOnFirstLogin</key>
+ <string>Mostrar o floater de Ajuda no primeiro login.</string>
+ <key>ShowHoverTips</key>
+ <string>Mostrar dica descritiva quando o mouse passa sobre itens no mundo.</string>
+ <key>ShowInInventory</key>
+ <string>Abre automaticamente o inventário para mostrar os objetos aceitos.</string>
+ <key>ShowLandHoverTip</key>
+ <string>Mostrar dica descritiva quando o mouse passa sobre o terreno.</string>
+ <key>ShowMatureClassifieds</key>
+ <string>Exibir resultados de classificados marcados como moderados.</string>
+ <key>ShowMatureEvents</key>
+ <string>Exibir resultados de eventos marcados como moderados.</string>
+ <key>ShowMatureGroups</key>
+ <string>Incluir grupos marcados como maduros nos resultados de busca.</string>
+ <key>ShowMatureLand</key>
+ <string>Exibir resultados de vendas de terreno marcadas como moderadas.</string>
+ <key>ShowMatureSims</key>
+ <string>Exibir resultados de buscar lugares ou populares que estejam em sims moderados.</string>
+ <key>ShowMiniLocationPanel</key>
+ <string>Mostrar/ocultar o mini painel de localização.</string>
+ <key>ShowMiniMapButton</key>
+ <string>Mostra/oculta o botão de minimapa na bandeja inferior.</string>
+ <key>ShowMoveButton</key>
+ <string>Mostra/oculta o botão de mover na bandeja inferior.</string>
+ <key>ShowMyComplexityChanges</key>
+ <string>Por quanto tempo mostrar avisos sobre a complexidade do avatar (defina zero para desativar esses avisos).</string>
+ <key>ShowNavbarFavoritesPanel</key>
+ <string>Mostrar/ocultar o painel de favoritos da barra de navegação.</string>
+ <key>ShowNavbarNavigationPanel</key>
+ <string>Mostrar/ocultar o painel de navegação da barra de navegação.</string>
+ <key>ShowNearClip</key>
+ <string>Mostrar o plano de recorte próximo (near clip) na visualização.</string>
+ <key>ShowNetStats</key>
+ <string>Mostrar os indicadores de status de uso do viewer e da rede na sobreposição de status.</string>
+ <key>ShowNewInventory</key>
+ <string>Visualiza automaticamente novos notecards/texturas/landmarks.</string>
+ <key>ShowObjectRenderingCost</key>
+ <string>Mostrar o custo de renderização do objeto nas ferramentas de construção.</string>
+ <key>ShowObjectUpdates</key>
+ <string>Mostrar quando mensagens de atualização são recebidas para objetos individuais.</string>
+ <key>ShowOfferedInventory</key>
+ <string>Mostrar a janela de inventário com a última oferta de inventário selecionada ao receber inventário de outros usuários.</string>
+ <key>ShowOverlayTitle</key>
+ <string>Imprime uma mensagem de marca d'água na tela.</string>
+ <key>ShowPGClassifieds</key>
+ <string>Exibir resultados de classificados marcados como gerais.</string>
+ <key>ShowPGEvents</key>
+ <string>Exibir resultados de eventos marcados como gerais.</string>
+ <key>ShowPGLand</key>
+ <string>Exibir resultados de vendas de terreno marcadas como gerais.</string>
+ <key>ShowPGSims</key>
+ <string>Exibir resultados de buscar lugares ou populares que estejam em sims gerais.</string>
+ <key>ShowParcelOwners</key>
+ <string>Mostrar nomes dos proprietários das parcelas.</string>
+ <key>ShowPermissions</key>
+ <string>Mostrar permissões de objetos (configuração de depuração legada).</string>
+ <key>ShowPropertyLines</key>
+ <string>Mostrar sobreposição de linhas demarcando os limites de propriedade.</string>
+ <key>ShowScriptErrors</key>
+ <string>Mostrar erros de script.</string>
+ <key>ShowScriptErrorsLocation</key>
+ <string>Mostrar erro de script no chat (0) ou em janela (1).</string>
+ <key>ShowSearchButton</key>
+ <string>Mostra/oculta o botão de busca na bandeja inferior.</string>
+ <key>ShowSelectionBeam</key>
+ <string>Mostrar o feixe de partículas de seleção ao selecionar ou interagir com objetos.</string>
+ <key>ShowSnapshotButton</key>
+ <string>Mostra/oculta o botão de captura na bandeja inferior.</string>
+ <key>ShowStartLocation</key>
+ <string>Exibir o menu de local de início na tela de login.</string>
+ <key>ShowTangentBasis</key>
+ <string>Renderizar normal e binormal (depuração de bump mapping).</string>
+ <key>ShowTunedART</key>
+ <string>Mostrar o tempo de renderização atual, não o tempo pré-ajuste, na exibição do avatar.</string>
+ <key>ShowTutorial</key>
+ <string>Mostrar a janela de tutorial no login.</string>
+ <key>ShowVoiceChannelPopup</key>
+ <string>Controla a visibilidade do pop-up do canal de voz atual acima da aba de voz.</string>
+ <key>ShowVoiceVisualizersInCalls</key>
+ <string>Ativa visualizadores de voz no mundo, gestos de voz e sincronia labial durante chamadas em grupo ou P2P.</string>
+ <key>ShowVolumeSettingsPopup</key>
+ <string>Mostrar controle de volume individual para voz, efeitos sonoros, etc.</string>
+ <key>ShowWorldMapButton</key>
+ <string>Mostra/oculta o botão de mapa na bandeja inferior.</string>
+ <key>SimPendingUploads</key>
+ <string>Modo de estatística no painel de Estatísticas.</string>
+ <key>SimulateFBOFailure</key>
+ <string>[DEPURAÇÃO] Fazer allocateScreenBuffer retornar falso. Usado para testar o tratamento de erros.</string>
+ <key>SingleModeDoubleClickOpenWindow</key>
+ <string>Define a ação do duplo clique em pasta na visão de pasta única (0 - permanece na janela atual, 1 - abre uma nova janela).</string>
+ <key>SkinCurrent</key>
+ <string>O skin selecionado no momento.</string>
+ <key>SkinningSettingsFile</key>
+ <string>Nome do arquivo de configuração de cor do skin do cliente (por instalação).</string>
+ <key>SkipBenchmark</key>
+ <string>Se verdadeiro, desativa a execução do benchmark de GPU na inicialização (padrão para classe 1).</string>
+ <key>SkyAmbientScale</key>
+ <string>Controla a intensidade da luz ambiente, ou não direcional, do sol e da lua (fração ou múltiplo do nível de ambiente padrão).</string>
+ <key>SkyMoonDefaultPosition</key>
+ <string>Posição padrão do sol no céu (direção em coordenadas do mundo).</string>
+ <key>SkyNightColorShift</key>
+ <string>Controla a cor do luar (cor base aplicada à lua como fonte de luz).</string>
+ <key>SkyOverrideSimSunPosition</key>
+ <string>Substituir a posição do sol do simulador pela posição local definida.</string>
+ <key>SkySunDefaultPosition</key>
+ <string>Posição padrão do sol no céu (direção em coordenadas do mundo).</string>
+ <key>SnapEnabled</key>
+ <string>Ativar encaixe na grade.</string>
+ <key>SnapMargin</key>
+ <string>Controla a distância máxima entre janelas antes que elas se encaixem automaticamente (pixels).</string>
+ <key>SnapToMouseCursor</key>
+ <string>Ao encaixar na grade, centralizar o objeto no ponto de grade mais próximo do cursor do mouse.</string>
+ <key>SnapshotBaseDir</key>
+ <string>Caminho do último local de salvamento de capturas.</string>
+ <key>SnapshotBaseName</key>
+ <string>Padrão de nomenclatura das capturas.</string>
+ <key>SnapshotFormat</key>
+ <string>Salvar capturas neste formato (0 = PNG, 1 = JPEG, 2 = BMP).</string>
+ <key>SnapshotQuality</key>
+ <string>Configuração de qualidade dos JPEGs de cartão postal (0 = pior, 100 = melhor).</string>
+ <key>SocialPhotoResolution</key>
+ <string>Resolução padrão ao compartilhar foto usando os floaters sociais.</string>
+ <key>Socks5AuthType</key>
+ <string>Mecanismo de autenticação selecionado para o Socks5.</string>
+ <key>Socks5ProxyEnabled</key>
+ <string>Usar Proxy Socks5.</string>
+ <key>Socks5ProxyHost</key>
+ <string>Host do Proxy Socks 5.</string>
+ <key>Socks5ProxyPort</key>
+ <string>Porta do Proxy Socks 5.</string>
+ <key>SortFriendsFirst</key>
+ <string>Especifica se os amigos serão classificados primeiro no registro de chamadas.</string>
+ <key>SoundUploadFolder</key>
+ <string>Todos os uploads de sons serão armazenados neste diretório (UUID).</string>
+ <key>SpeakerParticipantRemoveDelay</key>
+ <string>Timeout para remover participantes que não estão no canal antes de removê-los da lista de oradores ativos (chat de texto/voz).</string>
+ <key>SpeedTest</key>
+ <string>Modo de teste de desempenho, sem rede.</string>
+ <key>SpellCheck</key>
+ <string>Verificação ortográfica.</string>
+ <key>SpellCheckDictionary</key>
+ <string>Dicionários primário e secundário atuais usados na verificação ortográfica.</string>
+ <key>StartUpToastLifeTime</key>
+ <string>Número de segundos em que um toast de inicialização existe.</string>
+ <key>StatsAutoRun</key>
+ <string>Reproduzir piloto automático.</string>
+ <key>StatsFile</key>
+ <string>Nome do arquivo de saída do log de estatísticas.</string>
+ <key>StatsFrametimeEventThreshold</key>
+ <string>O percentual que a diferença de frametime deve exceder para registrar um evento de frametime. 0,1 = 10%, 0,25 = 25%, etc.</string>
+ <key>StatsFrametimeSampleSeconds</key>
+ <string>O número de segundos para amostrar dados estendidos de frametime (percentis, desvio padrão).</string>
+ <key>StatsNumRuns</key>
+ <string>Repetir a reprodução do piloto automático este número de vezes.</string>
+ <key>StatsPilotFile</key>
+ <string>Nome do arquivo do caminho do piloto automático do log de estatísticas.</string>
+ <key>StatsPilotXMLFile</key>
+ <string>Nome do arquivo do caminho estendido do piloto automático do log de estatísticas.</string>
+ <key>StatsQuitAfterRuns</key>
+ <string>Encerrar o aplicativo após este número de execuções de reprodução do piloto automático.</string>
+ <key>StatsReportFileInterval</key>
+ <string>Intervalo para salvar os dados do arquivo de estatísticas do viewer.</string>
+ <key>StatsReportMaxDuration</key>
+ <string>Máximo de segundos para os dados do arquivo de estatísticas do viewer, evita arquivos enormes.</string>
+ <key>StatsReportSkipZeroDataSaves</key>
+ <string>No arquivo de dados de estatísticas do viewer, pular o salvamento da entrada se não houver dados.</string>
+ <key>StatsSessionTrackFrameStats</key>
+ <string>Rastrear estatísticas de renderização e de rede.</string>
+ <key>StatsSummaryFile</key>
+ <string>Nome do arquivo de resumo do log de estatísticas.</string>
+ <key>SwitchToSharedEnvAfterTeleport</key>
+ <string>Alternar para o ambiente compartilhado após o teletransporte.</string>
+ <key>SyncMaterialSettings</key>
+ <string>Sincronizar configurações de material.</string>
+ <key>SystemLanguage</key>
+ <string>Idioma indicado pelas configurações do sistema (para a interface).</string>
+ <key>TabToTextFieldsOnly</key>
+ <string>A tecla TAB leva você ao próximo campo de entrada de texto, em vez do próximo widget.</string>
+ <key>TargetFPS</key>
+ <string>FPS mínimo desejado.</string>
+ <key>TeleportArrivalDelay</key>
+ <string>Tempo a aguardar antes de exibir o mundo durante o teletransporte (segundos).</string>
+ <key>TeleportLocalDelay</key>
+ <string>Atraso para evitar teletransportes após iniciar um teletransporte dentro do sim (segundos).</string>
+ <key>TerrainColorHeightRange</key>
+ <string>Faixa de altitude na qual uma dada textura de terreno tem efeito (metros).</string>
+ <key>TerrainColorStartHeight</key>
+ <string>Altitude inicial para a texturização do terreno (metros).</string>
+ <key>TerrainPaintBitDepth</key>
+ <string>Profundidade de bits para futuras operações de paint map de terreno. Mín: 1. Máx: 8. Tem efeito quando o paint map é criado ou modificado. Modificações em um paintmap existente de profundidade de bits diferente terão precisão menor.</string>
+ <key>TerrainPaintResolution</key>
+ <string>Resolução do paint map do terreno em pixels. Arredondada para uma potência de dois. Mín: 16. Máx: RenderMaxTextureResolution. Tem efeito quando o paint map é criado.</string>
+ <key>TestGridStatusRSSFromFile</key>
+ <string>Apenas para testes: não atualizar o arquivo xml do rss a partir do servidor.</string>
+ <key>TextureCameraBoost</key>
+ <string>Quanto impulsionar a resolução de texturas que são importantes para a câmera.</string>
+ <key>TextureChannelPriority</key>
+ <string>Agressividade do streaming de textura por canal. X=normais, Y=difuso, Z=especular/metálico, W=emissivo. 1,0=base, maior=redução de resolução mais agressiva.</string>
+ <key>TextureDecodeDisabled</key>
+ <string>Se TRUE, não buscar nem decodificar nenhuma textura.</string>
+ <key>TextureDisable</key>
+ <string>Se TRUE, não carregar texturas para o conteúdo no mundo.</string>
+ <key>TextureDiscardBackgroundedTime</key>
+ <string>Especifica quanto tempo aguardar antes de descartar os dados de textura após o viewer ir para segundo plano (zero ou negativo para desativar).</string>
+ <key>TextureDiscardLevel</key>
+ <string>Especifica a resolução da textura (0 = mais alta, 5 = mais baixa).</string>
+ <key>TextureDiscardMinimizedTime</key>
+ <string>Especifica quanto tempo aguardar antes de descartar os dados de textura após o viewer ser minimizado (zero ou negativo para desativar).</string>
+ <key>TextureFetchConcurrency</key>
+ <string>Número máximo de conexões HTTP usadas para buscas de textura.</string>
+ <key>TextureFetchFakeFailureRate</key>
+ <string>Simular falhas de busca HTTP para algumas texturas assadas no servidor.</string>
+ <key>TextureFetchMinTimeToLog</key>
+ <string>Se o tempo de busca de textura exceder este valor, o testador de busca de textura registrará informações.</string>
+ <key>TextureFetchUpdateMinCount</key>
+ <string>Número mínimo de texturas a atualizar por quadro.</string>
+ <key>TextureLivePreview</key>
+ <string>Pré-visualizar seleções no seletor de textura ou de material imediatamente.</string>
+ <key>TextureLoadFullRes</key>
+ <string>Se TRUE, sempre carregar texturas em resolução total (discard = 0).</string>
+ <key>TextureLoggingThreshold</key>
+ <string>Especifica o limiar em bytes a partir do qual os dados de download de textura devem ser enviados ao sim.</string>
+ <key>TextureNewByteRange</key>
+ <string>Usar o novo cálculo de faixa de bytes mais preciso para os níveis de descarte j2c.</string>
+ <key>TextureReverseByteRange</key>
+ <string>Percentual mínimo da faixa de bytes ótima permitido para renderizar um dado nível de descarte.</string>
+ <key>TextureSaveLocation</key>
+ <string>Localização atual para salvar texturas em massa no disco.</string>
+ <key>TextureScaleMaxAreaFactor</key>
+ <string>Limita o quanto a escala da textura afeta o cálculo de área.</string>
+ <key>TextureScaleMinAreaFactor</key>
+ <string>Limita o quanto a escala da textura afeta o cálculo de área.</string>
+ <key>TextureUploadFolder</key>
+ <string>Todos os uploads de imagens (texturas) serão armazenados neste diretório (UUID).</string>
+ <key>ThreadPoolSizes</key>
+ <string>Mapa de substituições de tamanho para pools de threads específicos.</string>
+ <key>ThrottleBandwidthKBPS</key>
+ <string>Largura de banda máxima permitida de download (kilobits por segundo).</string>
+ <key>TipToastMessageLineCount</key>
+ <string>Contagem máxima de linhas da mensagem de texto no toast de dica.</string>
+ <key>ToastButtonWidth</key>
+ <string>Largura padrão dos botões no toast. Notas: se a largura necessária for menor que esta, o botão será redimensionado para o tamanho padrão, caso contrário para o necessário. A alteração deste parâmetro afetará o layout dos botões no toast de notificação.</string>
+ <key>ToastFadingTime</key>
+ <string>Número de segundos em que um toast desaparece.</string>
+ <key>ToastGap</key>
+ <string>Espaço entre toasts na tela (valor mín. é 5).</string>
+ <key>ToolTipDelay</key>
+ <string>Segundos antes de exibir a dica quando o mouse para sobre um elemento da interface.</string>
+ <key>ToolTipFadeTime</key>
+ <string>Segundos durante os quais a dica desaparece.</string>
+ <key>ToolTipFastDelay</key>
+ <string>Segundos antes de exibir a dica quando o mouse para sobre um elemento da interface (quando uma dica já está visível).</string>
+ <key>ToolTipVisibleTimeFar</key>
+ <string>Desaparecer a dica após o tempo passar (segundos) enquanto o mouse não está perto da dica.</string>
+ <key>ToolTipVisibleTimeNear</key>
+ <string>Desaparecer a dica após o tempo passar (segundos) enquanto o mouse está perto da dica ou da posição original.</string>
+ <key>ToolTipVisibleTimeOver</key>
+ <string>Desaparecer a dica após o tempo passar (segundos) enquanto o mouse está sobre a dica.</string>
+ <key>ToolboxAutoMove</key>
+ <string>[NÃO USADO]</string>
+ <key>TrackFocusObject</key>
+ <string>A câmera rastreia o último objeto no qual foi dado zoom.</string>
+ <key>TranslateChat</key>
+ <string>Traduzir mensagens de chat recebidas.</string>
+ <key>TranslateLanguage</key>
+ <string>Especificador do idioma de tradução.</string>
+ <key>TranslatingEnabled</key>
+ <string>Preferências de tradução definidas.</string>
+ <key>TranslationService</key>
+ <string>API de tradução a usar. (google|azure).</string>
+ <key>TuningFPSStrategy</key>
+ <string>Estratégia a usar ao ajustar o FPS. 0=Ajustar apenas a renderização de avatares, 1=Ajustar tanto avatares quanto configurações globais da cena, 2=Ajustar apenas a cena global.</string>
+ <key>TutorialURL</key>
+ <string>URL do item de menu de tutorial, definida automaticamente durante o login.</string>
+ <key>TypeAheadTimeout</key>
+ <string>Atraso antes de limpar o buffer de digitação antecipada nas listas (segundos).</string>
+ <key>UIAutoScale</key>
+ <string>Manter a escala da interface consistente entre diferentes resoluções.</string>
+ <key>UIButtonOrigHPad</key>
+ <string>Preenchimento horizontal original do botão da interface.</string>
+ <key>UICheckboxctrlHPad</key>
+ <string>Preenchimento horizontal do controle de caixa de seleção da interface.</string>
+ <key>UICheckboxctrlSpacing</key>
+ <string>Espaçamento do controle de caixa de seleção da interface.</string>
+ <key>UICheckboxctrlVPad</key>
+ <string>Preenchimento vertical do controle de caixa de seleção da interface.</string>
+ <key>UICloseBoxFromTop</key>
+ <string>Distância do topo do floater até o topo do ícone da caixa de fechar, em pixels.</string>
+ <key>UIExtraTriangleHeight</key>
+ <string>Altura do triângulo extra da interface.</string>
+ <key>UIExtraTriangleWidth</key>
+ <string>Largura do triângulo extra da interface.</string>
+ <key>UIFloaterCloseBoxSize</key>
+ <string>Tamanho da caixa de fechar do floater da interface.</string>
+ <key>UIFloaterHPad</key>
+ <string>Tamanho do preenchimento horizontal do floater da interface.</string>
+ <key>UIFloaterTestBool</key>
+ <string>Exemplo de configuração salva para o floater de teste.</string>
+ <key>UIImgDefaultAlphaUUID</key>
+ <string>UUID da textura padrão de alpha usada no editor de aparência.</string>
+ <key>UIImgDefaultEyesUUID</key>
+ <string>UUID da textura padrão de íris usada no editor de aparência.</string>
+ <key>UIImgDefaultGlovesUUID</key>
+ <string>UUID da textura padrão de luvas usada no editor de aparência.</string>
+ <key>UIImgDefaultHairUUID</key>
+ <string>UUID da textura padrão de cabelo usada no editor de aparência.</string>
+ <key>UIImgDefaultJacketUUID</key>
+ <string>UUID da textura padrão de jaqueta usada no editor de aparência.</string>
+ <key>UIImgDefaultPantsUUID</key>
+ <string>UUID da textura padrão de calça usada no editor de aparência.</string>
+ <key>UIImgDefaultShirtUUID</key>
+ <string>UUID da textura padrão de camisa usada no editor de aparência.</string>
+ <key>UIImgDefaultShoesUUID</key>
+ <string>UUID da textura padrão de sapatos usada no editor de aparência.</string>
+ <key>UIImgDefaultSkirtUUID</key>
+ <string>UUID da textura padrão de saia usada no editor de aparência.</string>
+ <key>UIImgDefaultSocksUUID</key>
+ <string>UUID da textura padrão de meias usada no editor de aparência.</string>
+ <key>UIImgDefaultUnderwearUUID</key>
+ <string>UUID da textura padrão de roupa íntima usada no editor de aparência.</string>
+ <key>UILineEditorCursorThickness</key>
+ <string>Espessura do cursor do editor de linha da interface.</string>
+ <key>UIMaxComboWidth</key>
+ <string>Largura máxima da caixa de combinação.</string>
+ <key>UIMinimizedWidth</key>
+ <string>Tamanho da largura minimizada do floater da interface.</string>
+ <key>UIMultiSliderctrlSpacing</key>
+ <string>Espaçamento do controle de múltiplos sliders da interface.</string>
+ <key>UIMultiTrackHeight</key>
+ <string>Altura da múltipla trilha da interface.</string>
+ <key>UIPreeditMarkerBrightness</key>
+ <string>Brilho do marcador de pré-edição da interface.</string>
+ <key>UIPreeditMarkerGap</key>
+ <string>Espaçamento do marcador de pré-edição da interface.</string>
+ <key>UIPreeditMarkerPosition</key>
+ <string>Posição do marcador de pré-edição da interface.</string>
+ <key>UIPreeditMarkerThickness</key>
+ <string>Espessura do marcador de pré-edição da interface.</string>
+ <key>UIPreeditStandoutBrightness</key>
+ <string>Brilho do destaque de pré-edição da interface.</string>
+ <key>UIPreeditStandoutGap</key>
+ <string>Espaçamento do destaque de pré-edição da interface.</string>
+ <key>UIPreeditStandoutPosition</key>
+ <string>Posição do destaque de pré-edição da interface.</string>
+ <key>UIPreeditStandoutThickness</key>
+ <string>Espessura do destaque de pré-edição da interface.</string>
+ <key>UIPreviewMaterial</key>
+ <string>Se a amostra de material PBR está ou não ativada.</string>
+ <key>UIResizeBarHeight</key>
+ <string>Tamanho da altura da barra de redimensionamento da interface.</string>
+ <key>UIScaleFactor</key>
+ <string>Tamanho da interface em relação ao layout padrão em tela de 1024x768.</string>
+ <key>UIScrollbarSize</key>
+ <string>Tamanho da barra de rolagem da interface.</string>
+ <key>UISliderctrlHeight</key>
+ <string>Altura do controle de slider da interface.</string>
+ <key>UISliderctrlSpacing</key>
+ <string>Espaçamento do controle de slider da interface.</string>
+ <key>UISndAlert</key>
+ <string>Arquivo de som para alertas (uuid do recurso de som).</string>
+ <key>UISndBadKeystroke</key>
+ <string>Arquivo de som para tecla inválida (uuid do recurso de som).</string>
+ <key>UISndChatMention</key>
+ <string>Arquivo de som para menção em chat (uuid do recurso de som).</string>
+ <key>UISndChatPing</key>
+ <string>Arquivo de som para ping de chat (uuid do recurso de som).</string>
+ <key>UISndClick</key>
+ <string>Arquivo de som para clique do mouse (uuid do recurso de som).</string>
+ <key>UISndClickRelease</key>
+ <string>Arquivo de som para soltar o botão do mouse (uuid do recurso de som).</string>
+ <key>UISndDebugSpamToggle</key>
+ <string>Registrar efeitos sonoros da interface conforme são reproduzidos.</string>
+ <key>UISndHealthReductionF</key>
+ <string>Arquivo de som para dor feminina (uuid do recurso de som).</string>
+ <key>UISndHealthReductionM</key>
+ <string>Arquivo de som para dor masculina (uuid do recurso de som).</string>
+ <key>UISndHealthReductionThreshold</key>
+ <string>Quantidade de redução de saúde necessária para acionar o som de "dor".</string>
+ <key>UISndInvalidOp</key>
+ <string>Arquivo de som para operações inválidas (uuid do recurso de som).</string>
+ <key>UISndMoneyChangeDown</key>
+ <string>Arquivo de som para aumento do saldo de L$ (uuid do recurso de som).</string>
+ <key>UISndMoneyChangeThreshold</key>
+ <string>Quantidade de mudança no saldo de L$ necessária para acionar o som de "dinheiro".</string>
+ <key>UISndMoneyChangeUp</key>
+ <string>Arquivo de som para diminuição do saldo de L$ (uuid do recurso de som).</string>
+ <key>UISndNewIncomingIMSession</key>
+ <string>Arquivo de som para nova sessão de mensagem instantânea (uuid do recurso de som).</string>
+ <key>UISndObjectCreate</key>
+ <string>Arquivo de som para criação de objeto (uuid do recurso de som).</string>
+ <key>UISndObjectDelete</key>
+ <string>Arquivo de som para exclusão de objeto (uuid do recurso de som).</string>
+ <key>UISndObjectRezIn</key>
+ <string>Arquivo de som para surgimento (rez) de objetos (uuid do recurso de som).</string>
+ <key>UISndObjectRezOut</key>
+ <string>Arquivo de som para desaparecimento (derez) de objetos (uuid do recurso de som).</string>
+ <key>UISndRestart</key>
+ <string>Arquivo de som para reinício de região (uuid do recurso de som).</string>
+ <key>UISndSnapshot</key>
+ <string>Arquivo de som para tirar uma captura (uuid do recurso de som).</string>
+ <key>UISndStartIM</key>
+ <string>Arquivo de som para iniciar uma nova sessão de IM (uuid do recurso de som).</string>
+ <key>UISndTeleportOut</key>
+ <string>Arquivo de som para teletransporte (uuid do recurso de som).</string>
+ <key>UISndTyping</key>
+ <string>Arquivo de som para começar a digitar uma mensagem de chat (uuid do recurso de som).</string>
+ <key>UISndWindowClose</key>
+ <string>Arquivo de som para fechar uma janela (uuid do recurso de som).</string>
+ <key>UISndWindowOpen</key>
+ <string>Arquivo de som para abrir uma janela (uuid do recurso de som).</string>
+ <key>UISpinctrlBtnHeight</key>
+ <string>Altura do botão do controle giratório da interface.</string>
+ <key>UISpinctrlBtnWidth</key>
+ <string>Largura do botão do controle giratório da interface.</string>
+ <key>UISpinctrlSpacing</key>
+ <string>Espaçamento do controle giratório da interface.</string>
+ <key>UITabCntrArrowBtnSize</key>
+ <string>Tamanho do botão de seta do contêiner de abas da interface.</string>
+ <key>UITabCntrButtonPanelOverlap</key>
+ <string>Sobreposição do painel de botões do contêiner de abas da interface.</string>
+ <key>UITabCntrCloseBtnSize</key>
+ <string>Tamanho do botão de fechar do contêiner de abas da interface.</string>
+ <key>UITabCntrTabHPad</key>
+ <string>Preenchimento horizontal da aba do contêiner de abas da interface.</string>
+ <key>UITabCntrTabPartialWidth</key>
+ <string>Largura parcial da aba do contêiner de abas da interface.</string>
+ <key>UITabCntrVertTabMinWidth</key>
+ <string>Largura mínima da aba vertical do contêiner de abas da interface.</string>
+ <key>UITabCntrvArrowBtnSize</key>
+ <string>Tamanho do botão de seta vertical do contêiner de abas da interface.</string>
+ <key>UITabCntrvPad</key>
+ <string>Preenchimento vertical do contêiner de abas da interface.</string>
+ <key>UITabPadding</key>
+ <string>Preenchimento da aba da interface.</string>
+ <key>UpdateAppWindowTitleBar</key>
+ <string>Atualiza a barra de título da janela do aplicativo com informações breves sobre usuário/localização.</string>
+ <key>UpdateRememberPasswordSetting</key>
+ <string>Salvar a configuração de 'lembrar senha' para o usuário atual.</string>
+ <key>UpdaterServiceSetting</key>
+ <string>Configurar o serviço de atualização.</string>
+ <key>UpdaterShowReleaseNotes</key>
+ <string>Ativa a exibição das notas de versão em um floater web após a atualização. 0 - não mostrar, 1 - mostrar, 2 - mostrar mesmo para viewers de teste.</string>
+ <key>UpdaterWillingToTest</key>
+ <string>Se o atualizador deve ou não oferecer upgrades Beta.</string>
+ <key>UploadBakedTexOld</key>
+ <string>Força o pipeline de texturas assadas a fazer upload usando o método antigo.</string>
+ <key>UploadsEveryoneCopy</key>
+ <string>Todos podem copiar o item recém-enviado.</string>
+ <key>UploadsNextOwnerCopy</key>
+ <string>Itens recém-enviados podem ser copiados pelo próximo proprietário.</string>
+ <key>UploadsNextOwnerModify</key>
+ <string>Itens recém-enviados podem ser modificados pelo próximo proprietário.</string>
+ <key>UploadsNextOwnerTransfer</key>
+ <string>Itens recém-enviados podem ser revendidos ou doados pelo próximo proprietário.</string>
+ <key>UploadsShareWithGroup</key>
+ <string>Itens recém-enviados são compartilhados com o grupo ativo no momento.</string>
+ <key>Use24HourClock</key>
+ <string>12 vs 24. No momento a cobertura é parcial.</string>
+ <key>UseAltKeyForMenus</key>
+ <string>Acessar menus pelo teclado tocando Alt.</string>
+ <key>UseChatBubbles</key>
+ <string>Mostrar o chat acima da cabeça dos avatares em balões de chat.</string>
+ <key>UseCircuitCodeMaxRetries</key>
+ <string>Contagem máxima de timeout para a mensagem inicial UseCircuitCode.</string>
+ <key>UseCircuitCodeTimeout</key>
+ <string>Duração do timeout em segundos para a mensagem inicial UseCircuitCode.</string>
+ <key>UseDebugLogin</key>
+ <string>Fornece controle extra sobre a qual grade se conectar.</string>
+ <key>UseDebugMenus</key>
+ <string>Ativa o menu "Debug".</string>
+ <key>UseDefaultColorPicker</key>
+ <string>Usar o seletor de cores fornecido pelo sistema operacional.</string>
+ <key>UseDisplayNames</key>
+ <string>Usar nomes novos, alteráveis e em unicode.</string>
+ <key>UseEnergy</key>
+ <string>Enviar mensagens de energia ao simulador (configuração de depuração legada).</string>
+ <key>UseFreezeFrame</key>
+ <string>Congelar o tempo ao tirar capturas.</string>
+ <key>UseGroupMemberPagination</key>
+ <string>Ativar paginação da lista de membros do grupo, 50 membros por vez.</string>
+ <key>UseNewWalkRun</key>
+ <string>Substituir as animações padrão de caminhar/correr por novas.</string>
+ <key>UseObjectCacheOcclusion</key>
+ <string>Ativar culling de objetos no nível do cache de objetos com base na oclusão (cobertura) por outros objetos.</string>
+ <key>UseOcclusion</key>
+ <string>Ativar culling de objetos com base na oclusão (cobertura) por outros objetos.</string>
+ <key>UsePeopleAPI</key>
+ <string>Usar a cap da people API para busca de nomes de avatares; usar o protocolo legado antigo se falso. Requer reinício.</string>
+ <key>UseStartScreen</key>
+ <string>Se deve ou não carregar uma imagem de tela de início.</string>
+ <key>UseWebPagesOnPrims</key>
+ <string>[NÃO USADO]</string>
+ <key>UserConnectionPort</key>
+ <string>Porta na qual este cliente transmite.</string>
+ <key>UserLogFile</key>
+ <string>Nome do arquivo de log especificado pelo usuário.</string>
+ <key>UserLoginInfo</key>
+ <string>Dados de login do usuário.</string>
+ <key>UserSessionSettingsFile</key>
+ <string>Configurações do usuário aplicadas por sessão (não salvas).</string>
+ <key>VelocityInterpolate</key>
+ <string>Extrapolar o movimento do objeto a partir do último pacote com base na velocidade recebida.</string>
+ <key>VersionChannelName</key>
+ <string>Informações de versão geradas ao executar o viewer.</string>
+ <key>VoiceAutomaticGainControl</key>
+ <string>Controle automático de ganho de voz.</string>
+ <key>VoiceCallsFriendsOnly</key>
+ <string>Aceitar apenas chamadas de voz e receber IMs de residentes na sua lista de amigos.</string>
+ <key>VoiceCallsRejectGroup</key>
+ <string>Rejeitar silenciosamente todas as chamadas de voz de grupo recebidas.</string>
+ <key>VoiceDisableMic</key>
+ <string>Desativar completamente a capacidade de abrir o microfone.</string>
+ <key>VoiceEarLocation</key>
+ <string>Localização do ouvido virtual para voz.</string>
+ <key>VoiceEchoCancellation</key>
+ <string>Cancelamento de eco de voz.</string>
+ <key>VoiceEffectDefault</key>
+ <string>Morfologia de voz selecionada.</string>
+ <key>VoiceImageLevel0</key>
+ <string>UUID de textura para o nível de imagem de voz 0.</string>
+ <key>VoiceImageLevel1</key>
+ <string>UUID de textura para o nível de imagem de voz 1.</string>
+ <key>VoiceImageLevel2</key>
+ <string>UUID de textura para o nível de imagem de voz 2.</string>
+ <key>VoiceImageLevel3</key>
+ <string>UUID de textura para o nível de imagem de voz 3.</string>
+ <key>VoiceImageLevel4</key>
+ <string>UUID de textura para o nível de imagem de voz 4.</string>
+ <key>VoiceImageLevel5</key>
+ <string>UUID de textura para o nível de imagem de voz 5.</string>
+ <key>VoiceImageLevel6</key>
+ <string>UUID de textura para o nível de imagem de voz 6.</string>
+ <key>VoiceInputAudioDevice</key>
+ <string>Dispositivo de entrada de áudio a usar para voz.</string>
+ <key>VoiceMorphingEnabled</key>
+ <string>Se deve ou não ativar os Voice Morphs e mostrar a interface.</string>
+ <key>VoiceNoiseSuppressionLevel</key>
+ <string>Nível de supressão de ruído de voz.</string>
+ <key>VoiceOutputAudioDevice</key>
+ <string>Dispositivo de saída de áudio a usar para voz.</string>
+ <key>VoiceParticipantLeftRemoveDelay</key>
+ <string>Timeout para remover participantes que saíram do chat de voz da lista no painel de controles de voz.</string>
+ <key>VoiceServerType</key>
+ <string>O tipo de servidor de voz a usar para chamadas em grupo, conferência e p2p.</string>
+ <key>VoiceVisualizerEnabled</key>
+ <string>Exibir o indicador de ponto de voz acima de um avatar.</string>
+ <key>WLSkyDetail</key>
+ <string>Controla o detalhe de vértices no céu WindLight. Números menores dão melhor desempenho e céus mais feios.</string>
+ <key>WarningsAsChat</key>
+ <string>Exibir mensagens de aviso no histórico de chat.</string>
+ <key>WatchdogEnabled</key>
+ <string>Controla se o temporizador watchdog de threads é ativado. O valor é S32. Defina -1 para usar o padrão embutido.</string>
+ <key>WaterGLFogDensityScale</key>
+ <string>Mapeia a densidade da névoa de água do shader para a densidade da névoa do gl.</string>
+ <key>WaterGLFogDepthFloor</key>
+ <string>Controla quão escura a névoa de água do gl pode ficar.</string>
+ <key>WaterGLFogDepthScale</key>
+ <string>Controla quão rapidamente a névoa do gl escurece debaixo d'água.</string>
+ <key>WearFolderLimit</key>
+ <string>Limita o número de itens na pasta que podem ser substituídos/adicionados ao visual atual.</string>
+ <key>WearablesEveryoneCopy</key>
+ <string>Todos podem copiar a roupa ou parte do corpo recém-criada.</string>
+ <key>WearablesNextOwnerCopy</key>
+ <string>A roupa ou parte do corpo recém-criada pode ser copiada pelo próximo proprietário.</string>
+ <key>WearablesNextOwnerModify</key>
+ <string>A roupa ou parte do corpo recém-criada pode ser modificada pelo próximo proprietário.</string>
+ <key>WearablesNextOwnerTransfer</key>
+ <string>A roupa ou parte do corpo recém-criada pode ser revendida ou doada pelo próximo proprietário.</string>
+ <key>WearablesShareWithGroup</key>
+ <string>Roupas ou partes do corpo recém-criadas são compartilhadas com o grupo ativo no momento.</string>
+ <key>WebContentWindowLimit</key>
+ <string>Número máximo de janelas do navegador web que podem estar abertas ao mesmo tempo no floater de conteúdo web (0 para sem limite).</string>
+ <key>WebProfileFloaterRect</key>
+ <string>Dimensões do floater de perfil web.</string>
+ <key>WindLightUseAtmosShaders</key>
+ <string>OBSOLETO (apenas true é suportado) - Se deve ativar ou desativar os shaders atmosféricos WindLight.</string>
+ <key>WindowHeight</key>
+ <string>Altura da janela do viewer SL.</string>
+ <key>WindowMaximized</key>
+ <string>Janela do viewer SL maximizada no login.</string>
+ <key>WindowWidth</key>
+ <string>Largura da janela do viewer SL.</string>
+ <key>WindowX</key>
+ <string>Coordenada X do canto superior esquerdo da janela do viewer SL, relativa ao canto superior esquerdo da tela principal (pixels).</string>
+ <key>WindowY</key>
+ <string>Coordenada Y do canto superior esquerdo da janela do viewer SL, relativa ao canto superior esquerdo da tela principal (pixels).</string>
+ <key>XferThrottle</key>
+ <string>Largura de banda máxima permitida de download para transferências de asset (bits por segundo).</string>
+ <key>YawFromMousePosition</key>
+ <string>Faixa horizontal na qual a cabeça do avatar acompanha a posição do mouse (graus de rotação da cabeça da esquerda da janela até a direita).</string>
+ <key>YieldTime</key>
+ <string>Ceder algum tempo ao host local.</string>
+ <key>YouAreHereDistance</key>
+ <string>Raio de distância para o banner que indica se o residente está "no" Lugar (metros do avatar até o lugar solicitado).</string>
+ <key>ZoomDirect</key>
+ <string>Mapear o eixo de zoom do joystick diretamente para o zoom da câmera.</string>
+ <key>ZoomTime</key>
+ <string>Tempo de transição entre diferentes modos de câmera (segundos).</string>
+ <key>always_showable_floaters</key>
+ <string>Floaters que podem ser exibidos mesmo no modo mouselook.</string>
+ <key>max_texture_dimension_X</key>
+ <string>Largura máxima de textura para texturas enviadas pelo usuário.</string>
+ <key>max_texture_dimension_Y</key>
+ <string>Altura máxima de textura para texturas enviadas pelo usuário.</string>
+ <key>moapbeacon</key>
+ <string>Marcador / Destaque de fontes de mídia em prim.</string>
+ <key>moonbeacon</key>
+ <string>Mostrar a direção da Lua.</string>
+ <key>particlesbeacon</key>
+ <string>Marcador / Destaque de geradores de partículas.</string>
+ <key>physicalbeacon</key>
+ <string>Marcador / Destaque de objetos físicos.</string>
+ <key>renderbeacons</key>
+ <string>Marcador / Destaque de geradores de partículas.</string>
+ <key>renderhighlights</key>
+ <string>Marcador / Destaque de objetos com script e função de toque.</string>
+ <key>scriptsbeacon</key>
+ <string>Marcador / Destaque de objetos com script.</string>
+ <key>scripttouchbeacon</key>
+ <string>Marcador / Destaque de objetos com script e função de toque.</string>
+ <key>soundsbeacon</key>
+ <string>Marcador / Destaque de geradores de som.</string>
+ <key>sourceid</key>
+ <string>Identifica a agência de referência para os servidores web da Linden.</string>
+ <key>sunbeacon</key>
+ <string>Mostrar a direção do Sol.</string>
+ <key>teleport_offer_invitation_max_length</key>
+ <string>Comprimento máximo do editor da linha de convite de oferta de teletransporte. 254 - max_location_url_length(76) = 178.</string>
+</map>
+</llsd>
diff --git a/indra/newview/skins/default/xui/pt/sidepanel_item_info.xml b/indra/newview/skins/default/xui/pt/sidepanel_item_info.xml
index 9afcb67d3d..33bd389c01 100644
--- a/indra/newview/skins/default/xui/pt/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/pt/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
Descrição:
</text>
- <line_editor name="LabelItemDesc" tool_tip="Quando as pessoas selecionam &apos;Passe o mouse em todos os objetos’ na configuração de visualização, eles verão a descrição dos objetos em uma janela pop-up sempre que passar o mouse por cima. A descrição prim é limitada em 127 bytes, qualquer string maior ficará truncada."/>
+ <text_editor name="LabelItemDesc" tool_tip="Quando as pessoas selecionam &apos;Passe o mouse em todos os objetos’ na configuração de visualização, eles verão a descrição dos objetos em uma janela pop-up sempre que passar o mouse por cima. A descrição prim é limitada em 127 bytes, qualquer string maior ficará truncada."/>
<text name="LabelCreatorTitle">
Criador:
</text>
diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml
index 543fd45573..3d03a71e81 100644
--- a/indra/newview/skins/default/xui/pt/strings.xml
+++ b/indra/newview/skins/default/xui/pt/strings.xml
@@ -5406,9 +5406,6 @@ Tente colocar o caminho do editor entre aspas.
<string name="Command_Grid_Status_Label">
Status da grade
</string>
- <string name="Command_HowTo_Label">
- Como
- </string>
<string name="Command_Inventory_Label">
Inventário
</string>
@@ -5505,9 +5502,6 @@ Tente colocar o caminho do editor entre aspas.
<string name="Command_Grid_Status_Tooltip">
Mostrar status da grade atual
</string>
- <string name="Command_HowTo_Tooltip">
- Como executar tarefas comuns
- </string>
<string name="Command_Inventory_Tooltip">
Exibir e usar seus pertences
</string>
diff --git a/indra/newview/skins/default/xui/ru/floater_how_to.xml b/indra/newview/skins/default/xui/ru/floater_how_to.xml
deleted file mode 100644
index 52525e5d33..0000000000
--- a/indra/newview/skins/default/xui/ru/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="ПОМОЩЬ"/>
diff --git a/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml
index 30a196b1ca..65dd67c78a 100644
--- a/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="СКРИПТ: СОЗДАТЬ СКРИПТ">
<floater.string name="not_allowed">
- Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрана категория «не копируемые». Для просмотра или изменения скрипта в объекте нужны полные права доступа.
+ Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта нужны эти права доступа.
</floater.string>
<floater.string name="script_running">
Выполняется
diff --git a/indra/newview/skins/default/xui/ru/notifications.xml b/indra/newview/skins/default/xui/ru/notifications.xml
index e75fd1fd82..96eb917e39 100644
--- a/indra/newview/skins/default/xui/ru/notifications.xml
+++ b/indra/newview/skins/default/xui/ru/notifications.xml
@@ -1459,7 +1459,6 @@
Ваш персонаж появится через мгновение.
Для ходьбы нажимайте клавиши со стрелками.
-В любой момент можно нажать клавишу F1 для получения справки или информации о [SECOND_LIFE].
Выберите мужской или женский аватар. Этот выбор затем можно будет изменить.
<usetemplate name="okcancelbuttons" notext="Женщина" yestext="Мужчина"/>
</notification>
diff --git a/indra/newview/skins/default/xui/ru/panel_preferences_setup.xml b/indra/newview/skins/default/xui/ru/panel_preferences_setup.xml
index 137e047d97..cec72ebe21 100644
--- a/indra/newview/skins/default/xui/ru/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/ru/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
Настройки прокси-сервера:
</text>
<button label="Задать настройки" label_selected="Обзор" name="set_proxy"/>
+ <text name="os_hibernation_mode">
+ Предотвращать гибернацию ОС, если пользователь не отошел:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Предотвращает переход ОС в сон, только если пользователь не отошел">
+ <combo_box.item label="Гибернация разрешена" name="can_hibernate"/>
+ <combo_box.item label="Гибернация запрещена, экран можно выключать" name="cant_hibernate"/>
+ <combo_box.item label="Гибернация запрещена, экран нельзя выключать" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/ru/panel_script_ed.xml b/indra/newview/skins/default/xui/ru/panel_script_ed.xml
index 54a1eaab75..6322917e8e 100644
--- a/indra/newview/skins/default/xui/ru/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/ru/panel_script_ed.xml
@@ -4,7 +4,7 @@
Загрузка...
</panel.string>
<panel.string name="can_not_view">
- Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрана категория «не копируемые». Для просмотра или изменения скрипта в объекте нужны полные права доступа.
+ Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта нужны эти права доступа.
</panel.string>
<panel.string name="public_objects_can_not_run">
Общедоступные объекты не могут запускать скрипты
diff --git a/indra/newview/skins/default/xui/ru/sidepanel_item_info.xml b/indra/newview/skins/default/xui/ru/sidepanel_item_info.xml
index 79a645f536..e9e03fc52d 100644
--- a/indra/newview/skins/default/xui/ru/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/ru/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
Описание:
</text>
- <line_editor name="LabelItemDesc" tool_tip="Когда в настройках окна просмотра выбрано &apos;Всплывающие подсказки на всех объектах&apos;, у игроков появляется всплывающее окошко с описанием объекта при наведении мыши на любой объект. Описание примитива ограничивается 127 байтами и строка со значением, превышающим этот размер, усекается."/>
+ <text_editor name="LabelItemDesc" tool_tip="Когда в настройках окна просмотра выбрано &apos;Всплывающие подсказки на всех объектах&apos;, у игроков появляется всплывающее окошко с описанием объекта при наведении мыши на любой объект. Описание примитива ограничивается 127 байтами и строка со значением, превышающим этот размер, усекается."/>
<text name="LabelCreatorTitle">
Создатель:
</text>
diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml
index 8433f893b5..86973c6ae7 100644
--- a/indra/newview/skins/default/xui/ru/strings.xml
+++ b/indra/newview/skins/default/xui/ru/strings.xml
@@ -5544,9 +5544,6 @@ support@secondlife.com.
<string name="Command_Grid_Status_Label">
Состояние сетки
</string>
- <string name="Command_HowTo_Label">
- Инструкции
- </string>
<string name="Command_Inventory_Label">
Инвентарь
</string>
@@ -5643,9 +5640,6 @@ support@secondlife.com.
<string name="Command_Grid_Status_Tooltip">
Показать текущее состояние сетки
</string>
- <string name="Command_HowTo_Tooltip">
- Выполнение типичных задач
- </string>
<string name="Command_Inventory_Tooltip">
Просмотр и использование вашего имущества
</string>
diff --git a/indra/newview/skins/default/xui/tr/floater_how_to.xml b/indra/newview/skins/default/xui/tr/floater_how_to.xml
deleted file mode 100644
index a42fe0b122..0000000000
--- a/indra/newview/skins/default/xui/tr/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="NASIL YAPILIR"/>
diff --git a/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml
index 760948edae..c53e12fc0e 100644
--- a/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="KOMUT DOSYASI: YENİ KOMUT DOSYASI">
<floater.string name="not_allowed">
- Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü &quot;kopyalanamaz&quot; olarak ayarlanmıştır. Bir nesnenin içerisindeki bir komut dosyasını görüntülemek veya düzenlemek için tam izinlere ihtiyacınız var.
+ Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü &quot;kopyalanamaz&quot; veya &quot;değiştirilemez&quot; olarak ayarlanmıştır. Bir komut dosyasını görüntülemek veya düzenlemek için bu izinlere ihtiyacınız var.
</floater.string>
<floater.string name="script_running">
Çalışıyor
diff --git a/indra/newview/skins/default/xui/tr/notifications.xml b/indra/newview/skins/default/xui/tr/notifications.xml
index 17d2969d19..de429f3292 100644
--- a/indra/newview/skins/default/xui/tr/notifications.xml
+++ b/indra/newview/skins/default/xui/tr/notifications.xml
@@ -1460,7 +1460,6 @@ Yeni bir ana konum ayarlamak isteyebilirsiniz.
Karakteriniz birazdan görünecek.
Yürümek için ok tuşlarını kullanın.
-Yardım almak ya da [SECOND_LIFE] hakkında daha fazla bilgi edinmek için istediğiniz zaman F1 tuşuna basın.
Lütfen bir erkek ya da kadın avatar seçin. Fikrinizi daha sonra değiştirebilirsiniz.
<usetemplate name="okcancelbuttons" notext="Kadın" yestext="Erkek"/>
</notification>
diff --git a/indra/newview/skins/default/xui/tr/panel_preferences_setup.xml b/indra/newview/skins/default/xui/tr/panel_preferences_setup.xml
index a20d0e5ff1..a3d99ac01f 100644
--- a/indra/newview/skins/default/xui/tr/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/tr/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
Proxy Ayarları:
</text>
<button label="Proxy ayarlarını yap" label_selected="Gözat" name="set_proxy"/>
+ <text name="os_hibernation_mode">
+ Uzakta değilse işletim sisteminin hazırda beklemesini engelle:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="Yalnızca kullanıcı uzakta değilse işletim sisteminin uykuya geçmesini engeller">
+ <combo_box.item label="Hazırda bekleyebilir" name="can_hibernate"/>
+ <combo_box.item label="Hazırda bekleyemez, ekran kapanabilir" name="cant_hibernate"/>
+ <combo_box.item label="Hazırda bekleyemez, ekran kapanamaz" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/tr/panel_script_ed.xml b/indra/newview/skins/default/xui/tr/panel_script_ed.xml
index 83040d06e4..e037a79e99 100644
--- a/indra/newview/skins/default/xui/tr/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/tr/panel_script_ed.xml
@@ -4,7 +4,7 @@
Yükleniyor...
</panel.string>
<panel.string name="can_not_view">
- Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü &quot;kopyalanamaz&quot; olarak ayarlanmıştır. Bir nesnenin içerisindeki bir komut dosyasını görüntülemek veya düzenlemek için tam izinlere ihtiyacınız var.
+ Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü &quot;kopyalanamaz&quot; veya &quot;değiştirilemez&quot; olarak ayarlanmıştır. Bir komut dosyasını görüntülemek veya düzenlemek için bu izinlere ihtiyacınız var.
</panel.string>
<panel.string name="public_objects_can_not_run">
Kamuya Açık Nesneler komut dosyalarını çalıştıramaz
diff --git a/indra/newview/skins/default/xui/tr/sidepanel_item_info.xml b/indra/newview/skins/default/xui/tr/sidepanel_item_info.xml
index 9c1b6a8dfd..fcee742590 100644
--- a/indra/newview/skins/default/xui/tr/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/tr/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
Açıklama:
</text>
- <line_editor name="LabelItemDesc" tool_tip="Görüntüleyici ayarlarında &apos;İpuçlarını Tüm Nesnelerin Üzerine Getir&apos; seçildiğinde, fare imlecinin altındaki herhangi bir nesneye ait nesne açıklaması açılır penceresi görülür. Prim (temel öğe) açıklaması 127 bayt ile sınırlıdır ve bundan daha uzun her dize kesilir."/>
+ <text_editor name="LabelItemDesc" tool_tip="Görüntüleyici ayarlarında &apos;İpuçlarını Tüm Nesnelerin Üzerine Getir&apos; seçildiğinde, fare imlecinin altındaki herhangi bir nesneye ait nesne açıklaması açılır penceresi görülür. Prim (temel öğe) açıklaması 127 bayt ile sınırlıdır ve bundan daha uzun her dize kesilir."/>
<text name="LabelCreatorTitle">
Oluşturan:
</text>
diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml
index e68d000179..3eaa6a6492 100644
--- a/indra/newview/skins/default/xui/tr/strings.xml
+++ b/indra/newview/skins/default/xui/tr/strings.xml
@@ -5539,9 +5539,6 @@ Düzenleyici yolunu çift tırnakla çevrelemeyi deneyin.
<string name="Command_Grid_Status_Label">
Ağ durumu
</string>
- <string name="Command_HowTo_Label">
- Nasıl yapılır
- </string>
<string name="Command_Inventory_Label">
Envanter
</string>
@@ -5638,9 +5635,6 @@ Düzenleyici yolunu çift tırnakla çevrelemeyi deneyin.
<string name="Command_Grid_Status_Tooltip">
Ağın mevcut durumunu göster
</string>
- <string name="Command_HowTo_Tooltip">
- Genel görevleri nasıl yapacağınız
- </string>
<string name="Command_Inventory_Tooltip">
Eşyalarınızı görüntüleyin ve kullanın
</string>
diff --git a/indra/newview/skins/default/xui/zh/floater_how_to.xml b/indra/newview/skins/default/xui/zh/floater_how_to.xml
deleted file mode 100644
index e033327165..0000000000
--- a/indra/newview/skins/default/xui/zh/floater_how_to.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<floater name="floater_how_to" title="簡易教學"/>
diff --git a/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml
index e971b166f7..ed7c127a6c 100644
--- a/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml
+++ b/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="script ed float" title="腳本: 新腳本">
<floater.string name="not_allowed">
- 你無法察看或編輯這腳本,它被設為「禁止複製」。 你需要完整權限去察看或編輯物件內的腳本。
+ 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯腳本。
</floater.string>
<floater.string name="script_running">
執行中
diff --git a/indra/newview/skins/default/xui/zh/notifications.xml b/indra/newview/skins/default/xui/zh/notifications.xml
index 4d0f1cb85b..fd0b613dd6 100644
--- a/indra/newview/skins/default/xui/zh/notifications.xml
+++ b/indra/newview/skins/default/xui/zh/notifications.xml
@@ -1446,7 +1446,6 @@
你的人物很快將會出現。
用方向鍵行走。
-任何時候你都可按 F1 鍵察看幫助,進一步瞭解 [SECOND_LIFE]。
請選擇男性或女性化身。 以後你仍可改變這個選擇。
<usetemplate name="okcancelbuttons" notext="女性" yestext="男性"/>
</notification>
diff --git a/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml b/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml
index 6ef25ad96e..c83a7512e0 100644
--- a/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml
+++ b/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml
@@ -36,4 +36,12 @@
代理伺服器設定:
</text>
<button label="調整代理伺服器設定" label_selected="瀏覽" name="set_proxy"/>
+ <text name="os_hibernation_mode">
+ 不處於離開狀態時阻止作業系統休眠:
+ </text>
+ <combo_box name="os_hibernation_combobox" tool_tip="僅在使用者不處於離開狀態時阻止作業系統睡眠">
+ <combo_box.item label="可休眠" name="can_hibernate"/>
+ <combo_box.item label="不可休眠,可關閉螢幕" name="cant_hibernate"/>
+ <combo_box.item label="不可休眠,不可關閉螢幕" name="cant_hibernate_screen"/>
+ </combo_box>
</panel>
diff --git a/indra/newview/skins/default/xui/zh/panel_script_ed.xml b/indra/newview/skins/default/xui/zh/panel_script_ed.xml
index c64982b397..a7f071806c 100644
--- a/indra/newview/skins/default/xui/zh/panel_script_ed.xml
+++ b/indra/newview/skins/default/xui/zh/panel_script_ed.xml
@@ -4,7 +4,7 @@
載入中...
</panel.string>
<panel.string name="can_not_view">
- 你無法察看或編輯這腳本,它被設為「禁止複製」。 你需要完整權限去察看或編輯物件內的腳本。
+ 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯腳本。
</panel.string>
<panel.string name="public_objects_can_not_run">
公開物件不能執行腳本
diff --git a/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml b/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml
index 61c52066d0..4737fe8776 100644
--- a/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml
@@ -38,7 +38,7 @@
<text name="LabelItemDescTitle">
描述:
</text>
- <line_editor name="LabelItemDesc" tool_tip="當人們在瀏覽器設定中選擇了「針對所有物件懸置顯示提醒」時,他們滑鼠所指的物件將會彈出描述該物件的內容。 幾何元件的描述內容不得超過127位元組,否則會被截短。"/>
+ <text_editor name="LabelItemDesc" tool_tip="當人們在瀏覽器設定中選擇了「針對所有物件懸置顯示提醒」時,他們滑鼠所指的物件將會彈出描述該物件的內容。 幾何元件的描述內容不得超過127位元組,否則會被截短。"/>
<text name="LabelCreatorTitle">
創造者:
</text>
diff --git a/indra/newview/skins/default/xui/zh/strings.xml b/indra/newview/skins/default/xui/zh/strings.xml
index cf6fa1d85f..1a094ea258 100644
--- a/indra/newview/skins/default/xui/zh/strings.xml
+++ b/indra/newview/skins/default/xui/zh/strings.xml
@@ -5531,9 +5531,6 @@ http://secondlife.com/support 求助解決問題。
<string name="Command_Grid_Status_Label">
網格狀態
</string>
- <string name="Command_HowTo_Label">
- 簡易教學
- </string>
<string name="Command_Inventory_Label">
收納區
</string>
@@ -5630,9 +5627,6 @@ http://secondlife.com/support 求助解決問題。
<string name="Command_Grid_Status_Tooltip">
顯示當前網格狀態
</string>
- <string name="Command_HowTo_Tooltip">
- 如何完成常用的動作
- </string>
<string name="Command_Inventory_Tooltip">
察看並使用你擁有的物件
</string>
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 7323658fa6..326b3adaaf 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -774,7 +774,7 @@ class Windows_x86_64_Manifest(ViewerManifest):
pack_version = '.'.join(self.args['version'][:3])
if len(self.args['version']) > 3 and self.args['version'][3]:
pack_version += '-' + self.args['version'][3]
- pack_title = self.app_name() # Display name with spaces
+ pack_title = pack_id #Wrapper exe, don't use spaces
pack_dir = self.get_dst_prefix()
main_exe = self.final_exe()
installer_base = self.installer_base_name()