mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-05-29 14:12:16 +02:00
Merge branch 'master' into tell-accountmanager-url-changed
This commit is contained in:
commit
e2d7e55e0b
16 changed files with 292 additions and 269 deletions
126
BUILD_LINUX.md
126
BUILD_LINUX.md
|
@ -1,75 +1,81 @@
|
||||||
# Build Linux
|
# Build Linux
|
||||||
|
|
||||||
*Last Updated on December 1, 2020*
|
*Last Updated on January 6, 2022*
|
||||||
|
|
||||||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.
|
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.
|
||||||
|
|
||||||
You can use the [Vircadia Builder](https://github.com/vircadia/vircadia-builder) to build on Linux more easily. Alternatively, you can follow the manual steps below.
|
You can use the [Vircadia Builder](https://github.com/vircadia/vircadia-builder) to build on Linux more easily. Alternatively, you can follow the manual steps below.
|
||||||
|
|
||||||
## Ubuntu 16.04/18.04 specific build guide
|
## Ubuntu 18.04
|
||||||
### Ubuntu 16.04 only
|
|
||||||
Add the following line to *.bash_profile*
|
### Ubuntu 18.04 Server only
|
||||||
`export QT_QPA_FONTDIR=/usr/share/fonts/truetype/dejavu/`
|
|
||||||
### Ubuntu 18.04 server only
|
|
||||||
Add the universe repository:
|
Add the universe repository:
|
||||||
_(This is not enabled by default on the server edition)_
|
_(This is not enabled by default on the server edition.)_
|
||||||
```bash
|
```bash
|
||||||
sudo add-apt-repository universe
|
sudo add-apt-repository universe
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
```
|
```
|
||||||
#### Install build tools:
|
|
||||||
|
### Install build tools:
|
||||||
- First update the repositories:
|
- First update the repositories:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get upgrade -y
|
sudo apt-get upgrade -y
|
||||||
```
|
```
|
||||||
- git
|
|
||||||
|
- Install git
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install git -y
|
sudo apt-get install git -y
|
||||||
```
|
```
|
||||||
Verify by git --version
|
Verify git was installed by running `git --version`.
|
||||||
- g++
|
|
||||||
|
- Install g++
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install g++ -y
|
sudo apt-get install g++ -y
|
||||||
```
|
```
|
||||||
Verify by g++ --version
|
Verify g++ was installed by running `g++ --version`.
|
||||||
- *Ubuntu 18.04* cmake
|
|
||||||
|
- **Ubuntu 18.04** CMake
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install cmake -y
|
sudo apt-get install cmake -y
|
||||||
```
|
```
|
||||||
Verify by cmake --version
|
Verify CMake was installed by running `cmake --version`.
|
||||||
- *Ubuntu 16.04* cmake
|
|
||||||
```bash
|
### Install build dependencies:
|
||||||
wget https://cmake.org/files/v3.14/cmake-3.14.2-Linux-x86_64.sh
|
|
||||||
sudo sh cmake-3.14.2-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir
|
|
||||||
```
|
|
||||||
#### Install build dependencies:
|
|
||||||
- OpenSSL:
|
- OpenSSL:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install libssl-dev
|
sudo apt-get install libssl-dev
|
||||||
```
|
```
|
||||||
Verify with `openssl version`
|
Verify OpenSSL was installed by running `openssl version`.
|
||||||
|
|
||||||
- OpenGL:
|
- OpenGL:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install libgl1-mesa-dev -y
|
sudo apt-get install libgl1-mesa-dev -y
|
||||||
sudo ln -s /usr/lib/x86_64-linux-gnu/libGL.so.346.35 /usr/lib/x86_64-linux-gnu/libGL.so.1.2.0
|
|
||||||
```
|
```
|
||||||
- Verify OpenGL:
|
Verify OpenGL:
|
||||||
- First install mesa-utils with the command `sudo apt install mesa-utils -y`
|
- First install mesa-utils with the command `sudo apt install mesa-utils -y`.
|
||||||
- Then run `glxinfo | grep "OpenGL version"`
|
- Then run `glxinfo | grep "OpenGL version"`.
|
||||||
#### To compile interface in a server you must install:
|
|
||||||
|
|
||||||
|
### Extra dependencies to compile Interface on a server
|
||||||
|
|
||||||
|
|
||||||
|
- Install the following:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1
|
sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1
|
||||||
```
|
```
|
||||||
|
|
||||||
- Misc dependencies:
|
- Misc dependencies:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev
|
sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
- Install Python 3 and required packages:
|
- Install Python 3 and required packages:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install python python3 python3-distro
|
sudo apt-get install python python3 python3-distro
|
||||||
```
|
```
|
||||||
- Install node, required to build the jsdoc documentation:
|
|
||||||
|
- Install Node.js as it is required to build the jsdoc documentation:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install nodejs
|
sudo apt-get install nodejs
|
||||||
```
|
```
|
||||||
|
@ -81,16 +87,17 @@ Clone this repository:
|
||||||
git clone https://github.com/vircadia/vircadia.git
|
git clone https://github.com/vircadia/vircadia.git
|
||||||
```
|
```
|
||||||
|
|
||||||
To compile a DEV version checkout the branch you need. To get a list of all tags:
|
Then checkout the master branch with:
|
||||||
```bash
|
|
||||||
git fetch -a
|
|
||||||
```
|
|
||||||
|
|
||||||
Then checkout the main branch with:
|
|
||||||
```bash
|
```bash
|
||||||
git checkout master
|
git checkout master
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you need a different branch, you can get a list of all tags with:
|
||||||
|
```bash
|
||||||
|
git fetch --tags
|
||||||
|
git tag
|
||||||
|
```
|
||||||
|
|
||||||
### Using a custom Qt build
|
### Using a custom Qt build
|
||||||
|
|
||||||
Qt binaries are only provided for Ubuntu. In order to build on other distributions, a Qt5 install
|
Qt binaries are only provided for Ubuntu. In order to build on other distributions, a Qt5 install
|
||||||
|
@ -104,6 +111,18 @@ The system's Qt can be used, if the development packages are installed, by setti
|
||||||
also the last version available in the Qt 5 branch. It is expected that Linux distributions will have
|
also the last version available in the Qt 5 branch. It is expected that Linux distributions will have
|
||||||
Qt 5.15.2 available for a long time.
|
Qt 5.15.2 available for a long time.
|
||||||
|
|
||||||
|
### Architecture support
|
||||||
|
|
||||||
|
If the build is intended to be packaged for distribution, the `VIRCADIA_CPU_ARCHITECTURE`
|
||||||
|
CMake variable needs to be set to an architecture specific value.
|
||||||
|
|
||||||
|
By default, it is set to `-march=native -mtune=native`, which yields builds optimized for a particular
|
||||||
|
machine, but these builds will not work on machines lacking same CPU instructions.
|
||||||
|
|
||||||
|
For packaging, it is recommended to set it to a different value, for example `-msse3`. This will help ensure that the build will run on all reasonably modern CPUs.
|
||||||
|
|
||||||
|
Setting `VIRCADIA_CPU_ARCHITECTURE` to an empty string will use the default compiler settings and yield maximum compatibility.
|
||||||
|
|
||||||
### Compiling
|
### Compiling
|
||||||
|
|
||||||
Create the build directory:
|
Create the build directory:
|
||||||
|
@ -118,33 +137,34 @@ Prepare makefiles:
|
||||||
cmake ..
|
cmake ..
|
||||||
```
|
```
|
||||||
|
|
||||||
- If cmake fails with a vcpkg error - delete /tmp/hifi/vcpkg.
|
If cmake fails with a vcpkg error, then delete `~/vircadia-files/vcpkg/`.
|
||||||
|
|
||||||
Start compilation of the server and get a cup of coffee:
|
#### Server
|
||||||
|
|
||||||
|
To compile the Domain server:
|
||||||
```bash
|
```bash
|
||||||
make domain-server assignment-client
|
make domain-server assignment-client
|
||||||
```
|
```
|
||||||
|
|
||||||
To compile interface:
|
*Note: For a server, it is not necessary to compile the Interface.*
|
||||||
|
|
||||||
|
#### Interface
|
||||||
|
|
||||||
|
To compile the Interface client:
|
||||||
```bash
|
```bash
|
||||||
make interface
|
make interface
|
||||||
```
|
```
|
||||||
|
|
||||||
The commands above will compile with a single thread. If you have enough memory,
|
The commands above will compile with a single thread. If you have enough memory, you can decrease your build time using the `-j` flag. Since most x64 CPUs support two threads per core, this works out to CPU_COUNT*2. As an example, if you have a 2 core machine, you could use:
|
||||||
you can decrease your build time using the `-j` flag. Since most x64 CPUs
|
```bash
|
||||||
support two threads per core, this works out to CPU_COUNT*2. As an example, if
|
|
||||||
you have a 2 core machine, you could use:
|
|
||||||
```
|
|
||||||
make -j4 interface
|
make -j4 interface
|
||||||
```
|
```
|
||||||
|
|
||||||
In a server, it does not make sense to compile interface.
|
|
||||||
|
|
||||||
### Running the software
|
### Running the software
|
||||||
|
|
||||||
#### Domain server
|
#### Domain server
|
||||||
|
|
||||||
Running domain server:
|
Running Domain server:
|
||||||
```bash
|
```bash
|
||||||
./domain-server/domain-server
|
./domain-server/domain-server
|
||||||
```
|
```
|
||||||
|
@ -158,24 +178,16 @@ Running assignment client:
|
||||||
|
|
||||||
#### Interface
|
#### Interface
|
||||||
|
|
||||||
Running interface:
|
Running Interface:
|
||||||
```bash
|
```bash
|
||||||
./interface/interface
|
./interface/interface
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to localhost in the running interface.
|
Go to "localhost" in the running Interface to visit your newly launched Domain server.
|
||||||
|
|
||||||
#### Notes
|
### Notes
|
||||||
|
|
||||||
If your goal is to set up a development environment, it is desirable to set the
|
If your goal is to set up a development environment, it is desirable to set the directory that vcpkg builds into with the `HIFI_VCPKG_BASE` environment variable.
|
||||||
directory that vcpkg builds into with the `HIFI_VCPKG_BASE` environment variable.
|
|
||||||
For example, you might set `HIFI_VCPKG_BASE` to `/home/$USER/vcpkg`.
|
For example, you might set `HIFI_VCPKG_BASE` to `/home/$USER/vcpkg`.
|
||||||
By default, vcpkg will build in the system `/tmp` directory.
|
|
||||||
|
|
||||||
If build is intended for packaging or creation of AppImage, `VIRCADIA_CPU_ARCHITECTURE`
|
By default, vcpkg will build in the `~/vircadia-files/vcpkg/` directory.
|
||||||
CMake variable needs to be set to architecture specific value.
|
|
||||||
It defaults to `-march=native -mtune=native`, which yields builds optimized for particular
|
|
||||||
machine, but builds will not work on machines lacking same CPU instructions.
|
|
||||||
For packaging and AppImage it is recommended to set it to different value, for example `-msse3`.
|
|
||||||
Setting `VIRCADIA_CPU_ARCHITECTURE` to empty string will use default compiler settings and yield
|
|
||||||
maximum compatibility.
|
|
||||||
|
|
25
BUILD_OSX.md
25
BUILD_OSX.md
|
@ -1,6 +1,6 @@
|
||||||
# Build MacOS
|
# Build macOS
|
||||||
|
|
||||||
*Last Updated on October 19, 2021*
|
*Last Updated on December 1, 2021*
|
||||||
|
|
||||||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. This will include the necessary environment variables to customize your build. Only macOS specific instructions are found in this document.
|
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. This will include the necessary environment variables to customize your build. Only macOS specific instructions are found in this document.
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ brew install cmake openssl npm
|
||||||
Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/).
|
Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/).
|
||||||
Execute the `Update Shell Profile.command` script that is provided with the installer.
|
Execute the `Update Shell Profile.command` script that is provided with the installer.
|
||||||
|
|
||||||
### MacOS SDK
|
### macOS SDK
|
||||||
|
|
||||||
You will need version `10.12` of the macOS SDK for building, otherwise you may have crashing or other unintended issues due to the deprecation of OpenGL on macOS. You can get that SDK from [here](https://github.com/phracker/MacOSX-SDKs). You must copy it in to your Xcode SDK directory, e.g.
|
You will need version `10.12` of the macOS SDK for building, otherwise you may experience crashing or other unintended issues due to the deprecation of OpenGL on macOS. You can get that SDK from [here](https://github.com/phracker/MacOSX-SDKs). You must copy it in to your Xcode SDK directory, e.g.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp -rp ~/Downloads/MacOSX10.12.sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
|
cp -rp ~/Downloads/MacOSX10.12.sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
|
||||||
|
@ -64,14 +64,17 @@ You can append `-j4` to assign more threads to build with. The number indicates
|
||||||
|
|
||||||
To package the installation, you can simply run `make package` afterwards.
|
To package the installation, you can simply run `make package` afterwards.
|
||||||
|
|
||||||
## Notes
|
## Architecture Support
|
||||||
|
|
||||||
If build is intended for packaging or creation of AppImage, `VIRCADIA_CPU_ARCHITECTURE`
|
If the build is intended to be packaged for distribution, the `VIRCADIA_CPU_ARCHITECTURE`
|
||||||
CMake variable needs to be set to architecture specific value.
|
CMake variable needs to be set to an architecture specific value.
|
||||||
It defaults to `-march=native -mtune=native`, which yields builds optimized for particular
|
|
||||||
machine, but builds will not work on machines lacking same CPU instructions.
|
By default, it is set to `-march=native -mtune=native`, which yields builds optimized for a particular
|
||||||
For packaging and AppImage it is recommended to set it to different value, for example `-msse3`.
|
machine, but these builds will not work on machines lacking same CPU instructions.
|
||||||
Setting `VIRCADIA_CPU_ARCHITECTURE` to empty string will use default compiler settings and yield
|
|
||||||
|
For packaging, it is recommended to set it to a different value, for example `-msse3`. This will help ensure that the build will run on all reasonably modern CPUs.
|
||||||
|
|
||||||
|
Setting `VIRCADIA_CPU_ARCHITECTURE` to an empty string will use the default compiler settings and yield
|
||||||
maximum compatibility.
|
maximum compatibility.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
11
README.md
11
README.md
|
@ -54,7 +54,6 @@ Vircadia™ is a 3D social software project seeking to incrementally bring about
|
||||||
- [For Windows - Interface & Server](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md)
|
- [For Windows - Interface & Server](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md)
|
||||||
- [For Mac - Interface](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md#os-x)
|
- [For Mac - Interface](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md#os-x)
|
||||||
- [For Linux - Server .deb - Vircadia Builder](INSTALLER.md#ubuntu-1804--deb)
|
- [For Linux - Server .deb - Vircadia Builder](INSTALLER.md#ubuntu-1804--deb)
|
||||||
- [For Linux - Server .rpm - Vircadia Builder](INSTALLER.md#amazon-linux-2--rpm)
|
|
||||||
- [For Linux - Interface AppImage - Vircadia Builder](https://github.com/vircadia/vircadia-builder/blob/master/README.md#building-appimages)
|
- [For Linux - Interface AppImage - Vircadia Builder](https://github.com/vircadia/vircadia-builder/blob/master/README.md#building-appimages)
|
||||||
|
|
||||||
### Boot to Metaverse: [The Goal](https://vircadia.com/vision/)
|
### Boot to Metaverse: [The Goal](https://vircadia.com/vision/)
|
||||||
|
@ -94,10 +93,8 @@ Keep in mind that Vircadia consists of multiple smaller projects that might have
|
||||||
|
|
||||||
#### Supporters of the Vircadia Project
|
#### Supporters of the Vircadia Project
|
||||||
|
|
||||||
| [ksuprynowicz (74hc595)](https://github.com/ksuprynowicz) |
|
One (1) anonymous, three (3) total sponsors through GitHub. ❤️
|
||||||
|
|
||||||
|
| [Daichi Shimabukuro](https://github.com/mshlomd) |
|
||||||
| --- |
|
| --- |
|
||||||
| <p align="center">[<img src="https://vircadia.com/wp-content/uploads/2021/07/74hc595_profile_2-1.png" width="80" alt="ksuprynowicz" />](https://github.com/ksuprynowicz)</p>
|
| <p align="center">[<img src="https://avatars.githubusercontent.com/u/4787162?v=4" width="80" alt="Daichi Shimabukuro" />](https://github.com/mshlomd)</p>
|
||||||
|
|
||||||
#### Sponsors of Open Source
|
|
||||||
|
|
||||||
<img src="https://vircadia.com/wp-content/uploads/2021/11/CBT_OS-logo_White-V.png" width="250" alt="CrossBrowserTesting" />
|
|
||||||
|
|
14
hifi_qt.py
14
hifi_qt.py
|
@ -144,12 +144,16 @@ endif()
|
||||||
if 'x86_64' == cpu_architecture:
|
if 'x86_64' == cpu_architecture:
|
||||||
u_major = int( distro.major_version() )
|
u_major = int( distro.major_version() )
|
||||||
u_minor = int( distro.minor_version() )
|
u_minor = int( distro.minor_version() )
|
||||||
if (distro.id() == 'ubuntu' and u_major == 18) or distro.id() == 'linuxmint' and u_major == 19:
|
if distro.id() == 'ubuntu' or distro.id() == 'linuxmint':
|
||||||
self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.15.2-ubuntu-18.04-amd64.tar.xz'
|
if (distro.id() == 'ubuntu' and u_major == 18) or distro.id() == 'linuxmint' and u_major == 19:
|
||||||
elif (distro.id() == 'ubuntu' and u_major > 18) or (distro.id() == 'linuxmint' and u_major > 19):
|
self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.15.2-ubuntu-18.04-amd64.tar.xz'
|
||||||
self.__no_qt_package_error()
|
elif (distro.id() == 'ubuntu' and u_major > 18) or (distro.id() == 'linuxmint' and u_major > 19):
|
||||||
|
self.__no_qt_package_error()
|
||||||
|
else:
|
||||||
|
self.__unsupported_error()
|
||||||
else:
|
else:
|
||||||
self.__unsupported_error()
|
self.__no_qt_package_error()
|
||||||
|
|
||||||
|
|
||||||
elif 'aarch64' == cpu_architecture:
|
elif 'aarch64' == cpu_architecture:
|
||||||
if distro.id() == 'ubuntu':
|
if distro.id() == 'ubuntu':
|
||||||
|
|
|
@ -182,7 +182,7 @@ void runTimingTests() {
|
||||||
const int EXTRA_JUNK_SIZE = 200;
|
const int EXTRA_JUNK_SIZE = 200;
|
||||||
extraJunk.append((unsigned char)255);
|
extraJunk.append((unsigned char)255);
|
||||||
for (int i = 0; i < EXTRA_JUNK_SIZE; i++) {
|
for (int i = 0; i < EXTRA_JUNK_SIZE; i++) {
|
||||||
extraJunk.append(QString("junk"));
|
extraJunk.append(QString("junk").toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -257,17 +257,17 @@ void runUnitTests() {
|
||||||
|
|
||||||
quint64 LAST_TEST = 10;
|
quint64 LAST_TEST = 10;
|
||||||
quint64 SKIP_BY = 1;
|
quint64 SKIP_BY = 1;
|
||||||
|
|
||||||
for (quint64 value = 0; value <= LAST_TEST; value += SKIP_BY) {
|
for (quint64 value = 0; value <= LAST_TEST; value += SKIP_BY) {
|
||||||
qDebug() << "value:" << value;
|
qDebug() << "value:" << value;
|
||||||
|
|
||||||
ByteCountCoded<quint64> codedValue = value;
|
ByteCountCoded<quint64> codedValue = value;
|
||||||
|
|
||||||
QByteArray codedValueBuffer = codedValue;
|
QByteArray codedValueBuffer = codedValue;
|
||||||
|
|
||||||
codedValueBuffer.append((unsigned char)255);
|
codedValueBuffer.append((unsigned char)255);
|
||||||
codedValueBuffer.append(QString("junk"));
|
codedValueBuffer.append(QString("junk").toUtf8());
|
||||||
|
|
||||||
qDebug() << "codedValueBuffer:";
|
qDebug() << "codedValueBuffer:";
|
||||||
outputBufferBits((const unsigned char*)codedValueBuffer.constData(), codedValueBuffer.size());
|
outputBufferBits((const unsigned char*)codedValueBuffer.constData(), codedValueBuffer.size());
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ void runUnitTests() {
|
||||||
quint64 valueDecoded = valueDecoder;
|
quint64 valueDecoded = valueDecoder;
|
||||||
qDebug() << "valueDecoded:" << valueDecoded;
|
qDebug() << "valueDecoded:" << valueDecoded;
|
||||||
qDebug() << "bytesConsumed:" << bytesConsumed;
|
qDebug() << "bytesConsumed:" << bytesConsumed;
|
||||||
|
|
||||||
|
|
||||||
if (value == valueDecoded) {
|
if (value == valueDecoded) {
|
||||||
qDebug() << "SUCCESS!";
|
qDebug() << "SUCCESS!";
|
||||||
|
|
|
@ -525,7 +525,7 @@ bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** out
|
||||||
} else {
|
} else {
|
||||||
foundFooter = (line == IMAGE_FOOTER);
|
foundFooter = (line == IMAGE_FOOTER);
|
||||||
if (!foundFooter) {
|
if (!foundFooter) {
|
||||||
base64EncryptedBuffer.append(line);
|
base64EncryptedBuffer.append(line.toUtf8());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -844,7 +844,7 @@ glm::uvec2 OpenGLDisplayPlugin::getSurfacePixels() const {
|
||||||
uvec2 result;
|
uvec2 result;
|
||||||
auto window = _container->getPrimaryWidget();
|
auto window = _container->getPrimaryWidget();
|
||||||
if (window) {
|
if (window) {
|
||||||
result = toGlm(window->geometry().size() * window->devicePixelRatio());
|
result = toGlm(window->geometry().size() * window->devicePixelRatioF());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,7 +239,7 @@ QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::
|
||||||
} else {
|
} else {
|
||||||
requestURL.setPath(getMetaverseServerURLPath(true) + path.left(queryStringLocation));
|
requestURL.setPath(getMetaverseServerURLPath(true) + path.left(queryStringLocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
// qCDebug(networking) << "Creating request path" << requestURL;
|
// qCDebug(networking) << "Creating request path" << requestURL;
|
||||||
// qCDebug(networking) << "requestURL.isValid()" << requestURL.isValid();
|
// qCDebug(networking) << "requestURL.isValid()" << requestURL.isValid();
|
||||||
// qCDebug(networking) << "requestURL.errorString()" << requestURL.errorString();
|
// qCDebug(networking) << "requestURL.errorString()" << requestURL.errorString();
|
||||||
|
@ -573,7 +573,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
|
||||||
postData.append("grant_type=password&");
|
postData.append("grant_type=password&");
|
||||||
postData.append("username=" + QUrl::toPercentEncoding(login) + "&");
|
postData.append("username=" + QUrl::toPercentEncoding(login) + "&");
|
||||||
postData.append("password=" + QUrl::toPercentEncoding(password) + "&");
|
postData.append("password=" + QUrl::toPercentEncoding(password) + "&");
|
||||||
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
|
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE.toUtf8());
|
||||||
|
|
||||||
request.setUrl(grantURL);
|
request.setUrl(grantURL);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
@ -594,9 +594,9 @@ void AccountManager::requestAccessTokenWithAuthCode(const QString& authCode, con
|
||||||
|
|
||||||
QByteArray postData;
|
QByteArray postData;
|
||||||
postData.append("grant_type=authorization_code&");
|
postData.append("grant_type=authorization_code&");
|
||||||
postData.append("client_id=" + clientId + "&");
|
postData.append("client_id=" + clientId.toUtf8() + "&");
|
||||||
postData.append("client_secret=" + clientSecret + "&");
|
postData.append("client_secret=" + clientSecret.toUtf8() + "&");
|
||||||
postData.append("code=" + authCode + "&");
|
postData.append("code=" + authCode.toUtf8() + "&");
|
||||||
postData.append("redirect_uri=" + QUrl::toPercentEncoding(redirectUri));
|
postData.append("redirect_uri=" + QUrl::toPercentEncoding(redirectUri));
|
||||||
|
|
||||||
request.setUrl(grantURL);
|
request.setUrl(grantURL);
|
||||||
|
@ -618,7 +618,7 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) {
|
||||||
QByteArray postData;
|
QByteArray postData;
|
||||||
postData.append("grant_type=password&");
|
postData.append("grant_type=password&");
|
||||||
postData.append("steam_auth_ticket=" + QUrl::toPercentEncoding(authSessionTicket) + "&");
|
postData.append("steam_auth_ticket=" + QUrl::toPercentEncoding(authSessionTicket) + "&");
|
||||||
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
|
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE.toUtf8());
|
||||||
|
|
||||||
request.setUrl(grantURL);
|
request.setUrl(grantURL);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
@ -639,9 +639,9 @@ void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QS
|
||||||
|
|
||||||
QByteArray postData;
|
QByteArray postData;
|
||||||
postData.append("grant_type=password&");
|
postData.append("grant_type=password&");
|
||||||
postData.append("oculus_nonce=" + nonce + "&");
|
postData.append("oculus_nonce=" + nonce.toUtf8() + "&");
|
||||||
postData.append("oculus_id=" + oculusID + "&");
|
postData.append("oculus_id=" + oculusID.toUtf8() + "&");
|
||||||
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
|
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE.toUtf8());
|
||||||
|
|
||||||
request.setUrl(grantURL);
|
request.setUrl(grantURL);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
@ -671,7 +671,7 @@ void AccountManager::refreshAccessToken() {
|
||||||
QByteArray postData;
|
QByteArray postData;
|
||||||
postData.append("grant_type=refresh_token&");
|
postData.append("grant_type=refresh_token&");
|
||||||
postData.append("refresh_token=" + QUrl::toPercentEncoding(_accountInfo.getAccessToken().refreshToken) + "&");
|
postData.append("refresh_token=" + QUrl::toPercentEncoding(_accountInfo.getAccessToken().refreshToken) + "&");
|
||||||
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
|
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE.toUtf8());
|
||||||
|
|
||||||
request.setUrl(grantURL);
|
request.setUrl(grantURL);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
|
|
@ -57,7 +57,7 @@ void DomainAccountManager::setAuthURL(const QUrl& authURL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentAuth.authURL = authURL;
|
_currentAuth.authURL = authURL;
|
||||||
qCDebug(networking) << "DomainAccountManager URL for authenticated requests has been changed to"
|
qCDebug(networking) << "DomainAccountManager URL for authenticated requests has been changed to"
|
||||||
<< qPrintable(_currentAuth.authURL.toString());
|
<< qPrintable(_currentAuth.authURL.toString());
|
||||||
|
|
||||||
_currentAuth.accessToken = "";
|
_currentAuth.accessToken = "";
|
||||||
|
@ -70,7 +70,7 @@ bool DomainAccountManager::hasLogIn() {
|
||||||
return !_currentAuth.authURL.isEmpty();
|
return !_currentAuth.authURL.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainAccountManager::isLoggedIn() {
|
bool DomainAccountManager::isLoggedIn() {
|
||||||
return !_currentAuth.authURL.isEmpty() && hasValidAccessToken();
|
return !_currentAuth.authURL.isEmpty() && hasValidAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt
|
||||||
formData.append("grant_type=password&");
|
formData.append("grant_type=password&");
|
||||||
formData.append("username=" + QUrl::toPercentEncoding(username) + "&");
|
formData.append("username=" + QUrl::toPercentEncoding(username) + "&");
|
||||||
formData.append("password=" + QUrl::toPercentEncoding(password) + "&");
|
formData.append("password=" + QUrl::toPercentEncoding(password) + "&");
|
||||||
formData.append("client_id=" + _currentAuth.clientID);
|
formData.append("client_id=" + _currentAuth.clientID.toUtf8());
|
||||||
|
|
||||||
request.setUrl(_currentAuth.authURL);
|
request.setUrl(_currentAuth.authURL);
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ bool DomainAccountManager::hasValidAccessToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ####### TODO
|
// ####### TODO
|
||||||
|
|
||||||
// if (!_isWaitingForTokenRefresh && needsToRefreshToken()) {
|
// if (!_isWaitingForTokenRefresh && needsToRefreshToken()) {
|
||||||
// refreshAccessToken();
|
// refreshAccessToken();
|
||||||
// }
|
// }
|
||||||
|
@ -184,18 +184,18 @@ void DomainAccountManager::setTokensFromJSON(const QJsonObject& jsonObject, cons
|
||||||
bool DomainAccountManager::checkAndSignalForAccessToken() {
|
bool DomainAccountManager::checkAndSignalForAccessToken() {
|
||||||
bool hasToken = hasValidAccessToken();
|
bool hasToken = hasValidAccessToken();
|
||||||
|
|
||||||
// ####### TODO: Handle hasToken == true.
|
// ####### TODO: Handle hasToken == true.
|
||||||
// It causes the login dialog not to display (OK) but somewhere the domain server needs to be sent it (and if domain server
|
// It causes the login dialog not to display (OK) but somewhere the domain server needs to be sent it (and if domain server
|
||||||
// gets error when trying to use it then user should be prompted to login).
|
// gets error when trying to use it then user should be prompted to login).
|
||||||
hasToken = false;
|
hasToken = false;
|
||||||
|
|
||||||
if (!hasToken) {
|
if (!hasToken) {
|
||||||
// Emit a signal so somebody can call back to us and request an access token given a user name and password.
|
// Emit a signal so somebody can call back to us and request an access token given a user name and password.
|
||||||
|
|
||||||
// Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed.
|
// Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed.
|
||||||
auto domain = _currentAuth.authURL.host();
|
auto domain = _currentAuth.authURL.host();
|
||||||
QTimer::singleShot(500, this, [this, domain] {
|
QTimer::singleShot(500, this, [this, domain] {
|
||||||
emit this->authRequired(domain);
|
emit this->authRequired(domain);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
||||||
// Adding the action name
|
// Adding the action name
|
||||||
QHttpPart actionPart;
|
QHttpPart actionPart;
|
||||||
actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
|
actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
|
||||||
actionPart.setBody(QByteArray().append(action));
|
actionPart.setBody(QByteArray().append(action.toUtf8()));
|
||||||
multipart->append(actionPart);
|
multipart->append(actionPart);
|
||||||
|
|
||||||
// Log the local-time that this event was logged
|
// Log the local-time that this event was logged
|
||||||
|
|
|
@ -63,11 +63,11 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromFile(QString path) {
|
||||||
QByteArray OctreeUtils::RawOctreeData::toByteArray() {
|
QByteArray OctreeUtils::RawOctreeData::toByteArray() {
|
||||||
QByteArray jsonString;
|
QByteArray jsonString;
|
||||||
|
|
||||||
jsonString += QString("{\n \"DataVersion\": %1,\n").arg(dataVersion);
|
jsonString += QString("{\n \"DataVersion\": %1,\n").arg(dataVersion).toUtf8();
|
||||||
|
|
||||||
writeSubclassData(jsonString);
|
writeSubclassData(jsonString);
|
||||||
|
|
||||||
jsonString += QString(",\n \"Id\": \"%1\",\n \"Version\": %2\n}").arg(id.toString()).arg(version);
|
jsonString += QString(",\n \"Id\": \"%1\",\n \"Version\": %2\n}").arg(id.toString()).arg(version).toUtf8();
|
||||||
|
|
||||||
return jsonString;
|
return jsonString;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,14 +38,14 @@
|
||||||
* <p><em>This API currently has no effect and is not used.</em></p>
|
* <p><em>This API currently has no effect and is not used.</em></p>
|
||||||
*
|
*
|
||||||
* @namespace OffscreenFlags
|
* @namespace OffscreenFlags
|
||||||
*
|
*
|
||||||
* @hifi-interface
|
* @hifi-interface
|
||||||
* @hifi-client-entity
|
* @hifi-client-entity
|
||||||
* @hifi-avatar
|
* @hifi-avatar
|
||||||
*
|
*
|
||||||
* @property {boolean} navigationFocused - <code>true</code> if UI has joystick navigation focus, <code>false</code> if it
|
* @property {boolean} navigationFocused - <code>true</code> if UI has joystick navigation focus, <code>false</code> if it
|
||||||
* doesn't.
|
* doesn't.
|
||||||
* @property {boolean} navigationFocusDisabled - <code>true</code> if UI joystick navigation focus is disabled,
|
* @property {boolean} navigationFocusDisabled - <code>true</code> if UI joystick navigation focus is disabled,
|
||||||
* <code>false</code> if it isn't.
|
* <code>false</code> if it isn't.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ public:
|
||||||
emit navigationFocusDisabledChanged();
|
emit navigationFocusDisabledChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
/*@jsdoc
|
/*@jsdoc
|
||||||
|
@ -99,9 +99,9 @@ private:
|
||||||
|
|
||||||
static OffscreenFlags* offscreenFlags { nullptr };
|
static OffscreenFlags* offscreenFlags { nullptr };
|
||||||
|
|
||||||
// This hack allows the QML UI to work with keys that are also bound as
|
// This hack allows the QML UI to work with keys that are also bound as
|
||||||
// shortcuts at the application level. However, it seems as though the
|
// shortcuts at the application level. However, it seems as though the
|
||||||
// bound actions are still getting triggered. At least for backspace.
|
// bound actions are still getting triggered. At least for backspace.
|
||||||
// Not sure why.
|
// Not sure why.
|
||||||
//
|
//
|
||||||
// However, the problem may go away once we switch to the new menu system,
|
// However, the problem may go away once we switch to the new menu system,
|
||||||
|
@ -168,7 +168,7 @@ void OffscreenUi::onRootContextCreated(QQmlContext* qmlContext) {
|
||||||
qmlContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
|
qmlContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
qmlContext->setContextProperty("DebugQML", QVariant(true));
|
qmlContext->setContextProperty("DebugQML", QVariant(true));
|
||||||
#else
|
#else
|
||||||
qmlContext->setContextProperty("DebugQML", QVariant(false));
|
qmlContext->setContextProperty("DebugQML", QVariant(false));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ QQuickItem* OffscreenUi::createMessageBox(Icon icon, const QString& title, const
|
||||||
map.insert("buttons", buttons.operator int());
|
map.insert("buttons", buttons.operator int());
|
||||||
map.insert("defaultButton", defaultButton);
|
map.insert("defaultButton", defaultButton);
|
||||||
QVariant result;
|
QVariant result;
|
||||||
bool invokeResult;
|
bool invokeResult = false;
|
||||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
if (tablet->getToolbarMode() && _desktop) {
|
if (tablet->getToolbarMode() && _desktop) {
|
||||||
|
@ -309,7 +309,7 @@ int OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) {
|
||||||
if (!messageBox) {
|
if (!messageBox) {
|
||||||
return QMessageBox::NoButton;
|
return QMessageBox::NoButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageBoxListener(messageBox).waitForButtonResult();
|
return MessageBoxListener(messageBox).waitForButtonResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,8 +424,8 @@ QString OffscreenUi::getText(const Icon icon, const QString& title, const QStrin
|
||||||
QString OffscreenUi::getItem(const Icon icon, const QString& title, const QString& label, const QStringList& items,
|
QString OffscreenUi::getItem(const Icon icon, const QString& title, const QString& label, const QStringList& items,
|
||||||
int current, bool editable, bool* ok) {
|
int current, bool editable, bool* ok) {
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
*ok = false;
|
*ok = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
|
@ -580,7 +580,7 @@ QQuickItem* OffscreenUi::createInputDialog(const Icon icon, const QString& title
|
||||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
|
|
||||||
bool invokeResult;
|
bool invokeResult = false;
|
||||||
if (tablet->getToolbarMode() && _desktop) {
|
if (tablet->getToolbarMode() && _desktop) {
|
||||||
invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
|
invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
|
||||||
Q_RETURN_ARG(QVariant, result),
|
Q_RETURN_ARG(QVariant, result),
|
||||||
|
@ -608,7 +608,7 @@ QQuickItem* OffscreenUi::createCustomInputDialog(const Icon icon, const QString&
|
||||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
|
|
||||||
bool invokeResult;
|
bool invokeResult = false;
|
||||||
if (tablet->getToolbarMode() && _desktop) {
|
if (tablet->getToolbarMode() && _desktop) {
|
||||||
invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
|
invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
|
||||||
Q_RETURN_ARG(QVariant, result),
|
Q_RETURN_ARG(QVariant, result),
|
||||||
|
@ -619,7 +619,7 @@ QQuickItem* OffscreenUi::createCustomInputDialog(const Icon icon, const QString&
|
||||||
Q_ARG(QVariant, QVariant::fromValue(map)));
|
Q_ARG(QVariant, QVariant::fromValue(map)));
|
||||||
emit tabletScriptingInterface->tabletNotification();
|
emit tabletScriptingInterface->tabletNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invokeResult) {
|
if (!invokeResult) {
|
||||||
qWarning() << "Failed to create custom message box";
|
qWarning() << "Failed to create custom message box";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -648,13 +648,13 @@ void OffscreenUi::setNavigationFocused(bool focused) {
|
||||||
// FIXME HACK....
|
// FIXME HACK....
|
||||||
// This hack is an attempt to work around the 'offscreen UI can't gain keyboard focus' bug
|
// This hack is an attempt to work around the 'offscreen UI can't gain keyboard focus' bug
|
||||||
// https://app.asana.com/0/27650181942747/83176475832393
|
// https://app.asana.com/0/27650181942747/83176475832393
|
||||||
// The problem seems related to https://bugreports.qt.io/browse/QTBUG-50309
|
// The problem seems related to https://bugreports.qt.io/browse/QTBUG-50309
|
||||||
//
|
//
|
||||||
// The workaround seems to be to give some other window (same process or another process doesn't seem to matter)
|
// The workaround seems to be to give some other window (same process or another process doesn't seem to matter)
|
||||||
// focus and then put focus back on the interface main window.
|
// focus and then put focus back on the interface main window.
|
||||||
//
|
//
|
||||||
// If I could reliably reproduce this bug I could eventually track down what state change is occuring
|
// If I could reliably reproduce this bug I could eventually track down what state change is occuring
|
||||||
// during the process of the main window losing and then gaining focus, but failing that, here's a
|
// during the process of the main window losing and then gaining focus, but failing that, here's a
|
||||||
// brute force way of triggering that state change at application start in a way that should be nearly
|
// brute force way of triggering that state change at application start in a way that should be nearly
|
||||||
// imperceptible to the user.
|
// imperceptible to the user.
|
||||||
class KeyboardFocusHack : public QObject {
|
class KeyboardFocusHack : public QObject {
|
||||||
|
@ -754,7 +754,7 @@ private slots:
|
||||||
|
|
||||||
QString OffscreenUi::fileDialog(const QVariantMap& properties) {
|
QString OffscreenUi::fileDialog(const QVariantMap& properties) {
|
||||||
QVariant buildDialogResult;
|
QVariant buildDialogResult;
|
||||||
bool invokeResult;
|
bool invokeResult = false;
|
||||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
if (tablet->getToolbarMode() && _desktop) {
|
if (tablet->getToolbarMode() && _desktop) {
|
||||||
|
@ -783,7 +783,7 @@ QString OffscreenUi::fileDialog(const QVariantMap& properties) {
|
||||||
|
|
||||||
ModalDialogListener* OffscreenUi::fileDialogAsync(const QVariantMap& properties) {
|
ModalDialogListener* OffscreenUi::fileDialogAsync(const QVariantMap& properties) {
|
||||||
QVariant buildDialogResult;
|
QVariant buildDialogResult;
|
||||||
bool invokeResult;
|
bool invokeResult = false;
|
||||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
if (tablet->getToolbarMode() && _desktop) {
|
if (tablet->getToolbarMode() && _desktop) {
|
||||||
|
@ -1003,7 +1003,7 @@ class AssetDialogListener : public ModalDialogListener {
|
||||||
QString OffscreenUi::assetDialog(const QVariantMap& properties) {
|
QString OffscreenUi::assetDialog(const QVariantMap& properties) {
|
||||||
// ATP equivalent of fileDialog().
|
// ATP equivalent of fileDialog().
|
||||||
QVariant buildDialogResult;
|
QVariant buildDialogResult;
|
||||||
bool invokeResult;
|
bool invokeResult = false;
|
||||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
if (tablet->getToolbarMode() && _desktop) {
|
if (tablet->getToolbarMode() && _desktop) {
|
||||||
|
@ -1033,7 +1033,7 @@ QString OffscreenUi::assetDialog(const QVariantMap& properties) {
|
||||||
ModalDialogListener *OffscreenUi::assetDialogAsync(const QVariantMap& properties) {
|
ModalDialogListener *OffscreenUi::assetDialogAsync(const QVariantMap& properties) {
|
||||||
// ATP equivalent of fileDialog().
|
// ATP equivalent of fileDialog().
|
||||||
QVariant buildDialogResult;
|
QVariant buildDialogResult;
|
||||||
bool invokeResult;
|
bool invokeResult = false;
|
||||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
if (tablet->getToolbarMode() && _desktop) {
|
if (tablet->getToolbarMode() && _desktop) {
|
||||||
|
@ -1165,7 +1165,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// QML input elements absorb key press, but apparently not key release.
|
// QML input elements absorb key press, but apparently not key release.
|
||||||
// therefore we want to ensure that key release events for key presses that were
|
// therefore we want to ensure that key release events for key presses that were
|
||||||
// accepted by the QML layer are suppressed
|
// accepted by the QML layer are suppressed
|
||||||
if (type == QEvent::KeyRelease && pressed) {
|
if (type == QEvent::KeyRelease && pressed) {
|
||||||
pressed = false;
|
pressed = false;
|
||||||
|
@ -1175,10 +1175,6 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int OffscreenUi::getMenuUserDataId() const {
|
|
||||||
return _vrMenu->_userDataId;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModalDialogListener::ModalDialogListener(QQuickItem *dialog) : _dialog(dialog) {
|
ModalDialogListener::ModalDialogListener(QQuickItem *dialog) : _dialog(dialog) {
|
||||||
if (!dialog) {
|
if (!dialog) {
|
||||||
_finished = true;
|
_finished = true;
|
||||||
|
|
|
@ -74,7 +74,7 @@ public:
|
||||||
|
|
||||||
// Setting pinned to true will hide all overlay elements on the desktop that don't have a pinned flag
|
// Setting pinned to true will hide all overlay elements on the desktop that don't have a pinned flag
|
||||||
void setPinned(bool pinned = true);
|
void setPinned(bool pinned = true);
|
||||||
|
|
||||||
void togglePinned();
|
void togglePinned();
|
||||||
void setConstrainToolbarToCenterX(bool constrained);
|
void setConstrainToolbarToCenterX(bool constrained);
|
||||||
|
|
||||||
|
@ -237,7 +237,6 @@ public:
|
||||||
static ModalDialogListener* getTextAsync(const Icon icon, const QString & title, const QString & label, const QString & text = QString());
|
static ModalDialogListener* getTextAsync(const Icon icon, const QString & title, const QString & label, const QString & text = QString());
|
||||||
static ModalDialogListener* getItemAsync(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true);
|
static ModalDialogListener* getItemAsync(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true);
|
||||||
|
|
||||||
unsigned int getMenuUserDataId() const;
|
|
||||||
QList<QObject *> &getModalDialogListeners();
|
QList<QObject *> &getModalDialogListeners();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -270,7 +269,7 @@ private:
|
||||||
QList<QObject*> _modalDialogListeners;
|
QList<QObject*> _modalDialogListeners;
|
||||||
std::unordered_map<int, bool> _pressedKeys;
|
std::unordered_map<int, bool> _pressedKeys;
|
||||||
VrMenu* _vrMenu { nullptr };
|
VrMenu* _vrMenu { nullptr };
|
||||||
QQueue<std::function<void(VrMenu*)>> _queuedMenuInitializers;
|
QQueue<std::function<void(VrMenu*)>> _queuedMenuInitializers;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -17,139 +17,116 @@
|
||||||
#include "OffscreenUi.h"
|
#include "OffscreenUi.h"
|
||||||
#include "ui/Logging.h"
|
#include "ui/Logging.h"
|
||||||
|
|
||||||
static unsigned int USER_DATA_ID = 0;
|
|
||||||
|
|
||||||
// Binds together a Qt Action or Menu with the QML Menu or MenuItem
|
|
||||||
//
|
|
||||||
// TODO On reflection, it may be pointless to use the UUID. Perhaps
|
|
||||||
// simply creating the bidirectional link pointing to both the widget
|
|
||||||
// and qml object and inject the pointer into both objects
|
|
||||||
class MenuUserData : public QObjectUserData {
|
|
||||||
public:
|
|
||||||
MenuUserData(QAction* action, QObject* qmlObject, QObject* qmlParent) {
|
|
||||||
if (!USER_DATA_ID) {
|
|
||||||
USER_DATA_ID = DependencyManager::get<OffscreenUi>()->getMenuUserDataId();
|
|
||||||
}
|
|
||||||
_action = action;
|
|
||||||
_qml = qmlObject;
|
|
||||||
_qmlParent = qmlParent;
|
|
||||||
|
|
||||||
action->setUserData(USER_DATA_ID, this);
|
MenuUserData::MenuUserData(QAction* action, QObject* qmlObject, QObject* qmlParent) {
|
||||||
qmlObject->setUserData(USER_DATA_ID, this);
|
_action = action;
|
||||||
qmlObject->setObjectName(uuid.toString());
|
_qml = qmlObject;
|
||||||
// Make sure we can find it again in the future
|
_qmlParent = qmlParent;
|
||||||
|
|
||||||
|
action->setProperty(USER_DATA, QVariant::fromValue(this));
|
||||||
|
qmlObject->setProperty(USER_DATA, QVariant::fromValue(this));
|
||||||
|
qmlObject->setObjectName(uuid.toString());
|
||||||
|
// Make sure we can find it again in the future
|
||||||
|
updateQmlItemFromAction();
|
||||||
|
_changedConnection = QObject::connect(action, &QAction::changed, [=] {
|
||||||
updateQmlItemFromAction();
|
updateQmlItemFromAction();
|
||||||
_changedConnection = QObject::connect(action, &QAction::changed, [=] {
|
});
|
||||||
updateQmlItemFromAction();
|
_shutdownConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=] {
|
||||||
});
|
|
||||||
_shutdownConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=] {
|
|
||||||
QObject::disconnect(_changedConnection);
|
|
||||||
});
|
|
||||||
|
|
||||||
class ExclusionGroupSetter : public QObject {
|
|
||||||
public:
|
|
||||||
ExclusionGroupSetter(QObject* from, QObject* to, QObject* qmlParent) : QObject(from), _from(from), _to(to), _qmlParent(qmlParent) {
|
|
||||||
_from->installEventFilter(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
~ExclusionGroupSetter() {
|
|
||||||
_from->removeEventFilter(this);
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
virtual bool eventFilter(QObject* o, QEvent* e) override {
|
|
||||||
if (e->type() == QEvent::DynamicPropertyChange) {
|
|
||||||
QDynamicPropertyChangeEvent* dpc = static_cast<QDynamicPropertyChangeEvent*>(e);
|
|
||||||
if (dpc->propertyName() == "exclusionGroup")
|
|
||||||
{
|
|
||||||
// unfortunately Qt doesn't support passing dynamic properties between C++ / QML, so we have to use this ugly helper function
|
|
||||||
QMetaObject::invokeMethod(_qmlParent,
|
|
||||||
"addExclusionGroup",
|
|
||||||
Qt::DirectConnection,
|
|
||||||
Q_ARG(QVariant, QVariant::fromValue(_to)),
|
|
||||||
Q_ARG(QVariant, _from->property(dpc->propertyName())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return QObject::eventFilter(o, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QObject* _from;
|
|
||||||
QObject* _to;
|
|
||||||
QObject* _qmlParent;
|
|
||||||
};
|
|
||||||
|
|
||||||
new ExclusionGroupSetter(action, qmlObject, qmlParent);
|
|
||||||
}
|
|
||||||
|
|
||||||
~MenuUserData() {
|
|
||||||
QObject::disconnect(_changedConnection);
|
QObject::disconnect(_changedConnection);
|
||||||
QObject::disconnect(_shutdownConnection);
|
});
|
||||||
_action->setUserData(USER_DATA_ID, nullptr);
|
|
||||||
_qml->setUserData(USER_DATA_ID, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateQmlItemFromAction() {
|
class ExclusionGroupSetter : public QObject {
|
||||||
_qml->setProperty("checkable", _action->isCheckable());
|
public:
|
||||||
_qml->setProperty("enabled", _action->isEnabled());
|
ExclusionGroupSetter(QObject* from, QObject* to, QObject* qmlParent) : QObject(from), _from(from), _to(to), _qmlParent(qmlParent) {
|
||||||
QString text = _action->text();
|
_from->installEventFilter(this);
|
||||||
_qml->setProperty("text", text);
|
|
||||||
_qml->setProperty("shortcut", _action->shortcut().toString());
|
|
||||||
_qml->setProperty("checked", _action->isChecked());
|
|
||||||
_qml->setProperty("visible", _action->isVisible());
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
_qml->setProperty("checkable", 0);
|
|
||||||
_qml->setProperty("enabled", 0);
|
|
||||||
_qml->setProperty("text", 0);
|
|
||||||
_qml->setProperty("shortcut", 0);
|
|
||||||
_qml->setProperty("checked", 0);
|
|
||||||
_qml->setProperty("visible", 0);
|
|
||||||
|
|
||||||
_action->setUserData(USER_DATA_ID, nullptr);
|
|
||||||
_qml->setUserData(USER_DATA_ID, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const QUuid uuid{ QUuid::createUuid() };
|
|
||||||
|
|
||||||
static bool hasData(QAction* object) {
|
|
||||||
if (!object) {
|
|
||||||
qWarning() << "Attempted to fetch MenuUserData for null object";
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return (nullptr != static_cast<MenuUserData*>(object->userData(USER_DATA_ID)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static MenuUserData* forObject(QAction* object) {
|
~ExclusionGroupSetter() {
|
||||||
if (!object) {
|
_from->removeEventFilter(this);
|
||||||
qWarning() << "Attempted to fetch MenuUserData for null object";
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
auto result = static_cast<MenuUserData*>(object->userData(USER_DATA_ID));
|
protected:
|
||||||
if (!result) {
|
virtual bool eventFilter(QObject* o, QEvent* e) override {
|
||||||
qWarning() << "Unable to find MenuUserData for object " << object;
|
if (e->type() == QEvent::DynamicPropertyChange) {
|
||||||
if (auto action = dynamic_cast<QAction*>(object)) {
|
QDynamicPropertyChangeEvent* dpc = static_cast<QDynamicPropertyChangeEvent*>(e);
|
||||||
qWarning() << action->text();
|
if (dpc->propertyName() == "exclusionGroup") {
|
||||||
} else if (auto menu = dynamic_cast<QMenu*>(object)) {
|
// unfortunately Qt doesn't support passing dynamic properties between C++ / QML, so we have to use this ugly helper function
|
||||||
qWarning() << menu->title();
|
QMetaObject::invokeMethod(_qmlParent,
|
||||||
|
"addExclusionGroup",
|
||||||
|
Qt::DirectConnection,
|
||||||
|
Q_ARG(QVariant, QVariant::fromValue(_to)),
|
||||||
|
Q_ARG(QVariant, _from->property(dpc->propertyName())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
|
||||||
|
return QObject::eventFilter(o, e);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
|
private:
|
||||||
|
QObject* _from;
|
||||||
|
QObject* _to;
|
||||||
|
QObject* _qmlParent;
|
||||||
|
};
|
||||||
|
|
||||||
|
new ExclusionGroupSetter(action, qmlObject, qmlParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuUserData::~MenuUserData() {
|
||||||
|
QObject::disconnect(_changedConnection);
|
||||||
|
QObject::disconnect(_shutdownConnection);
|
||||||
|
_action->setProperty(USER_DATA, QVariant());
|
||||||
|
_qml->setProperty(USER_DATA, QVariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuUserData::updateQmlItemFromAction() {
|
||||||
|
_qml->setProperty("checkable", _action->isCheckable());
|
||||||
|
_qml->setProperty("enabled", _action->isEnabled());
|
||||||
|
QString text = _action->text();
|
||||||
|
_qml->setProperty("text", text);
|
||||||
|
_qml->setProperty("shortcut", _action->shortcut().toString());
|
||||||
|
_qml->setProperty("checked", _action->isChecked());
|
||||||
|
_qml->setProperty("visible", _action->isVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuUserData::clear() {
|
||||||
|
_qml->setProperty("checkable", 0);
|
||||||
|
_qml->setProperty("enabled", 0);
|
||||||
|
_qml->setProperty("text", 0);
|
||||||
|
_qml->setProperty("shortcut", 0);
|
||||||
|
_qml->setProperty("checked", 0);
|
||||||
|
_qml->setProperty("visible", 0);
|
||||||
|
|
||||||
|
_action->setProperty(USER_DATA, QVariant());
|
||||||
|
_qml->setProperty(USER_DATA, QVariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool MenuUserData::hasData(QAction* object) {
|
||||||
|
if (!object) {
|
||||||
|
qWarning() << "Attempted to fetch MenuUserData for null object";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return (nullptr != object->property(USER_DATA).value<MenuUserData*>());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
MenuUserData* MenuUserData::forObject(QAction* object) {
|
||||||
Q_DISABLE_COPY(MenuUserData);
|
if (!object) {
|
||||||
|
qWarning() << "Attempted to fetch MenuUserData for null object";
|
||||||
QMetaObject::Connection _shutdownConnection;
|
return nullptr;
|
||||||
QMetaObject::Connection _changedConnection;
|
}
|
||||||
QAction* _action { nullptr };
|
auto result = object->property(USER_DATA).value<MenuUserData*>();
|
||||||
QObject* _qml { nullptr };
|
if (!result) {
|
||||||
QObject* _qmlParent{ nullptr };
|
qWarning() << "Unable to find MenuUserData for object " << object;
|
||||||
};
|
if (auto action = dynamic_cast<QAction*>(object)) {
|
||||||
|
qWarning() << action->text();
|
||||||
|
} else if (auto menu = dynamic_cast<QMenu*>(object)) {
|
||||||
|
qWarning() << menu->title();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
VrMenu::VrMenu(OffscreenUi* parent) : QObject(parent) {
|
VrMenu::VrMenu(OffscreenUi* parent) : QObject(parent) {
|
||||||
_rootMenu = parent->getRootItem()->findChild<QObject*>("rootMenu");
|
_rootMenu = parent->getRootItem()->findChild<QObject*>("rootMenu");
|
||||||
|
|
|
@ -18,8 +18,44 @@
|
||||||
#include <QSignalMapper>
|
#include <QSignalMapper>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QUuid>
|
||||||
#include "OffscreenUi.h"
|
#include "OffscreenUi.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Binds together a Qt Action or Menu with the QML Menu or MenuItem
|
||||||
|
//
|
||||||
|
// TODO On reflection, it may be pointless to use the UUID. Perhaps
|
||||||
|
// simply creating the bidirectional link pointing to both the widget
|
||||||
|
// and qml object and inject the pointer into both objects
|
||||||
|
class MenuUserData : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
MenuUserData(QAction* action, QObject* qmlObject, QObject* qmlParent);
|
||||||
|
~MenuUserData();
|
||||||
|
void updateQmlItemFromAction();
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
const QUuid uuid{ QUuid::createUuid() };
|
||||||
|
|
||||||
|
static bool hasData(QAction* object);
|
||||||
|
|
||||||
|
static MenuUserData* forObject(QAction* object);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(MenuUserData);
|
||||||
|
|
||||||
|
static constexpr const char *USER_DATA{"user_data"};
|
||||||
|
|
||||||
|
QMetaObject::Connection _shutdownConnection;
|
||||||
|
QMetaObject::Connection _changedConnection;
|
||||||
|
QAction* _action { nullptr };
|
||||||
|
QObject* _qml { nullptr };
|
||||||
|
QObject* _qmlParent{ nullptr };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME break up the rendering code (VrMenu) and the code for mirroring a Widget based menu in QML
|
// FIXME break up the rendering code (VrMenu) and the code for mirroring a Widget based menu in QML
|
||||||
class VrMenu : public QObject {
|
class VrMenu : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -37,7 +73,6 @@ protected:
|
||||||
|
|
||||||
friend class MenuUserData;
|
friend class MenuUserData;
|
||||||
friend class OffscreenUi;
|
friend class OffscreenUi;
|
||||||
const unsigned int _userDataId { QObject::registerUserData() };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_VrMenu_h
|
#endif // hifi_VrMenu_h
|
||||||
|
|
12
scripts/system/inventory/package-lock.json
generated
12
scripts/system/inventory/package-lock.json
generated
|
@ -6488,9 +6488,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.7",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||||
"integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==",
|
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"for-in": {
|
"for-in": {
|
||||||
|
@ -10662,9 +10662,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"shelljs": {
|
"shelljs": {
|
||||||
"version": "0.8.4",
|
"version": "0.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
|
||||||
"integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==",
|
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "^7.0.0",
|
"glob": "^7.0.0",
|
||||||
|
|
Loading…
Reference in a new issue