Merge pull request #85 from overte-org/master
[pull] master from overte-org:master
29
.github/workflows/linux_server_build.yml
vendored
|
@ -26,8 +26,8 @@ env:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
# Only run master or tagged builds, or PRs if labeled as "server"
|
||||
if: contains( github.event.pull_request.labels.*.name, 'server') || github.event_name != 'pull_request'
|
||||
# Only run master or tagged builds, or PRs when labeled as "server"
|
||||
if: github.event.label.name == 'server'|| github.event_name != 'pull_request'
|
||||
name: "${{matrix.os}}, ${{matrix.arch}}"
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -93,12 +93,12 @@ jobs:
|
|||
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
|
||||
|
||||
- os: fedora-40
|
||||
image: docker.io/overte/overte-server-build:0.1.4-fedora-39-amd64
|
||||
image: docker.io/overte/overte-server-build:0.1.4-fedora-40-amd64
|
||||
arch: amd64
|
||||
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
|
||||
|
||||
- os: fedora-40
|
||||
image: docker.io/overte/overte-server-build:0.1.4-fedora-39-aarch64
|
||||
image: docker.io/overte/overte-server-build:0.1.4-fedora-40-aarch64
|
||||
arch: aarch64
|
||||
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
|
||||
|
||||
|
@ -132,6 +132,8 @@ jobs:
|
|||
echo "GIT_COMMIT_SHORT=`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
echo "REFNAME=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
|
||||
echo "JOB_NAME=${{matrix.os}}, ${{matrix.arch}}" >> $GITHUB_ENV
|
||||
|
||||
echo "CMAKE_BUILD_EXTRA=-- -j$(nproc)" >> $GITHUB_ENV
|
||||
|
@ -184,7 +186,8 @@ jobs:
|
|||
echo "RELEASE_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
|
||||
else # tagged
|
||||
echo "DEBVERSION=${{ github.ref_name }}-$GIT_COMMIT_SHORT-${{ matrix.os }}" >> $GITHUB_ENV
|
||||
echo "RPMVERSION=${{ github.ref_name }}.$GIT_COMMIT_SHORT" >> $GITHUB_ENV
|
||||
# We remove all dash characters from RPM versions, because rpmbuild doesn't allow dashes in version numbers.
|
||||
echo "RPMVERSION=${REFNAME//-}.$GIT_COMMIT_SHORT" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then # tagged
|
||||
|
@ -199,15 +202,13 @@ jobs:
|
|||
|
||||
echo "BUILD_NUMBER=$GIT_COMMIT_SHORT" >> $GITHUB_ENV
|
||||
|
||||
if [ -z "$CMAKE_BACKTRACE_URL" ]; then
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
export CMAKE_BACKTRACE_URL="${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}"
|
||||
export CMAKE_BACKTRACE_TOKEN="${{ github.ref_name }}_${{ matrix.os }}_${{ github.sha }}"
|
||||
else
|
||||
# We're building a PR, default to the PR endpoint
|
||||
export CMAKE_BACKTRACE_URL="https://o4504831972343808.ingest.sentry.io/api/4504832427950080/minidump/?sentry_key=f511de295975461b8f92a36f4a4a4f32"
|
||||
export CMAKE_BACKTRACE_TOKEN="server_pr_${{ github.event.number }}_${{ github.sha }}"
|
||||
fi
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
export CMAKE_BACKTRACE_URL="${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}"
|
||||
export CMAKE_BACKTRACE_TOKEN="${{ github.ref_name }}_${{ matrix.os }}_${{ github.sha }}"
|
||||
else
|
||||
# We're building a PR, default to the PR endpoint
|
||||
export CMAKE_BACKTRACE_URL="https://o4504831972343808.ingest.sentry.io/api/4504832427950080/minidump/?sentry_key=f511de295975461b8f92a36f4a4a4f32"
|
||||
export CMAKE_BACKTRACE_TOKEN="server_pr_${{ github.event.number }}_${{ github.sha }}"
|
||||
fi
|
||||
|
||||
- name: Configure Build Environment 3
|
||||
|
|
77
CHANGELOG.md
|
@ -12,14 +12,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
This project does **not** adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
<!-- ## [Unreleased] 2023.07.22 -->
|
||||
<!-- ## [2023.07.1] 2023.07.22 -->
|
||||
<!-- ## [Unreleased] 2023.07.2 -->
|
||||
|
||||
<!--
|
||||
### Misc
|
||||
- Updated the Unity Avatar Exporter and added Linux support
|
||||
- Added Linux support to the Unity Avatar Exporter
|
||||
-->
|
||||
<!-- ## [2023.07.1] 2023.07.2 -->
|
||||
|
||||
<!-- ## [2023.06.1] 2023.06.12 -->
|
||||
|
||||
## [2023.11.1] 2023.11.24
|
||||
|
||||
### Fixes
|
||||
- Fixed color conversion for glTF material colors (PR307)
|
||||
|
@ -36,14 +35,37 @@ This project does **not** adhere to [Semantic Versioning](https://semver.org/spe
|
|||
- Hugely improved Create app performance in Domains with many entities (PR498)
|
||||
- Fixed an issue that could cause laser pointers to rapidly flash (PR495)
|
||||
- Fixed the connection failure dialog mentioning "Explore" instead of "Places" (PR515)
|
||||
- Fixed broken documentation and code-completion of the "Script" API namespace (PR450)
|
||||
- Fixed warning about ForceHtmlAudioOutputDeviceUpdate (PR473)
|
||||
- Fixed Shield bubble dimensions (PR510)
|
||||
- Fixed Places app connecting to IP instead of Place name (PR522)
|
||||
- Fixed Interface scripts failing to shut down (PR521)
|
||||
- Fixed deadlock related to entity script engines (PR540)
|
||||
- Fixed leave and join messages in Chat (PR573)
|
||||
- Fixed crash when closing script log window (PR520)
|
||||
- Fixed some API documentation (PR598)
|
||||
- Fixed some missing assets, notably the sound when successfully shaking hands (PR590)
|
||||
- Fixed multiple script engine reload and shutdown related crashes (PR574)
|
||||
- Fixed flow bones on avatars with scale ≠ 100 (PR604)
|
||||
- Fixed curved flow bone chains (PR604)
|
||||
- Fixed invisible cursor (PR629)
|
||||
- Fixed loading avatars from URLs containing" =" such as Dropbox (PR634)
|
||||
- Fixed MicBar type error spam on Windows (PR669)
|
||||
- Fixed grabbing local entities in VR (PR671)
|
||||
- Fixed memory leak in entity server and improved its performance (PR690)
|
||||
- Fixed chat bar appearing in VR (PR672)
|
||||
- Fixed issues with third-party apps such as ALVR, Virtual Desktop and Streaming Assistant (PR700,PR714)
|
||||
- Fixed custom graphics settings not being saved (PR706)
|
||||
- Fixed Script.require behavior (PR697)
|
||||
- Fixed Entities.setLocalJointRotation not updating (PR708)
|
||||
- Improved client performance by moving squeezeHands.js to separate thread (PR737)
|
||||
|
||||
### Changes
|
||||
- Replaced Vircadia Metaverse Server with a testing server as federation default (PR330)
|
||||
- An empty audio device list now throws a warning instead of just a debug message (PR347)
|
||||
- Increased the maximum log file size from 512 kiB to 10 MiB (PR342,PR513)
|
||||
- Decreased the amount of retained log files from 100 to 20 (PR342)
|
||||
- Pressing the Return key with the the address/search bar in the Places App selected now navigates you to that address (PR403)
|
||||
- Replaced QT Script with V8 scripting engine (PR185,PR507,PR519)
|
||||
- Replaced QT Script with V8 scripting engine (PR185,PR507,PR519,PR566)
|
||||
This is a huge change under the hood, which ended up fixing a lot of issues.
|
||||
Since the new scripting engine does not behave exactly the same as the old one,
|
||||
some scripts might need fixing. The new scripting engine is especially picky when it comes to undefined behaviour.
|
||||
|
@ -54,6 +76,14 @@ This project does **not** adhere to [Semantic Versioning](https://semver.org/spe
|
|||
It will also ask once in case of a non-stable build.
|
||||
- Changed the VR overlay to only recenter when moving (PR478)
|
||||
- Added a workaround that prevents most users from needing to press down on the thumbstick to move (PR481,PR512)
|
||||
- Lowered inertia while moving (PR542)
|
||||
- Lowered control delays in VR (PR542)
|
||||
Configurable under Settings → Controls → Calibration
|
||||
- Changed Home button in Places app to lead to the tutorial by default (PR560)
|
||||
- Rewritten tutorial wizard in QML (PR645,PR737)
|
||||
- Disabled Oculus VR plugin by default (PR700,PR714)
|
||||
- Changed gravity constant to be more realistic (PR729)
|
||||
This fixes being catapulted into the air when moving up a slope. It also improves taking off, flying, and general movement.
|
||||
|
||||
### Additions
|
||||
- Added option to graphics menu for choosing which screen to use for full screen mode (PR302)
|
||||
|
@ -62,12 +92,28 @@ This project does **not** adhere to [Semantic Versioning](https://semver.org/spe
|
|||
This allows typing in languages like Japanese or Chinese that make use of an IME.
|
||||
- Added vertical Field Of View setting to graphics menu (PR465)
|
||||
- Added crash reporting to the Domain server, Assignment client, and Oven (PR482)
|
||||
- Added JavaScript profiling API (PR564)
|
||||
- Added require() to global scope in scipting API (PR585)
|
||||
- Added support for HDR lightmaps (PR611)
|
||||
- Added mouse look (PR607,PR624,PR627,PR662)
|
||||
- Dropbox URLs to assets now get rewritten to DDL URLs (PR636)
|
||||
- Added development script to configure avatar smoothing (PR579)
|
||||
- Added distance based LOD (PR663)
|
||||
Configurable under Settings → Graphics → Target frame rate
|
||||
- Added support for QML inside web-entities (PR645)
|
||||
QML files must be whitelisted in the settings.
|
||||
- Added Discord rich presence support (PR686,PR723)
|
||||
- Added command line arguments to ICE server (PR722)
|
||||
|
||||
### Removals
|
||||
- Removed outdated Inventory and Marketplace options from Wearables UI (PR303)
|
||||
- Removed outdated Beacon system (PR327)
|
||||
- Removed long deprecated styles-uit and controls-uit QML modules (PR380)
|
||||
- Removed outdated Marketplace and Wallet code (PR381,PR477,PR487)
|
||||
- Removed Appreciate app from defaults (PR563)
|
||||
- Removed debug messages from Places app (PR561)
|
||||
- Removed JQuery dependency from Emote app (PR560)
|
||||
- Removed File API (PR691)
|
||||
|
||||
### Build system
|
||||
- Fixed error in configuration step on some rolling release Linux distributions (PR301)
|
||||
|
@ -78,12 +124,21 @@ This project does **not** adhere to [Semantic Versioning](https://semver.org/spe
|
|||
- Updated TBB dependency from version 2019_U8-1 to 2021.5.0 (PR412)
|
||||
- Fixed NVTT compilation on Visual Studio 2022 (PR374)
|
||||
- Disabled libOVR on MSVC 2022 (PR430)
|
||||
- Added Qt 5.15.9 package for aarch64 Ubuntu 20.04 (PR409)
|
||||
- Fixed build error on aarch64 (PR409)
|
||||
- Replaced QT Script with V8/libnode (PR185,PR409,PR443)
|
||||
- Replaced QT Script with V8/libnode (PR185,PR409,PR443,PR535,PR566)
|
||||
- Updated Qt on Windows to 5.15.10 with KDE patches (PR448)
|
||||
- Updated included OpenSSL to 3.0.5 (PR448)
|
||||
- Updated OpenSSL Windwos dependency (PR448)
|
||||
- Changed libnode dependency to be built from source (PR452)
|
||||
- Disabled Crashpad on aarch64 Linux by default (PR526)
|
||||
- Added discord-rpc dependency (PR686)
|
||||
- Fixed building with memory debugging (PR704)
|
||||
- Updated VCPKG on Windows to version 2023.10.19 (PR730)
|
||||
|
||||
### Security
|
||||
- Updated Qt packages to fix CVE-2023-4863 (PR630,PR631)
|
||||
- Updated Qt packages to fix CVE-2023-5217(PR652,PR653)
|
||||
- Limited audio recording location (PR691)
|
||||
|
||||
|
||||
## [2022.12.1] 2022.12.24
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# Created by Leonardo Murillo on 12/16/2015.
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Copyright 2021 Vircadia contributors.
|
||||
# Copyright 2022 Overte e.V.
|
||||
# Copyright 2022-2024 Overte e.V.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -33,8 +33,7 @@ macro(GENERATE_INSTALLERS)
|
|||
set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME})
|
||||
set(CPACK_PACKAGE_VENDOR "Overte")
|
||||
set(CPACK_PACKAGE_VERSION ${BUILD_VERSION})
|
||||
# There is some sort of bug which adds a "-" between the BUILD_VERSION and the RELEASE_NAME.
|
||||
set(CPACK_PACKAGE_FILE_NAME "Overte${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}${RELEASE_NAME}")
|
||||
set(CPACK_PACKAGE_FILE_NAME "Overte${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}")
|
||||
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
|
||||
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
|
||||
set(CPACK_NSIS_COMPRESSOR "LZMA")
|
||||
|
|
|
@ -180,6 +180,8 @@ Also use the path in the `gn gen` commands, below, making sure to escape `\`s as
|
|||
|
||||
Use a VS2019 developer command prompt in the *src* directory.
|
||||
|
||||
If it complains about the debugger being missing from your Windows SDK, modify your Windows "Software Development Kit" in your installed programs (See: https://stackoverflow.com/questions/46237620/how-to-install-debugging-tools-with-visual-studio-2017-installer).
|
||||
|
||||
Create a release build of the WebRTC library:
|
||||
- `gn gen --ide=vs2019 out\Release --filters=//:webrtc "--args=is_debug=false is_clang=false use_custom_libcxx=false libcxx_is_shared=true symbol_level=2 use_lld=false rtc_include_tests=false rtc_build_tools=false rtc_build_examples=false proprietary_codecs=true rtc_use_h264=true enable_libaom=false rtc_enable_protobuf=false rtc_build_ssl=false rtc_ssl_root=\"<path>\""`
|
||||
- `ninja -C out\Release`
|
||||
|
|
|
@ -1207,53 +1207,8 @@ Section "-Core installation"
|
|||
; 2016-02-25 - The following delete blocks are temporary and can be removed once users who had the initial installer have updated
|
||||
; 2019-09-10 - (3 and a half years later) Sure they are buddy. Sure they are.
|
||||
|
||||
; MessageBox MB_OK|MB_ICONEXCLAMATION "installer type is @INSTALLER_TYPE@"
|
||||
|
||||
;Delete any server executables that might have been installed by bad versions of the client-only installer, but ONLY if we are a client-only installer
|
||||
${If} "@INSTALLER_TYPE@" == "client_only"
|
||||
; MessageBox MB_OK|MB_ICONEXCLAMATION "trying to delete server binaries"
|
||||
Delete "$INSTDIR\assignment-client.exe"
|
||||
Delete "$INSTDIR\domain-server.exe"
|
||||
${EndIf}
|
||||
|
||||
;Delete any server-console files installed before it was placed in sub-folder
|
||||
Delete "$INSTDIR\server-console.exe"
|
||||
RMDir /r "$INSTDIR\locales"
|
||||
RMDir /r "$INSTDIR\resources\app"
|
||||
RMDir /r "$INSTDIR\client"
|
||||
Delete "$INSTDIR\resources\atom.asar"
|
||||
Delete "$INSTDIR\build-info.json"
|
||||
Delete "$INSTDIR\content_resources_200_percent.pak"
|
||||
Delete "$INSTDIR\content_shell.pak"
|
||||
Delete "$INSTDIR\LICENSE"
|
||||
Delete "$INSTDIR\LICENSES.chromium.html"
|
||||
Delete "$INSTDIR\natives_blob.bin"
|
||||
Delete "$INSTDIR\node.dll"
|
||||
Delete "$INSTDIR\pdf.dll"
|
||||
Delete "$INSTDIR\snapshot_blob.bin"
|
||||
Delete "$INSTDIR\ui_resources_200_percent.pak"
|
||||
Delete "$INSTDIR\vccorlib120.dll"
|
||||
Delete "$INSTDIR\version"
|
||||
Delete "$INSTDIR\xinput1_3.dll"
|
||||
|
||||
; Delete old desktop shortcuts before they were renamed during Sandbox rename
|
||||
Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk"
|
||||
Delete "$DESKTOP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk"
|
||||
|
||||
; Delete old Start Menu shortcuts before Sandbox rename
|
||||
Delete "$SMPROGRAMS\$STARTMENU_FOLDER\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk"
|
||||
Delete "$SMPROGRAMS\$STARTMENU_FOLDER\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk"
|
||||
|
||||
; Delete old startup item for Server Console before Sandbox rename
|
||||
SetShellVarContext current
|
||||
Delete "$SMSTARTUP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk"
|
||||
SetShellVarContext all
|
||||
|
||||
; Rename the incorrectly cased Raleway font
|
||||
Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml"
|
||||
|
||||
; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console)
|
||||
RMDir /r "$INSTDIR\Interface"
|
||||
; Delete old hifiNeuron.dll, since we dropped support for it and it causes a crash on startup.
|
||||
Delete "$INSTDIR\plugins\hifiNeuron.dll"
|
||||
|
||||
;Use the entire tree produced by the INSTALL target. Keep the
|
||||
;list of directories here in sync with the RMDir commands below.
|
||||
|
|
|
@ -101,7 +101,7 @@ endif()
|
|||
if 'Windows' == system:
|
||||
self.exe = os.path.join(self.path, 'vcpkg.exe')
|
||||
self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.bat'), '-disableMetrics' ]
|
||||
self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-windows_x86_64_2023.10.19.zip'
|
||||
self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-windows_x86_64_2024.06.15.zip'
|
||||
self.vcpkgHash = 'f335234f0722c15376fb10747f558c18c83a3e1e3b6565cf0dabfb18c9625a99234d054457fd05190c0ecd7a59ca43305bc93b50dbf764a4e1f567a15168d051'
|
||||
self.hostTriplet = 'x64-windows'
|
||||
if usePrebuilt:
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// Created by David Rowe on 18 Apr 2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
// Copyright 2020 Vircadia contributors.
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -39,7 +40,7 @@ Rectangle {
|
|||
color: "white"
|
||||
}
|
||||
RalewayRegular {
|
||||
text: "Build " + About.buildVersion + " " + About.releaseName
|
||||
text: "Build " + About.buildVersion
|
||||
size: 16
|
||||
color: "white"
|
||||
}
|
||||
|
|
|
@ -465,7 +465,7 @@ float fetchUVAnimationMaskMap(vec2 uv) {
|
|||
<@func evalMaterialShadingShift(fetchedShadingShift, materialShadingShift, matKey, shadingShift)@>
|
||||
{
|
||||
<$shadingShift$> = mix(0.0, <$materialShadingShift$>, float((<$matKey$> & SHADING_SHIFT_VAL_BIT) != 0));
|
||||
<$shadingShift$> += mix(0.0, <$fetchedShadingShift$>.r, float((<$matKey$> & SHADING_SHIFT_MAP_BIT) != 0));
|
||||
<$shadingShift$> += mix(0.0, <$fetchedShadingShift$>, float((<$matKey$> & SHADING_SHIFT_MAP_BIT) != 0));
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
|
|
|
@ -457,6 +457,11 @@ bool startCrashHandler(std::string appPath, std::string crashURL, std::string cr
|
|||
}
|
||||
|
||||
void setCrashReportingEnabled(bool enabled) {
|
||||
if (!crashpadDatabase) {
|
||||
qCCritical(crash_handler) << "Can't set to enabled, crash handler not initialized!";
|
||||
return;
|
||||
}
|
||||
|
||||
auto settings = crashpadDatabase->GetSettings();
|
||||
settings->SetUploadsEnabled(enabled);
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ const int NUM_FRUSTUM_CORNERS = 8;
|
|||
const int NUM_FRUSTUM_PLANES = 6;
|
||||
|
||||
const float DEFAULT_CENTER_SPHERE_RADIUS = 3.0f;
|
||||
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f;
|
||||
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 55.0f;
|
||||
const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f;
|
||||
const float DEFAULT_NEAR_CLIP = 0.08f;
|
||||
const float DEFAULT_FAR_CLIP = 16384.0f;
|
||||
|
|
|
@ -13,7 +13,8 @@ fi
|
|||
# The regex below extracts the path from the VCPKG_INSTALL_ROOT variable. Said variable gets populated during the CMake step.
|
||||
VCPKG_INSTALL_ROOT=`grep VCPKG_INSTALL_ROOT $OVERTE/build/vcpkg.cmake | perl -ne 'm/set\(VCPKG_INSTALL_ROOT\s+\"(.*?)\"/; print $1'`
|
||||
|
||||
VERSION=${RPMVERSION}
|
||||
# Remove minus character from version numbers, because rpmtool doesn't allow them.
|
||||
VERSION=${RPMVERSION//-}
|
||||
|
||||
if [ "$OVERTE_USE_SYSTEM_QT" = "" ]; then
|
||||
SOFILES=`ls \
|
||||
|
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
@ -4,7 +4,7 @@
|
|||
//
|
||||
// By Don Hopkins (dhopkins@donhopkins.com) on May 5th, 2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
// Copyright 2023 Overte e.V.
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
(function() {
|
||||
|
||||
var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page.
|
||||
var webPageURL = Script.resolvePath("ChatPage.html"); // URL of tablet web page.
|
||||
var randomizeWebPageURL = true; // Set to true for debugging.
|
||||
var lastWebPageURL = ""; // Last random URL of tablet web page.
|
||||
var onChatPage = false; // True when chat web page is opened.
|
205
scripts/communityScripts/armored-chat/README.md
Normal file
|
@ -0,0 +1,205 @@
|
|||
# Armored Chat
|
||||
|
||||
1. What is Armored Chat
|
||||
2. User manual
|
||||
- Installation
|
||||
- Settings
|
||||
- Usability tips
|
||||
3. Development
|
||||
|
||||
## What is Armored Chat
|
||||
|
||||
Armored Chat is a chat application strictly made to communicate between players in the same domain. It is made using QML and to be as light weight as reasonably possible.
|
||||
|
||||
### Dependencies
|
||||
|
||||
AC uses the Overte [Messages](https://apidocs.overte.org/Messages.html) API to communicate.
|
||||
|
||||
For notifications, AC uses [notificationCore.js](https://github.com/overte-org/overte/blob/bb8bac43eadd3b20956a2ff7b0b21c28844b0f77/scripts/communityScripts/notificationCore/notificationCore.js).
|
||||
|
||||
## User manual
|
||||
|
||||
### Installation
|
||||
|
||||
Armored Chat is preinstalled courtesy of [defaultScripts.js](https://github.com/overte-org/overte/blob/8661e8a858663b48e8485c2cd7120dc3e2d7b87e/scripts/defaultScripts.js).
|
||||
|
||||
If AC is not preinstalled, or for some other reason it can not be automatically installed, you can install it manually by following [these instructions](https://github.com/overte-org/overte/blob/8661e8a858663b48e8485c2cd7120dc3e2d7b87e/scripts/defaultScripts.js) to open your script management application, and loading the script url:
|
||||
|
||||
```
|
||||
https://raw.githubusercontent.com/overte-org/overte/master/scripts/communityScripts/armored-chat/armored_chat.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Settings
|
||||
|
||||
Armored Chat comes with basic settings for managing itself.
|
||||
|
||||
#### External window
|
||||
|
||||
This boolean setting toggles whether AC will be a in-game overlay window, or whether AC will be a external floating window.
|
||||
|
||||
Default is `false`.
|
||||
|
||||
#### Maximum saved messages
|
||||
|
||||
This integer represents the amount of messages to save in the AC history. More messages may be present if AC is left on long enough. This setting only sets the number of saved messages and not the maximum amount of messages that can be viewed at any time.
|
||||
|
||||
This means if you set the value to `5`, your history will save a maximum of 5 messages, however you will still be able to see a longer history in the session should you receive more. Once AC completely closes and fetches your message history as it initializes, you will only see the last 5 messages.
|
||||
|
||||
Default value is `200`
|
||||
|
||||
#### Erase chat history
|
||||
|
||||
This action immediately clears the AC history and the session. Functionally this will set the message list to a empty Array.
|
||||
|
||||
### Usage
|
||||
|
||||
AC has two chat modes: Local, and Domain. Local chat displays all other local chat messages that are within 20 units of you. Domain chat will display all other Domain messages sent though that channel regardless of distance.
|
||||
|
||||
AC also handles link embedding. When you send an HTTP(S) link, it will automatically parse it using Qt RichText and allow everyone to click on the message. Next to the link you will also see a "⮺" symbol. Clicking on this symbol will open the link in an external window.
|
||||
|
||||
### Usability tips
|
||||
|
||||
#### Navigation
|
||||
|
||||
You can scroll quickly using kinetic scrolling! Try "grabbing" the right side of messages, where the timestamp is, and flinging yourself in a direction.
|
||||
|
||||
#### Formatting
|
||||
|
||||
You can format messages using basic HTML elements. Try `<div style="color: red"> Red text! </div>` to color your text red.
|
||||
Find the full list of Qt rich text tags [here](https://doc.qt.io/qt-6/richtext-html-subset.html). Please note that some of these tags may be intentionally restricted.
|
||||
|
||||
#### Media embedding
|
||||
|
||||
Images can be embedded when linked directly.
|
||||
|
||||
Try it out by linking to the Overte logo! `https://github.com/overte-org/overte/raw/master/interface/resources/images/brand-banner.svg`
|
||||
|
||||
In order for images to be embedded, URLs must end in a image filetype.
|
||||
Supported filetypes are:
|
||||
|
||||
- `.png`
|
||||
- `.jpg`
|
||||
- `.jpeg`
|
||||
- `.gif`
|
||||
- `.bmp`
|
||||
- `.svg`
|
||||
- `.webp`
|
||||
|
||||
## Development
|
||||
|
||||
### To QML communication
|
||||
|
||||
Here are the signals needed to communicate from the JavaScript core to the QML interface.
|
||||
|
||||
AC calls a `_emitEvent()` function that also includes a `type` key in the object. This `type` tells the QML and/or the JS core what the packet is for.
|
||||
When you call the `_emitEvent()` function be sure to include the following signals as a `type`. In the examples below, the `type` is being excluded for brevity.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{ type: "show_message", displayName: "username", ...}
|
||||
```
|
||||
|
||||
#### "show_message"
|
||||
|
||||
This signal tells the QML to add a new message to the ListView element list.
|
||||
|
||||
Supply a `JSON` object.
|
||||
|
||||
```json
|
||||
{
|
||||
"displayName": "username",
|
||||
"message": "chat message",
|
||||
"channel": "domain", // Channel to send message on. By default it should only be "domain" or "local".
|
||||
"date": "[ time and date string ]" // Optional, defaults to current time and date.
|
||||
}
|
||||
```
|
||||
|
||||
#### "clear_messages"
|
||||
|
||||
Clear all messages displayed in the ListView elements. Note this does not clear the history and this is only a visual erasure.
|
||||
|
||||
No payload required.
|
||||
|
||||
#### "notification"
|
||||
|
||||
Renders a notification to the domain channel.
|
||||
The intended use is to provide updates about the domain and make the notifications accessible.
|
||||
|
||||
Supply a `JSON` object.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "notification message" // Notification to render
|
||||
}
|
||||
```
|
||||
|
||||
#### "initial_settings"
|
||||
|
||||
Visually set the settings in the QML interface based on the supplied object.
|
||||
|
||||
Supply a `JSON` object.
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": {
|
||||
// JSON object of current AC settings
|
||||
"external_window": false,
|
||||
"maximum_messages": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### To JS communication
|
||||
|
||||
Here are the signals needed to communicate from the QML interface to the JavaScript core. AC is developed in a way that all actions that are not style related are preformed though the JavaScript core.
|
||||
This means that what ever action you want to preform must go though the JavaScript core for processing.
|
||||
|
||||
This is formatted the same was as the communication packets to the QML interface. Supply the following entries as "type"s in your packet.
|
||||
|
||||
#### "send_message"
|
||||
|
||||
Tell AC to broadcast a message to the domain.
|
||||
|
||||
Supply a `JSON` object.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "message content", // The contents of the message to send.
|
||||
"channel": "domain" // Channel to emit the message to.
|
||||
}
|
||||
```
|
||||
|
||||
#### "setting_change"
|
||||
|
||||
Tell AC to change a setting. Exercise caution when using this as you can add new settings unintentionally if you are not careful.
|
||||
|
||||
Supply a `JSON` object
|
||||
|
||||
```json
|
||||
{
|
||||
"setting": "external_window", // The name of the setting to change
|
||||
"value": true // The value to change the setting to
|
||||
}
|
||||
```
|
||||
|
||||
#### "action"
|
||||
|
||||
Tell AC to preform a generic action. This is normally reserved for functions that would get called on a button onClicked event in the QML.
|
||||
|
||||
Supply a `JSON` object
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "erase_history" // The action to preform
|
||||
}
|
||||
```
|
||||
|
||||
#### "initialized"
|
||||
|
||||
Tell AC the QML overlay has loaded successfully.
|
||||
This is called to hide the overlay on creation.
|
||||
|
||||
No payload required.
|
291
scripts/communityScripts/armored-chat/armored_chat.js
Normal file
|
@ -0,0 +1,291 @@
|
|||
//
|
||||
// armored_chat.js
|
||||
//
|
||||
// Created by Armored Dragon, 2024.
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
(() => {
|
||||
("use strict");
|
||||
|
||||
var appIsVisible = false;
|
||||
var settings = {
|
||||
external_window: false,
|
||||
maximum_messages: 200,
|
||||
};
|
||||
|
||||
// Global vars
|
||||
var tablet;
|
||||
var chatOverlayWindow;
|
||||
var appButton;
|
||||
var quickMessage;
|
||||
const channels = ["domain", "local"];
|
||||
var messageHistory = Settings.getValue("ArmoredChat-Messages", []) || [];
|
||||
var maxLocalDistance = 20; // Maximum range for the local chat
|
||||
var palData = AvatarManager.getPalData().data;
|
||||
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Messages.subscribe("Chat"); // Floofchat
|
||||
Messages.subscribe("chat");
|
||||
Messages.messageReceived.connect(receivedMessage);
|
||||
AvatarManager.avatarAddedEvent.connect((sessionId) => {
|
||||
_avatarAction("connected", sessionId);
|
||||
});
|
||||
AvatarManager.avatarRemovedEvent.connect((sessionId) => {
|
||||
_avatarAction("left", sessionId);
|
||||
});
|
||||
|
||||
startup();
|
||||
|
||||
function startup() {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
appButton = tablet.addButton({
|
||||
icon: Script.resolvePath("./img/icon_white.png"),
|
||||
activeIcon: Script.resolvePath("./img/icon_black.png"),
|
||||
text: "CHAT",
|
||||
isActive: appIsVisible,
|
||||
});
|
||||
|
||||
// When script ends, remove itself from tablet
|
||||
Script.scriptEnding.connect(function () {
|
||||
console.log("Shutting Down");
|
||||
tablet.removeButton(appButton);
|
||||
chatOverlayWindow.close();
|
||||
});
|
||||
|
||||
// Overlay button toggle
|
||||
appButton.clicked.connect(toggleMainChatWindow);
|
||||
|
||||
quickMessage = new OverlayWindow({
|
||||
source: Script.resolvePath("./armored_chat_quick_message.qml"),
|
||||
});
|
||||
|
||||
_openWindow();
|
||||
}
|
||||
function toggleMainChatWindow() {
|
||||
appIsVisible = !appIsVisible;
|
||||
appButton.editProperties({ isActive: appIsVisible });
|
||||
chatOverlayWindow.visible = appIsVisible;
|
||||
|
||||
// External window was closed; the window does not exist anymore
|
||||
if (chatOverlayWindow.title == "" && appIsVisible) {
|
||||
_openWindow();
|
||||
}
|
||||
}
|
||||
function _openWindow() {
|
||||
chatOverlayWindow = new Desktop.createWindow(
|
||||
Script.resolvePath("./armored_chat.qml"),
|
||||
{
|
||||
title: "Chat",
|
||||
size: { x: 550, y: 400 },
|
||||
additionalFlags: Desktop.ALWAYS_ON_TOP,
|
||||
visible: appIsVisible,
|
||||
presentationMode: Desktop.PresentationMode.VIRTUAL,
|
||||
}
|
||||
);
|
||||
|
||||
chatOverlayWindow.closed.connect(toggleMainChatWindow);
|
||||
chatOverlayWindow.fromQml.connect(fromQML);
|
||||
quickMessage.fromQml.connect(fromQML);
|
||||
}
|
||||
function receivedMessage(channel, message) {
|
||||
// Is the message a chat message?
|
||||
channel = channel.toLowerCase();
|
||||
if (channel !== "chat") return;
|
||||
message = JSON.parse(message);
|
||||
|
||||
if (!message.channel) message.channel = "domain"; // We don't know where to put this message. Assume it is a domain wide message.
|
||||
if (message.forApp) return; // Floofchat
|
||||
|
||||
// Floofchat compatibility hook
|
||||
message = floofChatCompatibilityConversion(message);
|
||||
message.channel = message.channel.toLowerCase(); // Make sure the "local", "domain", etc. is formatted consistently
|
||||
|
||||
if (!channels.includes(message.channel)) return; // Check the channel
|
||||
if (
|
||||
message.channel == "local" &&
|
||||
Vec3.distance(MyAvatar.position, message.position) >
|
||||
maxLocalDistance
|
||||
)
|
||||
return; // If message is local, and if player is too far away from location, don't do anything
|
||||
|
||||
// Update qml view of to new message
|
||||
_emitEvent({ type: "show_message", ...message });
|
||||
|
||||
Messages.sendLocalMessage(
|
||||
"Floof-Notif",
|
||||
JSON.stringify({
|
||||
sender: message.displayName,
|
||||
text: message.message,
|
||||
})
|
||||
);
|
||||
|
||||
// Save message to history
|
||||
let savedMessage = message;
|
||||
delete savedMessage.position;
|
||||
savedMessage.timeString = new Date().toLocaleTimeString(undefined, {
|
||||
hour12: false,
|
||||
});
|
||||
savedMessage.dateString = new Date().toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
messageHistory.push(savedMessage);
|
||||
while (messageHistory.length > settings.maximum_messages) {
|
||||
messageHistory.shift();
|
||||
}
|
||||
Settings.setValue("ArmoredChat-Messages", messageHistory);
|
||||
}
|
||||
function fromQML(event) {
|
||||
switch (event.type) {
|
||||
case "send_message":
|
||||
_sendMessage(event.message, event.channel);
|
||||
break;
|
||||
case "setting_change":
|
||||
settings[event.setting] = event.value; // Update local settings
|
||||
_saveSettings(); // Save local settings
|
||||
|
||||
switch (event.setting) {
|
||||
case "external_window":
|
||||
chatOverlayWindow.presentationMode = event.value
|
||||
? Desktop.PresentationMode.NATIVE
|
||||
: Desktop.PresentationMode.VIRTUAL;
|
||||
break;
|
||||
case "maximum_messages":
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case "action":
|
||||
switch (event.action) {
|
||||
case "erase_history":
|
||||
Settings.setValue("ArmoredChat-Messages", []);
|
||||
_emitEvent({
|
||||
type: "clear_messages",
|
||||
});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "initialized":
|
||||
// https://github.com/overte-org/overte/issues/824
|
||||
chatOverlayWindow.visible = appIsVisible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false)
|
||||
_loadSettings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
function keyPressEvent(event) {
|
||||
switch (JSON.stringify(event.key)) {
|
||||
case "16777220": // Enter key
|
||||
if (HMD.active) return; // Don't allow in VR
|
||||
|
||||
quickMessage.sendToQml({
|
||||
type: "change_visibility",
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
function _sendMessage(message, channel) {
|
||||
if (message.length == 0) return;
|
||||
|
||||
Messages.sendMessage(
|
||||
"chat",
|
||||
JSON.stringify({
|
||||
position: MyAvatar.position,
|
||||
message: message,
|
||||
displayName: MyAvatar.sessionDisplayName,
|
||||
channel: channel,
|
||||
action: "send_chat_message",
|
||||
})
|
||||
);
|
||||
|
||||
floofChatCompatibilitySendMessage(message, channel);
|
||||
}
|
||||
function _avatarAction(type, sessionId) {
|
||||
Script.setTimeout(() => {
|
||||
if (type == "connected") {
|
||||
palData = AvatarManager.getPalData().data;
|
||||
}
|
||||
|
||||
// Get the display name of the user
|
||||
let displayName = "";
|
||||
displayName =
|
||||
AvatarManager.getPalData([sessionId])?.data[0]
|
||||
?.sessionDisplayName || null;
|
||||
if (displayName == null) {
|
||||
for (let i = 0; i < palData.length; i++) {
|
||||
if (palData[i].sessionUUID == sessionId) {
|
||||
displayName = palData[i].sessionDisplayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format the packet
|
||||
let message = {};
|
||||
message.message = `${displayName} ${type}`;
|
||||
|
||||
_emitEvent({ type: "notification", ...message });
|
||||
}, 1500);
|
||||
}
|
||||
function _loadSettings() {
|
||||
settings = Settings.getValue("ArmoredChat-Config", settings);
|
||||
|
||||
if (messageHistory) {
|
||||
// Load message history
|
||||
messageHistory.forEach((message) => {
|
||||
delete message.action;
|
||||
_emitEvent({ type: "show_message", ...message });
|
||||
});
|
||||
}
|
||||
|
||||
// Send current settings to the app
|
||||
_emitEvent({ type: "initial_settings", settings: settings });
|
||||
}
|
||||
function _saveSettings() {
|
||||
console.log("Saving config");
|
||||
Settings.setValue("ArmoredChat-Config", settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a packet to the HTML front end. Easy communication!
|
||||
* @param {Object} packet - The Object packet to emit to the HTML
|
||||
* @param {("show_message"|"clear_messages"|"notification"|"initial_settings")} packet.type - The type of packet it is
|
||||
*/
|
||||
function _emitEvent(packet = { type: "" }) {
|
||||
chatOverlayWindow.sendToQml(packet);
|
||||
}
|
||||
|
||||
//
|
||||
// Floofchat compatibility functions
|
||||
// Added to ease the transition between Floofchat to ArmoredChat
|
||||
// These functions can be safely removed at a much later date.
|
||||
function floofChatCompatibilityConversion(message) {
|
||||
if (message.type === "TransmitChatMessage" && !message.forApp) {
|
||||
return {
|
||||
position: message.position,
|
||||
message: message.message,
|
||||
displayName: message.displayName,
|
||||
channel: message.channel.toLowerCase(),
|
||||
};
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function floofChatCompatibilitySendMessage(message, channel) {
|
||||
Messages.sendMessage(
|
||||
"Chat",
|
||||
JSON.stringify({
|
||||
position: MyAvatar.position,
|
||||
message: message,
|
||||
displayName: MyAvatar.sessionDisplayName,
|
||||
channel: channel.charAt(0).toUpperCase() + channel.slice(1),
|
||||
type: "TransmitChatMessage",
|
||||
forApp: "Floof",
|
||||
})
|
||||
);
|
||||
}
|
||||
})();
|
566
scripts/communityScripts/armored-chat/armored_chat.qml
Normal file
|
@ -0,0 +1,566 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.0
|
||||
import QtQuick.Layouts 1.3
|
||||
import controlsUit 1.0 as HifiControlsUit
|
||||
|
||||
Rectangle {
|
||||
color: Qt.rgba(0.1,0.1,0.1,1)
|
||||
signal sendToScript(var message);
|
||||
|
||||
property string pageVal: "local"
|
||||
property string last_message_user: ""
|
||||
property date last_message_time: new Date()
|
||||
|
||||
// When the window is created on the script side, the window starts open.
|
||||
// Once the QML window is created wait, then send the initialized signal.
|
||||
// This signal is mostly used to close the "Desktop overlay window" script side
|
||||
// https://github.com/overte-org/overte/issues/824
|
||||
Timer {
|
||||
interval: 10
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
toScript({type: "initialized"});
|
||||
load_scroll_timer.running = true
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: load_scroll_timer
|
||||
interval: 100
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
// User view
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Navigation Bar
|
||||
Rectangle {
|
||||
id: navigation_bar
|
||||
width: parent.width
|
||||
height: 40
|
||||
color:Qt.rgba(0,0,0,1)
|
||||
|
||||
Item {
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
width: pageVal === "local" ? 100 : 60
|
||||
height: parent.height
|
||||
color: pageVal === "local" ? "#505186" : "white"
|
||||
id: local_page
|
||||
|
||||
Image {
|
||||
source: "./img/ui/" + (pageVal === "local" ? "social_white.png" : "social_black.png")
|
||||
sourceSize.width: 40
|
||||
sourceSize.height: 40
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 50
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
pageVal = "local";
|
||||
load_scroll_timer.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
width: pageVal === "domain" ? 100 : 60
|
||||
height: parent.height
|
||||
color: pageVal === "domain" ? "#505186" : "white"
|
||||
anchors.left: local_page.right
|
||||
anchors.leftMargin: 5
|
||||
id: domain_page
|
||||
|
||||
Image {
|
||||
source: "./img/ui/" + (pageVal === "domain" ? "world_white.png" : "world_black.png")
|
||||
sourceSize.width: 30
|
||||
sourceSize.height: 30
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 50
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
pageVal = "domain"
|
||||
load_scroll_timer.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: pageVal === "settings" ? 100 : 60
|
||||
height: parent.height
|
||||
color: pageVal === "settings" ? "#505186" : "white"
|
||||
anchors.right: parent.right
|
||||
id: settings_page
|
||||
|
||||
Image {
|
||||
source: "./img/ui/" + (pageVal === "settings" ? "settings_white.png" : "settings_black.png")
|
||||
sourceSize.width: 30
|
||||
sourceSize.height: 30
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 50
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
pageVal = "settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Pages
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
anchors.top: navigation_bar.bottom
|
||||
visible: ["local", "domain"].includes(pageVal) ? true : false
|
||||
|
||||
|
||||
// Chat Message History
|
||||
ListView {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
clip: true
|
||||
interactive: true
|
||||
spacing: 5
|
||||
id: listview
|
||||
|
||||
delegate: Loader {
|
||||
property int delegateIndex: index
|
||||
property string delegateText: model.text
|
||||
property string delegateUsername: model.username
|
||||
property string delegateDate: model.date
|
||||
width: listview.width
|
||||
|
||||
sourceComponent: {
|
||||
if (model.type === "chat") {
|
||||
return template_chat_message;
|
||||
} else if (model.type === "notification") {
|
||||
return template_notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: chat_scrollbar
|
||||
height: 100
|
||||
size: 0.05
|
||||
}
|
||||
|
||||
model: getChannel(pageVal)
|
||||
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: local
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: domain
|
||||
}
|
||||
|
||||
// Chat Entry
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 40
|
||||
color: Qt.rgba(0.9,0.9,0.9,1)
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
TextField {
|
||||
width: parent.width - 60
|
||||
height: parent.height
|
||||
placeholderText: pageVal.charAt(0).toUpperCase() + pageVal.slice(1) + " chat message..."
|
||||
clip: false
|
||||
Keys.onPressed: {
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
|
||||
event.accepted = true;
|
||||
toScript({type: "send_message", message: text, channel: pageVal});
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
onFocusChanged: {
|
||||
if (!HMD.active) return;
|
||||
if (focus) return ApplicationInterface.showVRKeyboardForHudUI(true);
|
||||
ApplicationInterface.showVRKeyboardForHudUI(false);
|
||||
}
|
||||
}
|
||||
Button {
|
||||
width: 60
|
||||
height:parent.height
|
||||
|
||||
Image {
|
||||
source: "./img/ui/send_black.png"
|
||||
sourceSize.width: 30
|
||||
sourceSize.height: 30
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
toScript({type: "send_message", message: parent.children[0].text, channel: pageVal});
|
||||
parent.children[0].text = ""
|
||||
}
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
toScript({type: "send_message", message: parent.children[0].text, channel: pageVal});
|
||||
parent.children[0].text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
anchors.top: navigation_bar.bottom
|
||||
visible: ["local", "domain"].includes(pageVal) ? false : true
|
||||
|
||||
Column {
|
||||
width: parent.width - 10
|
||||
height: parent.height - 10
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
// External Window
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 40
|
||||
color: "transparent"
|
||||
|
||||
Text{
|
||||
text: "External window"
|
||||
color: "white"
|
||||
font.pointSize: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
CheckBox{
|
||||
id: s_external_window
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onCheckedChanged: {
|
||||
toScript({type: 'setting_change', setting: 'external_window', value: checked})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum saved messages
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 40
|
||||
color: "transparent"
|
||||
|
||||
Text{
|
||||
text: "Maximum saved messages"
|
||||
color: "white"
|
||||
font.pointSize: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
HifiControlsUit.SpinBox {
|
||||
id: s_maximum_messages
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
decimals: 0
|
||||
width: 100
|
||||
height: parent.height
|
||||
realFrom: 1
|
||||
realTo: 1000
|
||||
backgroundColor: "#cccccc"
|
||||
|
||||
onValueChanged: {
|
||||
toScript({type: 'setting_change', setting: 'maximum_messages', value: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Erase History
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 40
|
||||
color: Qt.rgba(0.15,0.15,0.15,1);
|
||||
|
||||
Text{
|
||||
text: "Erase chat history"
|
||||
color: "white"
|
||||
font.pointSize: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.right: parent.right
|
||||
text: "Erase"
|
||||
height: parent.height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onClicked: {
|
||||
toScript({type: "action", action: "erase_history"})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Templates
|
||||
Component {
|
||||
id: template_chat_message
|
||||
|
||||
Rectangle{
|
||||
property int index: delegateIndex
|
||||
property string texttest: delegateText
|
||||
property string username: delegateUsername
|
||||
property string date: delegateDate
|
||||
|
||||
height: Math.max(65, children[1].height + 30)
|
||||
color: index % 2 === 0 ? "transparent" : Qt.rgba(0.15,0.15,0.15,1)
|
||||
|
||||
Item {
|
||||
width: parent.width - 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: 22
|
||||
|
||||
Text{
|
||||
text: username
|
||||
color: "lightgray"
|
||||
}
|
||||
|
||||
Text{
|
||||
anchors.right: parent.right
|
||||
text: date
|
||||
color: "lightgray"
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit{
|
||||
anchors.top: parent.children[0].bottom
|
||||
x: 5
|
||||
text: texttest
|
||||
color:"white"
|
||||
font.pointSize: 12
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
width: parent.width * 0.8
|
||||
height: contentHeight
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: TextEdit.RichText
|
||||
|
||||
onLinkActivated: {
|
||||
Window.openWebBrowser(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: template_notification
|
||||
|
||||
Rectangle{
|
||||
property int index: delegateIndex
|
||||
property string texttest: delegateText
|
||||
property string username: delegateUsername
|
||||
property string date: delegateDate
|
||||
color: "#171717"
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Item {
|
||||
width: 10
|
||||
height: parent.height
|
||||
|
||||
Rectangle {
|
||||
height: parent.height
|
||||
width: 5
|
||||
color: "#505186"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width - parent.children[0].width - 5
|
||||
height: parent.height
|
||||
anchors.left: parent.children[0].right
|
||||
|
||||
TextEdit{
|
||||
text: texttest
|
||||
color:"white"
|
||||
font.pointSize: 12
|
||||
readOnly: true
|
||||
width: parent.width * 0.8
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
height: parent.height
|
||||
wrapMode: Text.Wrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.italic: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: date
|
||||
color:"white"
|
||||
font.pointSize: 12
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.italic: true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
property var channels: {
|
||||
"local": local,
|
||||
"domain": domain,
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
if (listview.count == 0) return;
|
||||
listview.positionViewAtEnd();
|
||||
}
|
||||
|
||||
|
||||
function addMessage(username, message, date, channel, type){
|
||||
channel = getChannel(channel)
|
||||
|
||||
// Format content
|
||||
message = formatContent(message);
|
||||
|
||||
message = embedImages(message);
|
||||
|
||||
if (type === "notification"){
|
||||
channel.append({ text: message, date: date, type: "notification" });
|
||||
last_message_user = "";
|
||||
scrollToBottom();
|
||||
last_message_time = new Date();
|
||||
return;
|
||||
}
|
||||
|
||||
var current_time = new Date();
|
||||
var elapsed_time = current_time - last_message_time;
|
||||
var elapsed_minutes = elapsed_time / (1000 * 60);
|
||||
|
||||
var last_item_index = channel.count - 1;
|
||||
var last_item = channel.get(last_item_index);
|
||||
|
||||
if (last_message_user === username && elapsed_minutes < 1 && last_item){
|
||||
message = "<br>" + message
|
||||
last_item.text = last_item.text += "\n" + message;
|
||||
scrollToBottom()
|
||||
last_message_time = new Date();
|
||||
return;
|
||||
}
|
||||
|
||||
last_message_user = username;
|
||||
last_message_time = new Date();
|
||||
channel.append({ text: message, username: username, date: date, type: type });
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function getChannel(id) {
|
||||
return channels[id];
|
||||
}
|
||||
|
||||
function formatContent(mess) {
|
||||
var arrow = /\</gi
|
||||
mess = mess.replace(arrow, "<");
|
||||
|
||||
var link = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
|
||||
mess = mess.replace(link, (match) => {return "<a onclick='Window.openUrl("+match+")' href='" + match + "'>" + match + "</a> <a onclick='Window.openUrl("+match+")'>⮺</a>"});
|
||||
|
||||
var newline = /\n/gi;
|
||||
mess = mess.replace(newline, "<br>");
|
||||
return mess
|
||||
}
|
||||
|
||||
function embedImages(mess){
|
||||
var image_link = /(https?:(\/){2})[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)(?:png|jpe?g|gif|bmp|svg|webp)/g;
|
||||
var matches = mess.match(image_link);
|
||||
var new_message = ""
|
||||
var listed = []
|
||||
var total_emeds = 0
|
||||
|
||||
new_message += mess
|
||||
|
||||
for (var i = 0; matches && matches.length > i && total_emeds < 3; i++){
|
||||
if (!listed.includes(matches[i])) {
|
||||
new_message += "<br><img src="+ matches[i] +" width='250' >"
|
||||
listed.push(matches[i]);
|
||||
total_emeds++
|
||||
}
|
||||
}
|
||||
return new_message;
|
||||
}
|
||||
|
||||
// Messages from script
|
||||
function fromScript(message) {
|
||||
let time = new Date().toLocaleTimeString(undefined, { hour12: false });
|
||||
let date = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric", });
|
||||
|
||||
switch (message.type){
|
||||
case "show_message":
|
||||
addMessage(message.displayName, message.message, `[ ${message.timeString || time} - ${message.dateString || date} ]`, message.channel, "chat");
|
||||
break;
|
||||
case "notification":
|
||||
addMessage("SYSTEM", message.message, `[ ${time} - ${date} ]`, "domain", "notification");
|
||||
break;
|
||||
case "clear_messages":
|
||||
local.clear();
|
||||
domain.clear();
|
||||
break;
|
||||
case "initial_settings":
|
||||
if (message.settings.external_window) s_external_window.checked = true;
|
||||
if (message.settings.maximum_messages) s_maximum_messages.value = message.settings.maximum_messages;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send message to script
|
||||
function toScript(packet){
|
||||
sendToScript(packet)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property var window
|
||||
|
||||
Binding { target: root; property:'window'; value: parent.parent; when: Boolean(parent.parent) }
|
||||
Binding { target: window; property: 'shown'; value: false; when: Boolean(window) }
|
||||
Component.onDestruction: chat_bar && chat_bar.destroy()
|
||||
|
||||
property alias chat_bar: chat_bar
|
||||
|
||||
Rectangle {
|
||||
id: chat_bar
|
||||
parent: desktop
|
||||
x: 0
|
||||
y: parent.height - height
|
||||
width: parent.width
|
||||
height: 50
|
||||
z: 99
|
||||
visible: false
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
x: 0
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
text:""
|
||||
textColor: "#ffffff"
|
||||
clip: false
|
||||
font.pointSize: 18
|
||||
|
||||
Keys.onReturnPressed: { _onEnterPressed(); }
|
||||
Keys.onEnterPressed: { _onEnterPressed(); }
|
||||
Keys.onLeftPressed: { moveLeft(); }
|
||||
Keys.onRightPressed: { moveRight(); }
|
||||
|
||||
function moveLeft(){
|
||||
if (cursorPosition > 0){
|
||||
cursorPosition--
|
||||
}
|
||||
}
|
||||
function moveRight(){
|
||||
if (cursorPosition < text.length){
|
||||
cursorPosition++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Local message..."
|
||||
font.pointSize: 16
|
||||
color: "gray"
|
||||
x: 0
|
||||
width: parent.width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: textArea.text == ""
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
x: parent.width - width
|
||||
y: 0
|
||||
width: 64
|
||||
height: parent.height
|
||||
clip: false
|
||||
visible: true
|
||||
|
||||
Image {
|
||||
id: image
|
||||
width: 30
|
||||
height: 30
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: true
|
||||
anchors.centerIn: parent
|
||||
source: "./img/ui/send_white.png"
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
_onEnterPressed();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function _onEnterPressed() {
|
||||
changeVisibility(false)
|
||||
toScript({type: "send_message", message: textArea.text, channel: "local"})
|
||||
textArea.text = "";
|
||||
}
|
||||
|
||||
function changeVisibility(state){
|
||||
chat_bar.visible = state
|
||||
if (state) textArea.forceActiveFocus();
|
||||
else root.parent.forceActiveFocus();
|
||||
}
|
||||
|
||||
// Messages from script
|
||||
function fromScript(message) {
|
||||
switch (message.type){
|
||||
case "change_visibility":
|
||||
changeVisibility(message.value)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send message to script
|
||||
function toScript(packet){
|
||||
sendToScript(packet)
|
||||
}
|
||||
}
|
BIN
scripts/communityScripts/armored-chat/img/icon_black.png
Normal file
After Width: | Height: | Size: 400 B |
BIN
scripts/communityScripts/armored-chat/img/icon_white.png
Normal file
After Width: | Height: | Size: 778 B |
42
scripts/communityScripts/armored-chat/img/ui/send.svg
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="32"
|
||||
viewBox="0 -960 760 640"
|
||||
width="38"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="send.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
inkscape:export-filename="send_black.png"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="21.395833"
|
||||
inkscape:cx="17.363194"
|
||||
inkscape:cy="16.031159"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1366"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="m 0,-320 v -640 l 760,320 z M 60,-413 604,-640 60,-870 v 168 l 242,62 -242,60 z m 0,0 v -457 z"
|
||||
id="path2"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/send_black.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/send_white.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/settings_black.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/settings_white.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/social_black.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/social_white.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/world_black.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
scripts/communityScripts/armored-chat/img/ui/world_white.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
// Copyright 2020 Vircadia contributors.
|
||||
// Copyright 2022 Overte e.V.
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -47,7 +47,7 @@ var DEFAULT_SCRIPTS_SEPARATE = [
|
|||
"communityScripts/notificationCore/notificationCore.js",
|
||||
"simplifiedUI/ui/simplifiedNametag/simplifiedNametag.js",
|
||||
{"stable": "system/more/app-more.js", "beta": "https://more.overte.org/more/app-more.js"},
|
||||
"communityScripts/chat/FloofChat.js",
|
||||
"communityScripts/armored-chat/armored_chat.js",
|
||||
//"system/chat.js"
|
||||
];
|
||||
|
||||
|
|
|
@ -1,204 +1,95 @@
|
|||
/*
|
||||
mouseLook.js – mouse look switching script
|
||||
by rampa3 (https://github.com/rampa3) and vegaslon (https://github.com/vegaslon)
|
||||
*/
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
//
|
||||
// mouseLook.js
|
||||
//
|
||||
// By Armored Dragon (June 6). Refactored from Rampa3 & Vegaslon work
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var away;
|
||||
(() => {
|
||||
// States ----
|
||||
let mouseLookActive = Settings.getValue("mouselook-active", false);
|
||||
let mouseLookEnabled = Camera.getMouseLook();
|
||||
let hmdActive = HMD.active;
|
||||
let overlayActive = Desktop.isOverlayWindowFocused();
|
||||
|
||||
var hmd = HMD.active;
|
||||
|
||||
var mouseLookEnabled = Camera.getMouseLook();
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
var tabletUp;
|
||||
|
||||
var keysOnOverlay = Desktop.isOverlayWindowFocused();
|
||||
|
||||
var tempOff = false;
|
||||
|
||||
var altMode = false;
|
||||
// Resources ----
|
||||
let tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
// Events ----
|
||||
Camera.mouseLookChanged.connect(onMouseLookChanged);
|
||||
Controller.keyPressEvent.connect(onKeyPressEvent);
|
||||
Desktop.uiFocusChanged.connect(onUiFocusChanged);
|
||||
HMD.displayModeChanged.connect(onDisplayModeChanged);
|
||||
MyAvatar.wentActive.connect(onWentActive);
|
||||
MyAvatar.wentAway.connect(onWentAway);
|
||||
tablet.tabletShownChanged.connect(onTabletShownChanged);
|
||||
Script.scriptEnding.connect(onScriptEnding);
|
||||
|
||||
// Program ----
|
||||
function onMouseLookChanged(newMouseLook) {
|
||||
mouseLookEnabled = newMouseLook;
|
||||
}
|
||||
|
||||
if (!hmd){
|
||||
if (mouseLookEnabled) {
|
||||
if (!keysOnOverlay) {
|
||||
if (!tablet.tabletShown){
|
||||
Window.displayAnnouncement("Mouse look: ON");
|
||||
mouseLookOn();
|
||||
} else {
|
||||
Window.displayAnnouncement("Tablet is up – mouse look temporarily OFF.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controller.keyPressEvent.connect(onKeyPressEvent);
|
||||
|
||||
function onKeyPressEvent(event) {
|
||||
if (!hmd){
|
||||
if(event.isAlt){
|
||||
if (keysOnOverlay) return;
|
||||
if (!mouseLookEnabled) return;
|
||||
mouseLookOff();
|
||||
Window.displayAnnouncement("Mouse look: Temporarily OFF");
|
||||
tempOff = true;
|
||||
altMode = true;
|
||||
}
|
||||
if (tempOff && altMode && ['left', 'right', 'up', 'down', 'esc', 'w', 'a', 's', 'd'].includes(event.text.toLowerCase())){
|
||||
if (keysOnOverlay) return;
|
||||
if (!mouseLookEnabled) return;
|
||||
mouseLookOn();
|
||||
tempOff = false;
|
||||
altMode = false
|
||||
}
|
||||
if (event.text.toLowerCase() === 'm') {
|
||||
if (!keysOnOverlay) {
|
||||
if (mouseLookEnabled) {
|
||||
if (!Camera.getCaptureMouse()){
|
||||
tempOff = false;
|
||||
Window.displayAnnouncement("Mouse look: ON");
|
||||
mouseLookOn();
|
||||
} else {
|
||||
tempOff = true;
|
||||
Window.displayAnnouncement("Mouse look: Temporarily OFF");
|
||||
mouseLookOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Toggle using the m key
|
||||
if (event.text.toLowerCase() === "m") {
|
||||
if (Camera.captureMouse) {
|
||||
mouseLookActive = false;
|
||||
Settings.setValue("mouselook-active", false);
|
||||
disableMouseLook();
|
||||
} else {
|
||||
mouseLookActive = true;
|
||||
Settings.setValue("mouselook-active", true);
|
||||
enableMouseLook();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tablet.tabletShownChanged.connect(onTabletShownChanged);
|
||||
|
||||
function onTabletShownChanged() {
|
||||
if (!hmd) {
|
||||
if (mouseLookEnabled) {
|
||||
if (!tablet.toolbarMode) {
|
||||
if (!keysOnOverlay) {
|
||||
if (tablet.tabletShown) {
|
||||
tabletUp = true;
|
||||
if (!tempOff) {
|
||||
if (!away) {
|
||||
Window.displayAnnouncement("Tablet is up – mouse look temporarily OFF.");
|
||||
mouseLookOff();
|
||||
}
|
||||
}
|
||||
} else if (!tablet.tabletShown) {
|
||||
tabletUp = false;
|
||||
if (!tempOff) {
|
||||
if (!away && !keysOnOverlay) {
|
||||
Window.displayAnnouncement("Tablet hidden – mouse look ON.");
|
||||
mouseLookOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tablet.tabletShown) disableMouseLook();
|
||||
else enableMouseLook();
|
||||
}
|
||||
|
||||
MyAvatar.wentAway.connect(onWentAway);
|
||||
|
||||
function onWentAway() {
|
||||
if (!hmd) {
|
||||
if (mouseLookEnabled) {
|
||||
away = true;
|
||||
if (!keysOnOverlay) {
|
||||
if (!tabletUp){
|
||||
Window.displayAnnouncement("Away state ON – mouse look temporarily OFF.")
|
||||
tempOff = false;
|
||||
mouseLookOff()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
disableMouseLook();
|
||||
}
|
||||
|
||||
MyAvatar.wentActive.connect(onWentActive);
|
||||
|
||||
function onWentActive() {
|
||||
if (!hmd) {
|
||||
if (mouseLookEnabled) {
|
||||
away = false;
|
||||
if (!keysOnOverlay) {
|
||||
if (!tabletUp) {
|
||||
Window.displayAnnouncement("Away state OFF – mouse look ON.");
|
||||
mouseLookOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
enableMouseLook();
|
||||
}
|
||||
|
||||
HMD.displayModeChanged.connect(onDisplayModeChanged);
|
||||
|
||||
function onDisplayModeChanged() {
|
||||
if (mouseLookEnabled) {
|
||||
if (HMD.active) {
|
||||
hmd = true;
|
||||
mouseLookOff();
|
||||
} else {
|
||||
hmd = false;
|
||||
if (!tempOff) {
|
||||
if (!keysOnOverlay) {
|
||||
if (!tabletUp) {
|
||||
mouseLookOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hmdActive = HMD.active;
|
||||
if (hmdActive) disableMouseLook();
|
||||
else enableMouseLook();
|
||||
}
|
||||
|
||||
function onUiFocusChanged(keyFocus) {
|
||||
if (keyFocus) {
|
||||
overlayActive = true;
|
||||
disableMouseLook();
|
||||
} else {
|
||||
overlayActive = false;
|
||||
enableMouseLook();
|
||||
}
|
||||
}
|
||||
|
||||
function mouseLookOn() {
|
||||
if (mouseLookEnabled)
|
||||
Camera.captureMouse = true;
|
||||
function enableMouseLook() {
|
||||
if (hmdActive) return;
|
||||
if (tablet.tabletShown) return;
|
||||
if (overlayActive) return;
|
||||
if (!mouseLookActive) return; // Mouse look disabled via the hotkey
|
||||
|
||||
Camera.captureMouse = true;
|
||||
}
|
||||
|
||||
function mouseLookOff() {
|
||||
function disableMouseLook() {
|
||||
Camera.captureMouse = false;
|
||||
}
|
||||
|
||||
Desktop.uiFocusChanged.connect(onUiFocusChanged);
|
||||
|
||||
function onUiFocusChanged(keyFocus) {
|
||||
if (!hmd) {
|
||||
if (mouseLookEnabled) {
|
||||
if (keyFocus) {
|
||||
keysOnOverlay = true;
|
||||
if (Camera.captureMouse) {
|
||||
mouseLookOff();
|
||||
}
|
||||
} else {
|
||||
keysOnOverlay = false;
|
||||
if (!tablet.tabletShown) {
|
||||
if (!tempOff) {
|
||||
if (!away) {
|
||||
mouseLookOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Messages.messageReceived.connect(onMessageReceived);
|
||||
function onMessageReceived(channel, message, sender, localOnly) {
|
||||
if (channel === "Hifi-Away-Enable")
|
||||
if (message === 'enable') mouseLookOn();
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(onScriptEnding);
|
||||
|
||||
function onScriptEnding() {
|
||||
Camera.captureMouse = false;
|
||||
|
@ -211,5 +102,4 @@ by rampa3 (https://github.com/rampa3) and vegaslon (https://github.com/vegaslon)
|
|||
Desktop.uiFocusChanged.disconnect(onUiFocusChanged);
|
||||
Script.scriptEnding.disconnect(onScriptEnding);
|
||||
}
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
})();
|
||||
|
|
|
@ -253,6 +253,9 @@ function keyPressEvent(event) {
|
|||
if (isEditUsingCamera) {
|
||||
return;
|
||||
}
|
||||
if (Settings.getValue("mouselook-active", false)){
|
||||
return;
|
||||
}
|
||||
alt = true;
|
||||
changed = true;
|
||||
Picks.enablePick(pick);
|
||||
|
|
|
@ -19,7 +19,7 @@ var Y_AXIS = {x: 0, y: 1, z: 0};
|
|||
var X_AXIS = {x: 1, y: 0, z: 0};
|
||||
var DEFAULT_DPI = 31;
|
||||
var DEFAULT_WIDTH = 0.4375;
|
||||
var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees
|
||||
var DEFAULT_VERTICAL_FIELD_OF_VIEW = 55; // degrees
|
||||
var SENSOR_TO_ROOM_MATRIX = -2;
|
||||
var CAMERA_MATRIX = -7;
|
||||
var ROT_Y_180 = {x: 0.0, y: 1.0, z: 0, w: 0};
|
||||
|
|