1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge branch 'master' into chatlog_v3_1

Conflicts:
	qtox.pro
	res.qrc
	src/misc/smileypack.cpp
	src/widget/chatareawidget.cpp
	src/widget/chatareawidget.h
	src/widget/form/chatform.cpp
	src/widget/form/genericchatform.cpp
	src/widget/form/genericchatform.h
	src/widget/form/settings/generalform.cpp
	src/widget/tool/chatactions/messageaction.cpp
	src/widget/tool/chatactions/systemmessageaction.cpp
	src/widget/widget.cpp
This commit is contained in:
krepa098 2015-01-03 10:17:53 +01:00
commit a0ea0675a7
148 changed files with 9273 additions and 2846 deletions

View File

@ -1,8 +1,8 @@
#Install Instructions
- [Dependencies](#dependencies)
- [Windows](#windows)
- [Linux](#linux)
- [OS X](#osx)
- [Windows](#windows)
<a name="dependencies" />
##Dependencies
@ -12,8 +12,205 @@
| Qt | >= 5.2.0 | core, gui, network, widget, xml, sql |
| GCC/MinGW | >= 4.8 | C++11 enabled |
| Tox Core | most recent | core, av |
| OpenCV | >= 2.4.9 | core, highgui |
| OpenCV | >= 2.4.9 | core, highgui, imgproc |
| OpenAL Soft | >= 1.16.0 | |
| filter_audio | most recent | |
<a name="linux" />
##Linux
###Simple install
Easy qTox install is provided for variety of distributions:
https://wiki.tox.im/Binaries#Apt.2FAptitude_.28Debian.2C_Ubuntu.2C_Mint.2C_etc..29
If your distribution is not listed, or you want/need to compile qTox, there are provided instructions.
**Please note that installing toxcore/qTox from AUR is not supported**, although installing other dependencies, provided that they met requirements, should be fine, unless you are installing cryptography library from AUR, which should rise red flags by itself…
----
Most of the dependencies should be available through your package manger. You may either follow the directions below, or simply run `./simple_make.sh` after cloning, which will attempt to automatically download dependencies followed by compilation.
###Cloning the Repository
In order to clone the qTox repository you need Git.
Arch Linux:
```bash
sudo pacman -S --needed git
```
Debian:
```bash
sudo apt-get install git
```
Fedora:
```bash
yum install git
```
Ubuntu:
```bash
sudo apt-get install git
```
Afterwards open a new Terminal, change to a directory of your choice and clone the repository:
```bash
cd /home/user/qTox
git clone https://github.com/tux3/qTox.git qTox
```
The following steps assumes that you cloned the repository at "/home/user/qTox". If you decided to choose another location, replace corresponding parts.
###GCC, Qt, OpenCV and OpanAL Soft
Arch Linux:
```bash
sudo pacman -S --needed base-devel qt5 opencv openal libxss
```
Debian:
```bash
sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev libxss-dev
```
Fedora:
```bash
yum groupinstall "Development Tools"
yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libXScrnSaver-devel
```
Slackware:
You can grab slackbuilds of the needed dependencies here:
http://slackbuilds.org/repository/14.1/libraries/OpenAL/
http://slackbuilds.org/repository/14.1/libraries/qt5/
http://slackbuilds.org/repository/14.1/libraries/opencv/
Ubuntu:
```bash
sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libopenal-dev libopencv-dev libxss-dev
```
###Tox Core
First of all install the dependencies of Tox Core.
Arch Linux:
```bash
sudo pacman -S --needed opus vpx
```
```
Debian:
```bash
sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev
```
Fedora:
```bash
yum install libtool autoconf automake check check-devel
```
Ubuntu:
```bash
sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev
```
Now you can either follow the instructions at https://github.com/irungentoo/toxcore/blob/master/INSTALL.md#unix or use the "bootstrap.sh" script located at "/home/user/qTox".
The script will automatically download and install Tox Core and libsodium to "/home/user/qTox/libs":
```bash
cd /home/user/qTox
./bootstrap.sh # use -h or --help for more information
```
###filter_audio
You also need to install filter_audio library separately if you did not run ``./bootstrap.sh``.
```bash
./install_libfilteraudio.sh
```
After all the dependencies are thus reeady to go, compiling should be as simple as
```bash
qmake
make
```
###Building packages
Alternately, qTox now has the experimental and probably-dodgy ability to package itself (in .deb
form natively, and .rpm form with <a href="http://joeyh.name/code/alien/">alien</a>).
After installing the required dependencies, run `bootstrap.sh` and then run the
`buildPackages.sh` script, found in the tools folder. It will automatically get the
packages necessary for building .debs, so be prepared to type your password for sudo.
<a name="osx" />
##OS X
###OSX Easy Install
Since https://github.com/ReDetection/homebrew-qtox you can easily install qtox with homebrew
```bash
brew install --HEAD ReDetection/qtox/qtox
```
###OSX Full Install Guide
This guide is intended for people who wish to use an existing or new ProjectTox-Core installation separate to the bundled installation with qTox, if you do not wish to use a separate installation you can skip to the section titled 'Final Steps'.
Installation on OSX, isn't quite straight forward, here is a quick guide on how to install;
Note that qTox now requires OpenCV and OpenAL for video and audio.
The first thing you need to do is install ProjectTox-Core with a/v support. Refer to the INSTALL guide in the PrjectTox-Core github repo.
Next you need to download QtTools (http://qt-project.org/downloads), at the time of writing this is at version .3.0.
Make sure you deselect all the unnecessary components from the 5.3 checkbox (iOS/Android libs) otherwise you will end up with a very large download.
Once that is installed you will most likely need to set the path for qmake. To do this, open up terminal and paste in the following;
```bash
export PATH=/location/to/qmake/binary:$PATH
```
For myself, the qmake binary was located in /Users/mouseym/Qt/5.3/clang_64/bin/.
This is not a permanent change, it will revert when you close the terminal window, to add it permanently you will need to add echo the above line to your .profile/.bash_profile.
Once this is installed, do the following;
```bash
git clone https://github.com/tux3/qTox
cd toxgui
qmake
```
Now, we need to create a symlink to /usr/local/lib/ and /usr/local/include/
```
mkdir -p $HOME/qTox/libs
sudo ln -s /usr/local/lib $HOME/qTox/libs/lib
sudo ln -s /usr/local/include $HOME/qTox/libs/include
```
####Final Steps
The final step is to run
```bash
make
```
in the qTox directory, or if you are using the bundled tox core installation, you can use
```bash
./bootstrap.sh
make
```
Assuming all went well you should now have a qTox.app file within the directory. Double click and it should open!
<a name="windows" />
##Windows
@ -74,168 +271,4 @@ As for OpenCV there are no prebuild packages of OpenAL Softe compiled with MinGW
make
make install
```
Copy the dll "OpenAL32.dll" located at "C:\qTox\libs\openal-build\install\bin" to "C:\qTox\libs\lib". Finally, copy the directory "AL" located at "C:\qTox\libs\openal-build\install\include" to "C:\qTox\libs\include". Unlike OpenCV you don't need to patch any files. Feel free to delete the directories "openal-soft-x.y.z" and "openal-build", but you don't need to.
<a name="linux" />
##Linux
Most of the dependencies should be available through your package manger. You may either follow the directions below, or simply run `./simple_make.sh` after cloning, which will attempt to automatically download dependencies followed by compilation.
###Cloning the Repository
In order to clone the qTox repository you need Git.
Debian:
```bash
sudo apt-get install git
```
Ubuntu:
```bash
sudo apt-get install git
```
Arch Linux:
```bash
sudo pacman -S --needed git
```
Fedora:
```bash
yum install git
```
Afterwards open a new Terminal, change to a directory of your choice and clone the repository:
```bash
cd /home/user/qTox
git clone https://github.com/tux3/qTox.git qTox
```
The following steps assumes that you cloned the repository at "/home/user/qTox". If you decided to choose another location, replace corresponding parts.
###GCC, Qt, OpenCV and OpanAL Soft
Debian:
```bash
sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev
```
Ubuntu:
```bash
sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libopenal-dev libopencv-dev
```
Arch Linux:
```bash
sudo pacman -S --needed base-devel qt5 opencv openal
```
Fedora:
```bash
yum groupinstall "Development Tools"
yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel
```
###Tox Core
First of all install the dependencies of Tox Core.
Debian:
```bash
sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev
```
Ubuntu:
```bash
sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev
```
Arch Linux: (Arch Linux provides the package "tox-git" in AUR)
```bash
sudo pacman -S --needed opus vpx
```
Fedora:
```bash
yum install libtool autoconf automake check check-devel
```
Now you can either follow the instructions at https://github.com/irungentoo/toxcore/blob/master/INSTALL.md#unix or use the "bootstrap.sh" script located at "/home/user/qTox".
The script will automatically download and install Tox Core and libsodium to "/home/user/qTox/libs":
```bash
cd /home/user/qTox
./bootstrap.sh # use -h or --help for more information
```
After all the dependencies are thus reeady to go, compiling should be as simple as
```bash
qmake
make
```
###Building packages
Alternately, qTox now has the experimental and probably-dodgy ability to package itself (in .deb
form natively, and .rpm form with <a href="http://joeyh.name/code/alien/">alien</a>).
After installing the required dependencies, run `bootstrap.sh` and then run the
`buildPackages.sh` script, found in the tools folder. It will automatically get the
packages necessary for building .debs, so be prepared to type your password for sudo.
<a name="osx" />
##OS X
###OSX Easy Install
Since https://github.com/ReDetection/homebrew-qtox you can easily install qtox with homebrew
```bash
brew install --HEAD ReDetection/qtox/qtox
```
###OSX Full Install Guide
This guide is intended for people who wish to use an existing or new ProjectTox-Core installation separate to the bundled installation with qTox, if you do not wish to use a separate installation you can skip to the section titled 'Final Steps'.
Installation on OSX, isn't quite straight forward, here is a quick guide on how to install;
Note that qTox now requires OpenCV and OpenAL for video and audio.
The first thing you need to do is install ProjectTox-Core with a/v support. Refer to the INSTALL guide in the PrjectTox-Core github repo.
Next you need to download QtTools (http://qt-project.org/downloads), at the time of writing this is at version .3.0.
Make sure you deselect all the unnecessary components from the 5.3 checkbox (iOS/Android libs) otherwise you will end up with a very large download.
Once that is installed you will most likely need to set the path for qmake. To do this, open up terminal and paste in the following;
```bash
export PATH=/location/to/qmake/binary:$PATH
```
For myself, the qmake binary was located in /Users/mouseym/Qt/5.3/clang_64/bin/.
This is not a permanent change, it will revert when you close the terminal window, to add it permanently you will need to add echo the above line to your .profile/.bash_profile.
Once this is installed, do the following;
```bash
git clone https://github.com/tux3/qTox
cd toxgui
qmake
```
Now, we need to create a symlink to /usr/local/lib/ and /usr/local/include/
```
mkdir -p $HOME/qTox/libs
sudo ln -s /usr/local/lib $HOME/qTox/libs/lib
sudo ln -s /usr/local/include $HOME/qTox/libs/include
```
####Final Steps
The final step is to run
```bash
make
```
in the qTox directory, or if you are using the bundled tox core installation, you can use
```bash
./bootstrap.sh
make
```
Assuming all went well you should now have a qTox.app file within the directory. Double click and it should open!
Copy the dll "OpenAL32.dll" located at "C:\qTox\libs\openal-build\install\bin" to "C:\qTox\libs\lib". Finally, copy the directory "AL" located at "C:\qTox\libs\openal-build\install\include" to "C:\qTox\libs\include". Unlike OpenCV you don't need to patch any files. Feel free to delete the directories "openal-soft-x.y.z" and "openal-build", but you don't need to.

View File

@ -1,27 +1,28 @@
qTox
======
Powerful Tox client that tries to follow the Tox UI mockup while running on all major systems. <br/>
This GUI uses code from @nurupo'tos ProjectTox-Qt-GUI, in particular the "Core" Toxcore wrapper. <br/>
However, it is not a fork.
qTox is a powerful Tox client that tries to follow the Tox design guidelines while running on all major platforms. <br/>
<h2>Features</h2>
- One to one chat with friends
- Group chats
- File transfers, with previewing of images
- Audio calls
- Audio calls, including group calls
- Video calls
- Tox DNS
- Tox DNS and Tox URI support
- Translations in various languages
- Avatars
- Auto-updates on Windows and Mac, packages on Linux
- And many more options!
<h2>Downloads</h2>
This client runs on Windows, Linux and Mac natively.<br/>
<a href="https://github.com/tux3/qTox/releases/latest">Windows download</a><br/>
<a href="https://dist-build.tox.im/qtox.dmg">Mac download </a><br/>
<a href="http://207.12.89.155:8080/job/qTox-win64-nsis/lastSuccessfulBuild/artifact/setup-qtox64.exe">Windows 64 bit download</a><br/>
<a href="http://207.12.89.155:8080/job/qTox-win32-nsis/lastSuccessfulBuild/artifact/setup-qtox32.exe">Windows 32 bit download (for older hardware)</a><br/>
<a href="https://dist-build.tox.im/qtox.dmg">Mac OS X download </a><br/>
<a href="https://jenkins.libtoxcore.so/job/qTox-linux-amd64/lastSuccessfulBuild/artifact/qt/qtox.xz">Linux binary download</a><br/>
<a href="https://jenkins.libtoxcore.so/user/tux3/my-views/view/qTox/job/qTox-Linux-pkg/lastSuccessfulBuild/artifact/">Linux packages</a><br/>
@ -33,3 +34,9 @@ This client runs on Windows, Linux and Mac natively.<br/>
##Documentation:
[Compiling](/INSTALL.md)
##Developer overview:
[GitStats](http://207.12.89.155/index.html)<br/>
[Mac & Linux jenkins](https://jenkins.libtoxcore.so/user/tux3/my-views/view/qTox/)<br/>
[Windows jenkins](http://207.12.89.155:8080)<br/>

View File

@ -15,6 +15,7 @@ SODIUM_VER=1.0.0
# directory names of cloned repositories
SODIUM_DIR=libsodium-$SODIUM_VER
TOX_CORE_DIR=libtoxcore-latest
FILTER_AUDIO_DIR=filter_audio
# this boolean describes whether the installation of
# libsodium should be skipped or not
@ -42,6 +43,11 @@ if [ -z "$TOX_CORE_DIR" ]; then
exit 1
fi
if [ -z "$FILTER_AUDIO_DIR" ]; then
echo "internal error detected!"
echo "FILTER_AUDIO_DIR should not be empty... aborting"
exit 1
fi
########## check input parameters ##########
@ -95,7 +101,7 @@ mkdir -p ${BASE_DIR}
# if exists, otherwise cloning them may fail
rm -rf ${BASE_DIR}/${SODIUM_DIR}
rm -rf ${BASE_DIR}/${TOX_CORE_DIR}
rm -rf ${BASE_DIR}/${FILTER_AUDIO_DIR}
############### install step ###############
@ -122,6 +128,12 @@ if [[ $TOX_ONLY = "false" ]]; then
fi
popd
if [[ $GLOBAL = "false" ]]; then
./install_libfilteraudio.sh ${BASE_DIR}/${FILTER_AUDIO_DIR} ${BASE_DIR}
else
./install_libfilteraudio.sh ${BASE_DIR}/${FILTER_AUDIO_DIR}
fi
fi
# clone current master of libtoxcore

BIN
img/group_button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

BIN
img/icons/qtox_profile.icns Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

40
install_libfilteraudio.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/sh
if [ -z $1 ]; then
SOURCE_DIR="filter_audio/"
else
SOURCE_DIR="$1/"
fi
if [ -z "$2" ]; then
LIB_DIR="/usr/local/lib/"
INCLUDE_DIR="/usr/local/include/"
else
LIB_DIR="$2/lib/"
INCLUDE_DIR="$2/include/"
fi
echo "Cloning filter_audio from GitHub.com"
git clone https://github.com/irungentoo/filter_audio.git $SOURCE_DIR
echo "Compiling filter_audio"
cd $SOURCE_DIR
gcc -c -fPIC filter_audio.c aec/*.c agc/*.c ns/*.c other/*.c -lm -lpthread
echo "Creating shared object file"
gcc *.o -shared -o libfilteraudio.so
echo "Cleaning up"
rm *.o
muhcmd="cp libfilteraudio.so $LIB_DIR"
[ -z "$2" ] && muhcmd="sudo $muhcmd"
echo "Installing libfilteraudio.so with $muhcmd"
$muhcmd
muhcmd="cp *.h $INCLUDE_DIR"
[ -z "$2" ] && muhcmd="sudo $muhcmd"
echo "Installing include files with $muhcmd"
$muhcmd
echo "Finished."

154
osx/gplv3.rtf Normal file
View File

@ -0,0 +1,154 @@
{\rtf1\ansi\deff0\adeflang1025
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset0 Times New Roman;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\fnil\fprq2\fcharset0 Arial Unicode MS;}{\f4\fnil\fprq2\fcharset0 MS Mincho;}{\f5\fnil\fprq2\fcharset0 Tahoma;}{\f6\fnil\fprq0\fcharset0 Tahoma;}}
{\colortbl;\red0\green0\blue0;\red128\green128\blue128;}
{\stylesheet{\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\snext1 Normal;}
{\s2\sb240\sa120\keepn\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\afs28\lang255\ltrch\dbch\af4\langfe255\hich\f2\fs28\lang1035\loch\f2\fs28\lang1035\sbasedon1\snext3 Heading;}
{\s3\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon1\snext3 Body Text;}
{\s4\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon3\snext4 List;}
{\s5\sb120\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ai\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\i\loch\f0\fs24\lang1035\i\sbasedon1\snext5 caption;}
{\s6\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon1\snext6 Index;}
}
{\info{\author Kimmo Varis}{\creatim\yr2010\mo1\dy17\hr1\min15}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}{\comment StarWriter}{\vern3100}}\deftab709
{\*\pgdsctbl
{\pgdsc0\pgdscuse195\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Standard;}}
\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc
\pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 GNU GENERAL PUBLIC LICENSE\line Version 3, 29 June 2007\line \line Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\line Everyone is permitted to copy and distribute verbatim copies\line of this license document, but
changing it is not allowed.\line \line Preamble\line \line The GNU General Public License is a free, copyleft license for\line software and other kinds of works.\line \line The licenses for most software and other practical works are designed\line to take away yo
ur freedom to share and change the works. By contrast,\line the GNU General Public License is intended to guarantee your freedom to\line share and change all versions of a program--to make sure it remains free\line software for all its users. We, the Free Software Foun
dation, use the\line GNU General Public License for most of our software; it applies also to\line any other work released this way by its authors. You can apply it to\line your programs, too.\line \line When we speak of free software, we are referring to freedom, not\line price. Ou
r General Public Licenses are designed to make sure that you\line have the freedom to distribute copies of free software (and charge for\line them if you wish), that you receive source code or can get it if you\line want it, that you can change the software or use pieces
of it in new\line free programs, and that you know you can do these things.\line \line To protect your rights, we need to prevent others from denying you\line these rights or asking you to surrender the rights. Therefore, you have\line certain responsibilities if you distribut
e copies of the software, or if\line you modify it: responsibilities to respect the freedom of others.\line \line For example, if you distribute copies of such a program, whether\line gratis or for a fee, you must pass on to the recipients the same\line freedoms that you receive
d. You must make sure that they, too, receive\line or can get the source code. And you must show them these terms so they\line know their rights.\line \line Developers that use the GNU GPL protect your rights with two steps:\line (1) assert copyright on the software, and (2) o
ffer you this License\line giving you legal permission to copy, distribute and/or modify it.\line \line For the developers' and authors' protection, the GPL clearly explains\line that there is no warranty for this free software. For both users' and\line authors' sake, the GPL r
equires that modified versions be marked as\line changed, so that their problems will not be attributed erroneously to\line authors of previous versions.\line \line Some devices are designed to deny users access to install or run\line modified versions of the software inside the
m, although the manufacturer\line can do so. This is fundamentally incompatible with the aim of\line protecting users' freedom to change the software. The systematic\line pattern of such abuse occurs in the area of products for individuals to\line use, which is precisely wh
ere it is most unacceptable. Therefore, we\line have designed this version of the GPL to prohibit the practice for those\line products. If such problems arise substantially in other domains, we\line stand ready to extend this provision to those domains in future versio
ns\line of the GPL, as needed to protect the freedom of users.\line \line Finally, every program is threatened constantly by software patents.\line States should not allow patents to restrict development and use of\line software on general-purpose computers, but in those that do
, we wish to\line avoid the special danger that patents applied to a free program could\line make it effectively proprietary. To prevent this, the GPL assures that\line patents cannot be used to render the program non-free.\line \line The precise terms and conditions for copyin
g, distribution and\line modification follow.\line \line TERMS AND CONDITIONS\line \line 0. Definitions.\line \line "This License" refers to version 3 of the GNU General Public License.\line \line "Copyright" also means copyright-like laws that apply to other kinds of\line wor
ks, such as semiconductor masks.\line \line "The Program" refers to any copyrightable work licensed under this\line License. Each licensee is addressed as "you". "Licensees" and\line "recipients" may be individuals or organizations.\line \line To "modify" a work means to copy fro
m or adapt all or part of the work\line in a fashion requiring copyright permission, other than the making of an\line exact copy. The resulting work is called a "modified version" of the\line earlier work or a work "based on" the earlier work.\line \line A "covered work" means
either the unmodified Program or a work based\line on the Program.\line \line To "propagate" a work means to do anything with it that, without\line permission, would make you directly or secondarily liable for\line infringement under applicable copyright law, except executing it
on a\line computer or modifying a private copy. Propagation includes copying,\line distribution (with or without modification), making available to the\line public, and in some countries other activities as well.\line \line To "convey" a work means any kind of propagation that
enables other\line parties to make or receive copies. Mere interaction with a user through\line a computer network, with no transfer of a copy, is not conveying.\line \line An interactive user interface displays "Appropriate Legal Notices"\line to the extent that it includes a
convenient and prominently visible\line feature that (1) displays an appropriate copyright notice, and (2)\line tells the user that there is no warranty for the work (except to the\line extent that warranties are provided), that licensees may convey the\line work under this
License, and how to view a copy of this License. If\line the interface presents a list of user commands or options, such as a\line menu, a prominent item in the list meets this criterion.\line \line 1. Source Code.\line \line The "source code" for a work means the preferred form o
f the work\line for making modifications to it. "Object code" means any non-source\line form of a work.\line \line A "Standard Interface" means an interface that either is an official\line standard defined by a recognized standards body, or, in the case of\line interfaces specified
for a particular programming language, one that\line is widely used among developers working in that language.\line \line The "System Libraries" of an executable work include anything, other\line than the work as a whole, that (a) is included in the normal form of\line packaging
a Major Component, but which is not part of that Major\line Component, and (b) serves only to enable use of the work with that\line Major Component, or to implement a Standard Interface for which an\line implementation is available to the public in source code form. A\line
"Major Component", in this context, means a major essential component\line (kernel, window system, and so on) of the specific operating system\line (if any) on which the executable work runs, or a compiler used to\line produce the work, or an object code interpreter used
to run it.\line \line The "Corresponding Source" for a work in object code form means all\line the source code needed to generate, install, and (for an executable\line work) run the object code and to modify the work, including scripts to\line control those activities. However
, it does not include the work's\line System Libraries, or general-purpose tools or generally available free\line programs which are used unmodified in performing those activities but\line which are not part of the work. For example, Corresponding Source\line includes interf
ace definition files associated with source files for\line the work, and the source code for shared libraries and dynamically\line linked subprograms that the work is specifically designed to require,\line such as by intimate data communication or control flow between th
ose\line subprograms and other parts of the work.\line \line The Corresponding Source need not include anything that users\line can regenerate automatically from other parts of the Corresponding\line Source.\line \line The Corresponding Source for a work in source code form is that\line same
work.\line \line 2. Basic Permissions.\line \line All rights granted under this License are granted for the term of\line copyright on the Program, and are irrevocable provided the stated\line conditions are met. This License explicitly affirms your unlimited\line permission to run the
unmodified Program. The output from running a\line covered work is covered by this License only if the output, given its\line content, constitutes a covered work. This License acknowledges your\line rights of fair use or other equivalent, as provided by copyright law.
\line \line You may make, run and propagate covered works that you do not\line convey, without conditions so long as your license otherwise remains\line in force. You may convey covered works to others for the sole purpose\line of having them make modifications exclusively for
you, or provide you\line with facilities for running those works, provided that you comply with\line the terms of this License in conveying all material for which you do\line not control copyright. Those thus making or running the covered works\line for you must do so exclus
ively on your behalf, under your direction\line and control, on terms that prohibit them from making any copies of\line your copyrighted material outside their relationship with you.\line \line Conveying under any other circumstances is permitted solely under\line the conditions
stated below. Sublicensing is not allowed; section 10\line makes it unnecessary.\line \line 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\line \line No covered work shall be deemed part of an effective technological\line measure under any applicable law fulfillin
g obligations under article\line 11 of the WIPO copyright treaty adopted on 20 December 1996, or\line similar laws prohibiting or restricting circumvention of such\line measures.\line \line When you convey a covered work, you waive any legal power to forbid\line circumvention of tech
nological measures to the extent such circumvention\line is effected by exercising rights under this License with respect to\line the covered work, and you disclaim any intention to limit operation or\line modification of the work as a means of enforcing, against the wor
k's\line users, your or third parties' legal rights to forbid circumvention of\line technological measures.\line \line 4. Conveying Verbatim Copies.\line \line You may convey verbatim copies of the Program's}
\par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 source code as you\line receive it, in any medium, provided that you conspicuously and\line appropriately publish on each copy an appropriate copyright notice;\line keep intact all notices stating that this License and any\line non-permissive terms added in accord with secti
on 7 apply to the code;\line keep intact all notices of the absence of any warranty; and give all\line recipients a copy of this License along with the Program.\line \line You may charge any price or no price for each copy that you convey,\line and you may offer support or warra
nty protection for a fee.\line \line 5. Conveying Modified Source Versions.\line \line You may convey a work based on the Program, or the modifications to\line produce it from the Program, in the form of source code under the\line terms of section 4, provided that you also meet all
of these conditions:\line \line a) The work must carry prominent notices stating that you modified\line it, and giving a relevant date.\line \line b) The work must carry prominent notices stating that it is\line released under this License and any conditions added unde
r section\line 7. This requirement modifies the requirement in section 4 to\line "keep intact all notices".\line \line c) You must license the entire work, as a whole, under this\line License to anyone who comes into possession of a copy. This\line License will th
erefore apply, along with any applicable section 7\line additional terms, to the whole of the work, and all its parts,\line regardless of how they are packaged. This License gives no\line permission to license the work in any other way, but it does not\line i
nvalidate such permission if you have separately received it.\line \line d) If the work has interactive user interfaces, each must display\line Appropriate Legal Notices; however, if the Program has interactive\line interfaces that do not display Appropriate Legal
Notices, your\line work need not make them do so.\line \line A compilation of a covered work with other separate and independent\line works, which are not by their nature extensions of the covered work,\line and which are not combined with it such as to form a larger progra
m,\line in or on a volume of a storage or distribution medium, is called an\line "aggregate" if the compilation and its resulting copyright are not\line used to limit the access or legal rights of the compilation's users\line beyond what the individual works permit. Inclusio
n of a covered work\line in an aggregate does not cause this License to apply to the other\line parts of the aggregate.\line \line 6. Conveying Non-Source Forms.\line \line You may convey a covered work in object code form under the terms\line of sections 4 and 5, provided that you also
convey the\line machine-readable Corresponding Source under the terms of this License,\line in one of these ways:\line \line a) Convey the object code in, or embodied in, a physical product\line (including a physical distribution medium), accompanied by the\line Correspond
ing Source fixed on a durable physical medium\line customarily used for software interchange.\line \line b) Convey the object code in, or embodied in, a physical product\line (including a physical distribution medium), accompanied by a\line written offer, valid for
at least three years and valid for as\line long as you offer spare parts or customer support for that product\line model, to give anyone who possesses the object code either (1) a\line copy of the Corresponding Source for all the software in the\line product
that is covered by this License, on a durable physical\line medium customarily used for software interchange, for a price no\line more than your reasonable cost of physically performing this\line conveying of source, or (2) access to copy the\line Correspondin
g Source from a network server at no charge.\line \line c) Convey individual copies of the object code with a copy of the\line written offer to provide the Corresponding Source. This\line alternative is allowed only occasionally and noncommercially, and\line only
if you received the object code with such an offer, in accord\line with subsection 6b.\line \line d) Convey the object code by offering access from a designated\line place (gratis or for a charge), and offer equivalent access to the\line Corresponding Source in the
same way through the same place at no\line further charge. You need not require recipients to copy the\line Corresponding Source along with the object code. If the place to\line copy the object code is a network server, the Corresponding Source\line may be
on a different server (operated by you or a third party)\line that supports equivalent copying facilities, provided you maintain\line clear directions next to the object code saying where to find the\line Corresponding Source. Regardless of what server hosts
the\line Corresponding Source, you remain obligated to ensure that it is\line available for as long as needed to satisfy these requirements.\line \line e) Convey the object code using peer-to-peer transmission, provided\line you inform other peers where the object
code and Corresponding\line Source of the work are being offered to the general public at no\line charge under subsection 6d.\line \line A separable portion of the object code, whose source code is excluded\line from the Corresponding Source as a System Library, need no
t be\line included in conveying the object code work.\line \line A "User Product" is either (1) a "consumer product", which means any\line tangible personal property which is normally used for personal, family,\line or household purposes, or (2) anything designed or sold for inc
orporation\line into a dwelling. In determining whether a product is a consumer product,\line doubtful cases shall be resolved in favor of coverage. For a particular\line product received by a particular user, "normally used" refers to a\line typical or common use of that c
lass of product, regardless of the status\line of the particular user or of the way in which the particular user\line actually uses, or expects or is expected to use, the product. A product\line is a consumer product regardless of whether the product has substantial\line com
mercial, industrial or non-consumer uses, unless such uses represent\line the only significant mode of use of the product.\line \line "Installation Information" for a User Product means any methods,\line procedures, authorization keys, or other information required to insta
ll\line and execute modified versions of a covered work in that User Product from\line a modified version of its Corresponding Source. The information must\line suffice to ensure that the continued functioning of the modified object\line code is in no case prevented or inter
fered with solely because\line modification has been made.\line \line If you convey an object code work under this section in, or with, or\line specifically for use in, a User Product, and the conveying occurs as\line part of a transaction in which the right of possession and us
e of the\line User Product is transferred to the recipient in perpetuity or for a\line fixed term (regardless of how the transaction is characterized), the\line Corresponding Source conveyed under this section must be accompanied\line by the Installation Information. But thi
s requirement does not apply\line if neither you nor any third party retains the ability to install\line modified object code on the User Product (for example, the work has\line been installed in ROM).\line \line The requirement to provide Installation Information does not inclu
de a\line requirement to continue to provide support service, warranty, or updates\line for a work that has been modified or installed by the recipient, or for\line the User Product in which it has been modified or installed. Access to a\line network may be denied when the m
odification itself materially and\line adversely affects the operation of the network or violates the rules and\line protocols for communication across the network.\line \line Corresponding Source conveyed, and Installation Information provided,\line in accord with this section
must be in a format that is publicly\line documented (and with an implementation available to the public in\line source code form), and must require no special password or key for\line unpacking, reading or copying.\line \line 7. Additional Terms.\line \line "Additional permissions" are
terms that supplement the terms of this\line License by making exceptions from one or more of its conditions.\line Additional permissions that are applicable to the entire Program shall\line be treated as though they were included in this License, to the extent\line that the
y are valid under applicable law. If additional permissions\line apply only to part of the Program, that part may be used separately\line under those permissions, but the entire Program remains governed by\line this License without regard to the additional permissions.\line
\line When you convey a copy of a covered work, you may at your option\line remove any additional permissions from that copy, or from any part of\line it. (Additional permissions may be written to require their own\line removal in certain cases when you modify the work.)
You may place\line additional permissions on material, added by you to a covered work,\line for which you have or can give appropriate copyright permission.\line \line Notwithstanding any other provision of this License, for material you\line add to a covered work, you may (if a
uthorized by the copyright holders of\line that material) supplement the terms of this License with terms:\line \line a) Disclaiming warranty or limiting liability differently from the\line terms of sections 15 and 16 of this License; or\line \line b) Requiring preservation
of specified reasonable legal notices or\line author attributions in that material or in the Appropriate Legal\line Notices displayed by works containing it; or\line \line c) Prohibiting misrepresentation of the origin of that material, or\line requiring that modi
fied versions of such material be marked in\line reasonable ways as different from the original version; or\line \line d) Limiting the use for publicity purposes of names of licensors or\line authors of the material; or\line \line e) Declining to grant rights under trad
emark law for use of some\line trade names, trademarks, or service marks; or\line \line f) Requiring indemnification of licensors and authors of that\line material by anyone who conveys}
\par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 the material (or modified versions of\line it) with contractual assumptions of liability to the recipient, for\line any liability that these contractual assumptions directly impose on\line those licensors and authors.\line \line All other non-permissive additional t
erms are considered "further\line restrictions" within the meaning of section 10. If the Program as you\line received it, or any part of it, contains a notice stating that it is\line governed by this License along with a term that is a further\line restriction, you may remov
e that term. If a license document contains\line a further restriction but permits relicensing or conveying under this\line License, you may add to a covered work material governed by the terms\line of that license document, provided that the further restriction does\line no
t survive such relicensing or conveying.\line \line If you add terms to a covered work in accord with this section, you\line must place, in the relevant source files, a statement of the\line additional terms that apply to those files, or a notice indicating\line where to find th
e applicable terms.\line \line Additional terms, permissive or non-permissive, may be stated in the\line form of a separately written license, or stated as exceptions;\line the above requirements apply either way.\line \line 8. Termination.\line \line You may not propagate or modify a cove
red work except as expressly\line provided under this License. Any attempt otherwise to propagate or\line modify it is void, and will automatically terminate your rights under\line this License (including any patent licenses granted under the third\line paragraph of section
11).\line \line However, if you cease all violation of this License, then your\line license from a particular copyright holder is reinstated (a)\line provisionally, unless and until the copyright holder explicitly and\line finally terminates your license, and (b) permanently, if
the copyright\line holder fails to notify you of the violation by some reasonable means\line prior to 60 days after the cessation.\line \line Moreover, your license from a particular copyright holder is\line reinstated permanently if the copyright holder notifies you of the\line vio
lation by some reasonable means, this is the first time you have\line received notice of violation of this License (for any work) from that\line copyright holder, and you cure the violation prior to 30 days after\line your receipt of the notice.\line \line Termination of your ri
ghts under this section does not terminate the\line licenses of parties who have received copies or rights from you under\line this License. If your rights have been terminated and not permanently\line reinstated, you do not qualify to receive new licenses for the same\line
material under section 10.\line \line 9. Acceptance Not Required for Having Copies.\line \line You are not required to accept this License in order to receive or\line run a copy of the Program. Ancillary propagation of a covered work\line occurring solely as a consequence of using
peer-to-peer transmission\line to receive a copy likewise does not require acceptance. However,\line nothing other than this License grants you permission to propagate or\line modify any covered work. These actions infringe copyright if you do\line not accept this License.
Therefore, by modifying or propagating a\line covered work, you indicate your acceptance of this License to do so.\line \line 10. Automatic Licensing of Downstream Recipients.\line \line Each time you convey a covered work, the recipient automatically\line receives a license from
the original licensors, to run, modify and\line propagate that work, subject to this License. You are not responsible\line for enforcing compliance by third parties with this License.\line \line An "entity transaction" is a transaction transferring control of an\line organizat
ion, or substantially all assets of one, or subdividing an\line organization, or merging organizations. If propagation of a covered\line work results from an entity transaction, each party to that\line transaction who receives a copy of the work also receives whatever\line l
icenses to the work the party's predecessor in interest had or could\line give under the previous paragraph, plus a right to possession of the\line Corresponding Source of the work from the predecessor in interest, if\line the predecessor has it or can get it with reason
able efforts.\line \line You may not impose any further restrictions on the exercise of the\line rights granted or affirmed under this License. For example, you may\line not impose a license fee, royalty, or other charge for exercise of\line rights granted under this License, a
nd you may not initiate litigation\line (including a cross-claim or counterclaim in a lawsuit) alleging that\line any patent claim is infringed by making, using, selling, offering for\line sale, or importing the Program or any portion of it.\line \line 11. Patents.\line \line A "contrib
utor" is a copyright holder who authorizes use under this\line License of the Program or a work on which the Program is based. The\line work thus licensed is called the contributor's "contributor version".\line \line A contributor's "essential patent claims" are all patent
claims\line owned or controlled by the contributor, whether already acquired or\line hereafter acquired, that would be infringed by some manner, permitted\line by this License, of making, using, or selling its contributor version,\line but do not include claims that would be
infringed only as a\line consequence of further modification of the contributor version. For\line purposes of this definition, "control" includes the right to grant\line patent sublicenses in a manner consistent with the requirements of\line this License.\line \line Each contributo
r grants you a non-exclusive, worldwide, royalty-free\line patent license under the contributor's essential patent claims, to\line make, use, sell, offer for sale, import and otherwise run, modify and\line propagate the contents of its contributor version.\line \line In the foll
owing three paragraphs, a "patent license" is any express\line agreement or commitment, however denominated, not to enforce a patent\line (such as an express permission to practice a patent or covenant not to\line sue for patent infringement). To "grant" such a patent l
icense to a\line party means to make such an agreement or commitment not to enforce a\line patent against the party.\line \line If you convey a covered work, knowingly relying on a patent license,\line and the Corresponding Source of the work is not available for anyone\line to copy,
free of charge and under the terms of this License, through a\line publicly available network server or other readily accessible means,\line then you must either (1) cause the Corresponding Source to be so\line available, or (2) arrange to deprive yourself of the benefi
t of the\line patent license for this particular work, or (3) arrange, in a manner\line consistent with the requirements of this License, to extend the patent\line license to downstream recipients. "Knowingly relying" means you have\line actual knowledge that, but for the pa
tent license, your conveying the\line covered work in a country, or your recipient's use of the covered work\line in a country, would infringe one or more identifiable patents in that\line country that you have reason to believe are valid.\line \line If, pursuant to or in connec
tion with a single transaction or\line arrangement, you convey, or propagate by procuring conveyance of, a\line covered work, and grant a patent license to some of the parties\line receiving the covered work authorizing them to use, propagate, modify\line or convey a specific
copy of the covered work, then the patent license\line you grant is automatically extended to all recipients of the covered\line work and works based on it.\line \line A patent license is "discriminatory" if it does not include within\line the scope of its coverage, prohibits t
he exercise of, or is\line conditioned on the non-exercise of one or more of the rights that are\line specifically granted under this License. You may not convey a covered\line work if you are a party to an arrangement with a third party that is\line in the business of distr
ibuting software, under which you make payment\line to the third party based on the extent of your activity of conveying\line the work, and under which the third party grants, to any of the\line parties who would receive the covered work from you, a discriminatory\line patent
license (a) in connection with copies of the covered work\line conveyed by you (or copies made from those copies), or (b) primarily\line for and in connection with specific products or compilations that\line contain the covered work, unless you entered into that arrange
ment,\line or that patent license was granted, prior to 28 March 2007.\line \line Nothing in this License shall be construed as excluding or limiting\line any implied license or other defenses to infringement that may\line otherwise be available to you under applicable patent la
w.\line \line 12. No Surrender of Others' Freedom.\line \line If conditions are imposed on you (whether by court order, agreement or\line otherwise) that contradict the conditions of this License, they do not\line excuse you from the conditions of this License. If you cannot conve
y a\line covered work so as to satisfy simultaneously your obligations under this\line License and any other pertinent obligations, then as a consequence you may\line not convey it at all. For example, if you agree to terms that obligate you\line to collect a royalty for fur
ther conveying from those to whom you convey\line the Program, the only way you could satisfy both those terms and this\line License would be to refrain entirely from conveying the Program.\line \line 13. Use with the GNU Affero General Public License.\line \line Notwithstanding an
y other provision of this License, you have\line permission to link or combine any covered work with a work licensed\line under version 3 of the GNU Affero General Public License into a single\line combined work, and to convey the resulting work. The terms of this\line Licen
se will continue to apply to the part which is the covered work,\line but the special requirements of the GNU Affero General Public License,\line section 13, concerning interaction through a network will apply to the\line combination as such.\line \line 14. Revised Versions of t
his License.\line \line The Free Software Foundation may publish revised and/or new versions of\line the GNU General Public License from time to time. Such new versions will\line be similar}
\par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 in spirit to the present version, but may differ in detail to\line address new problems or concerns.\line \line Each version is given a distinguishing version number. If the\line Program specifies that a certain numbered version of the GNU General\line Public License "or any l
ater version" applies to it, you have the\line option of following the terms and conditions either of that numbered\line version or of any later version published by the Free Software\line Foundation. If the Program does not specify a version number of the\line GNU General P
ublic License, you may choose any version ever published\line by the Free Software Foundation.\line \line If the Program specifies that a proxy can decide which future\line versions of the GNU General Public License can be used, that proxy's\line public statement of acceptance o
f a version permanently authorizes you\line to choose that version for the Program.\line \line Later license versions may give you additional or different\line permissions. However, no additional obligations are imposed on any\line author or copyright holder as a result of your
choosing to follow a\line later version.\line \line 15. Disclaimer of Warranty.\line \line THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\line APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\line HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
"AS IS" WITHOUT WARRANTY\line OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\line THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\line PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\line IS WITH YOU.
SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\line ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\line \line 16. Limitation of Liability.\line \line IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\line WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PAR
TY WHO MODIFIES AND/OR CONVEYS\line THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\line GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\line USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\line DA
TA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\line PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\line EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\line SUCH DAMAGES.\line \line 17. Interpretation
of Sections 15 and 16.\line \line If the disclaimer of warranty and limitation of liability provided\line above cannot be given local legal effect according to their terms,\line reviewing courts shall apply local law that most closely approximates\line an absolute waiver of all
civil liability in connection with the\line Program, unless a warranty or assumption of liability accompanies a\line copy of the Program in return for a fee.\line \line END OF TERMS AND CONDITIONS\line \line How to Apply These Terms to Your New Programs
\line \line If you develop a new program, and you want it to be of the greatest\line possible use to the public, the best way to achieve this is to make it\line free software which everyone can redistribute and change under these terms.\line \line To do so, attach the following not
ices to the program. It is safest\line to attach them to the start of each source file to most effectively\line state the exclusion of warranty; and each file should have at least\line the "copyright" line and a pointer to where the full notice is found.\line \line <one line
to give the program's name and a brief idea of what it does.>\line Copyright (C) <year> <name of author>\line \line This program is free software: you can redistribute it and/or modify\line it under the terms of the GNU General Public License as published by\line
the Free Software Foundation, either version 3 of the License, or\line (at your option) any later version.\line \line This program is distributed in the hope that it will be useful,\line but WITHOUT ANY WARRANTY; without even the implied warranty of\line MERCHANTAB
ILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\line GNU General Public License for more details.\line \line You should have received a copy of the GNU General Public License\line along with this program. If not, see <http://www.gnu.org/licenses/>.\line \line Also add
information on how to contact you by electronic and paper mail.\line \line If the program does terminal interaction, make it output a short\line notice like this when it starts in an interactive mode:\line \line <program> Copyright (C) <year> <name of author>\line This prog
ram comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\line This is free software, and you are welcome to redistribute it\line under certain conditions; type `show c' for details.\line \line The hypothetical commands `show w' and `show c' should show the ap
propriate\line parts of the General Public License. Of course, your program's commands\line might be different; for a GUI interface, you would use an "about box".\line \line You should also get your employer (if you work as a programmer) or school,\line if any, to sign a "copyr
ight disclaimer" for the program, if necessary.\line For more information on this, and how to apply and follow the GNU GPL, see\line <http://www.gnu.org/licenses/>.\line \line The GNU General Public License does not permit incorporating your program\line into proprietary program
s. If your program is a subroutine library, you\line may consider it more useful to permit linking proprietary applications with\line the library. If this is what you want to do, use the GNU Lesser General\line Public License instead of this License. But first, please
read\line <http://www.gnu.org/philosophy/why-not-lgpl.html>.\line }
\par }

102
osx/info.plist Normal file
View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleIconFile</key>
<string>qtox.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>qtox</string>
<key>CFBundleDisplayName</key>
<string>qTox</string>
<key>CFBundleName</key>
<string>qTox</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0-EXPERIMENTIAL</string>
<key>CFBundleIdentifier</key>
<string>im.tox.qtox</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Tox URL</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tox</string>
</array>
<key>CFBundleURLIconFile</key>
<string>qtox_profile.icns</string>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tox</string>
</array>
<key>CFBundleTypeName</key>
<string>Tox profile</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeIconFile</key>
<string>qtox_profile.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>application/x-tox.profile</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>public.tox</string>
</array>
</dict>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>public.tox</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>com.apple.ostype</key>
<string>TOX</string>
<key>public.filename-extension</key>
<array>
<string>tox</string>
</array>
<key>public.mime-type</key>
<string>tox/x-profile</string>
</dict>
</dict>
</array>
<key>CFBundleLocalizations</key>
<array>
<string>en_US</string>
<string>en</string>
<string>bg_BG</string>
<string>de_DE</string>
<string>fi_FI</string>
<string>fr_FR</string>
<string>it_IT</string>
<string>pl_PL</string>
<string>ru_RU</string>
<string>uk_UA</string>
<string>sv</string>
</array>
</dict>
</plist>

27
osx/makedist.sh Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
PWD=`pwd`
echo "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>
<installer-gui-script minSpecVersion=\"1\">
<pkg-ref id=\"im.tox.qtox\"/>
<options hostArchitectures=\"x86_64\"/>
<title>qTox</title>
<license file=\"$PWD/gplv3.rtf\"/>
<welcome file=\"$PWD/welcome.txt\"/>
<domains enable_currentUserHome=\"true\" enable_localSystem=\"false\" enable_anywhere=\"false\"/>
<options customize=\"never\" require-scripts=\"true\"/>
<choices-outline>
<line choice=\"default\">
<line choice=\"im.tox.qtox\"/>
</line>
</choices-outline>
<allowed-os-versions>
<os-version min=\"10.7\"/>
</allowed-os-versions>
<choice id=\"default\"/>
<choice id=\"im.tox.qtox\" visible=\"false\">
<pkg-ref id=\"im.tox.qtox\"/>
</choice>
<pkg-ref id=\"im.tox.qtox\" version=\"1\" onConclusion=\"none\">qtox.pkg</pkg-ref>
</installer-gui-script>" > distribution.xml

23
osx/updater/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test

13
osx/updater/README.md Normal file
View File

@ -0,0 +1,13 @@
The qTox OS X updater is a mix of objective C and Go compiled as static binaries used do effortless updates in the background.
It uses Objective C to access Apples own security framework and call some long dead APIs in order to give the statically linked go updater permissions to install the latest build without prompting the user for every file.
* Release commits: ``https://github.com/Tox/qTox_updater``
* Development commits: ``https://github.mit.edu/sean-2/updater``
Compiling:
* ```clang qtox_sudo.m -framework corefoundation -framework security -framework cocoa -Os -o qtox_sudo```
* ```go build updater.go```
(Starting with this commit all commits will be signed with [this key](http://pgp.mit.edu/pks/lookup?op=get&search=0x13D2043169D25DF4).)

273
osx/updater/qtox_sudo.m Normal file
View File

@ -0,0 +1,273 @@
// Modifications listed here GPLv3: https://gist.githubusercontent.com/stqism/2e82352026915f8f6ab3/raw/fca6f6f16fb8d61a64b6053dacf50c3433c2e0af/gistfile1.txt
//
// Copyright 2009 Performant Design, LLC. All rights reserved.
// Copyright (C) 2014 Tox Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <http://www.gnu.org/licenses/>.
//
#import <Cocoa/Cocoa.h>
#include <sys/stat.h>
#include <unistd.h>
#include <Security/Authorization.h>
#include <Security/AuthorizationTags.h>
char *addFileToPath(const char *path, const char *filename) {
char *outbuf;
char *lc;
lc = (char *)path + strlen(path) - 1;
if (lc < path || *lc != '/') {
lc = NULL;
}
while (*filename == '/') {
filename++;
}
outbuf = malloc(strlen(path) + strlen(filename) + 1 + (lc == NULL ? 1 : 0));
sprintf(outbuf, "%s%s%s", path, (lc == NULL) ? "/" : "", filename);
return outbuf;
}
int isExecFile(const char *name) {
struct stat s;
return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode));
}
char *which(const char *filename)
{
char *path, *p, *n;
path = getenv("PATH");
if (!path) {
return NULL;
}
p = path = strdup(path);
while (p) {
n = strchr(p, ':');
if (n) {
*n++ = '\0';
}
if (*p != '\0') {
p = addFileToPath(p, filename);
if (isExecFile(p)) {
free(path);
return p;
}
free(p);
}
p = n;
}
free(path);
return NULL;
}
int cocoaSudo(char *executable, char *commandArgs[], char *icon, char *prompt) {
int retVal = 1;
OSStatus status;
AuthorizationRef authRef;
AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights rightSet = {1, &right};
AuthorizationEnvironment myAuthorizationEnvironment;
AuthorizationItem kAuthEnv[2];
myAuthorizationEnvironment.items = kAuthEnv;
AuthorizationFlags flags = kAuthorizationFlagDefaults;
if (prompt && icon) {
kAuthEnv[0].name = kAuthorizationEnvironmentPrompt;
kAuthEnv[0].valueLength = strlen(prompt);
kAuthEnv[0].value = prompt;
kAuthEnv[0].flags = 0;
kAuthEnv[1].name = kAuthorizationEnvironmentIcon;
kAuthEnv[1].valueLength = strlen(icon);
kAuthEnv[1].value = icon;
kAuthEnv[1].flags = 0;
myAuthorizationEnvironment.count = 2;
}
else if (prompt) {
kAuthEnv[0].name = kAuthorizationEnvironmentPrompt;
kAuthEnv[0].valueLength = strlen(prompt);
kAuthEnv[0].value = prompt;
kAuthEnv[0].flags = 0;
myAuthorizationEnvironment.count = 1;
}
else if (icon) {
kAuthEnv[0].name = kAuthorizationEnvironmentIcon;
kAuthEnv[0].valueLength = strlen(icon);
kAuthEnv[0].value = icon;
kAuthEnv[0].flags = 0;
myAuthorizationEnvironment.count = 1;
}
else {
myAuthorizationEnvironment.count = 0;
}
status = AuthorizationCreate(NULL, &myAuthorizationEnvironment, flags, &authRef);
if (status != errAuthorizationSuccess) {
NSLog(@"Could not create authorization reference object.");
status = errAuthorizationBadAddress;
}
else {
flags = kAuthorizationFlagDefaults |
kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize |
kAuthorizationFlagExtendRights;
status = AuthorizationCopyRights(authRef, &rightSet, &myAuthorizationEnvironment, flags, NULL);
}
if (status == errAuthorizationSuccess) {
FILE *ioPipe;
char buffer[1024];
int bytesRead;
flags = kAuthorizationFlagDefaults;
status = AuthorizationExecuteWithPrivileges(authRef, executable, flags, commandArgs, &ioPipe);
/* Just pipe processes' stdout to our stdout for now; hopefully can add stdin pipe later as well */
for (;;) {
bytesRead = fread(buffer, sizeof(char), 1024, ioPipe);
if (bytesRead < 1) {
break;
}
write(STDOUT_FILENO, buffer, bytesRead * sizeof(char));
}
pid_t pid;
int pidStatus;
do {
pid = wait(&pidStatus);
}
while (pid != -1);
if (status == errAuthorizationSuccess) {
retVal = 0;
}
}
else {
AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
authRef = NULL;
if (status != errAuthorizationCanceled) {
// pre-auth failed
NSLog(@"Pre-auth failed");
}
}
return retVal;
}
void usage(char *appNameFull) {
char *appName = strrchr(appNameFull, '/');
if (appName == NULL) {
appName = appNameFull;
}
else {
appName++;
}
fprintf(stderr, "usage: %s command\n", appName);
}
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = 1;
int programArgsStartAt = 1;
char *icon = NULL;
char *prompt = NULL;
if (programArgsStartAt >= argc) {
usage(argv[0]);
}
else {
char *executable;
if (strchr(argv[programArgsStartAt], '/')) {
executable = isExecFile(argv[programArgsStartAt]) ? strdup(argv[programArgsStartAt]) : NULL;
}
else {
executable = which(argv[programArgsStartAt]);
}
if (executable) {
char **commandArgs = malloc((argc - programArgsStartAt) * sizeof(char**));
memcpy(commandArgs, argv + programArgsStartAt + 1, (argc - programArgsStartAt - 1) * sizeof(char**));
commandArgs[argc - programArgsStartAt - 1] = NULL;
retVal = cocoaSudo(executable, commandArgs, icon, prompt);
free(commandArgs);
free(executable);
}
else {
fprintf(stderr, "Unable to find %s\n", argv[programArgsStartAt]);
usage(argv[0]);
}
}
if (prompt) {
free(prompt);
}
[pool release];
return retVal;
}

133
osx/updater/updater.go Normal file
View File

@ -0,0 +1,133 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"os/user"
"syscall"
"bitbucket.org/kardianos/osext"
)
var custom_user string
func fs_type(path string) int {
//name := "FileOrDir"
f, err := os.Open(path)
if err != nil {
fmt.Println(err)
return -1
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
fmt.Println(err)
return -1
}
switch mode := fi.Mode(); {
case mode.IsDir():
return 0
case mode.IsRegular():
return 1
}
return -1
}
func install(path string, pathlen int) int {
files, _ := ioutil.ReadDir(path)
for _, file := range files {
if fs_type(path+file.Name()) == 1 {
addpath := ""
if len(path) != pathlen {
addpath = path[pathlen:len(path)]
}
fmt.Print("Installing: ")
fmt.Println("/Applications/qtox.app/Contents/" + addpath + file.Name())
if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) {
newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+addpath+file.Name())
newfile.Run()
}
cat := exec.Command("/bin/cat", path+file.Name())
auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+addpath+file.Name())
auth.Stdin, _ = cat.StdoutPipe()
auth.Stdout = os.Stdout
auth.Stderr = os.Stderr
_ = auth.Start()
_ = cat.Run()
_ = auth.Wait()
} else {
install(path+file.Name()+"/", pathlen)
}
}
return 0
}
func main() {
syscall.Setuid(0)
usr, e := user.Current()
if e != nil {
log.Fatal(e)
}
CHECK:
if usr.Name != "System Administrator" {
fmt.Println("Not running as root, relaunching")
appdir, _ := osext.Executable()
appdir_len := len(appdir)
sudo_path := appdir[0:(appdir_len-7)] + "qtox_sudo" //qtox_sudo is a fork of cocoasudo with all of its flags and other features stripped out
if _, err := os.Stat(sudo_path); os.IsNotExist(err) {
fmt.Println("Error: No qtox_sudo binary installed, falling back")
custom_user = usr.Name
usr.Name = "System Administrator"
goto CHECK
}
relaunch := exec.Command(sudo_path, appdir, usr.Name)
relaunch.Stdout = os.Stdout
relaunch.Stderr = os.Stderr
relaunch.Run()
return
} else {
if len(os.Args) > 1 || custom_user != "" {
if custom_user == "" {
custom_user = os.Args[1]
}
update_dir := "/Users/" + custom_user + "/Library/Preferences/tox/update/"
if _, err := os.Stat(update_dir); os.IsNotExist(err) {
fmt.Println("Error: No update folder, is check for updates enabled?")
return
}
fmt.Println("qTox Updater")
killqtox := exec.Command("/usr/bin/killall", "qtox")
_ = killqtox.Run()
install(update_dir, len(update_dir))
os.RemoveAll(update_dir)
fmt.Println("Update metadata wiped, launching qTox")
launchqtox := exec.Command("/usr/bin/open", "-b", "im.tox.qtox")
launchqtox.Run()
} else {
fmt.Println("Error: no user passed")
}
}
}

12
osx/welcome.txt Normal file
View File

@ -0,0 +1,12 @@
Welcome to the qTox for OS X internal nightly installer!
Please report all bugs to https://support.libtoxcore.so
########################################################
## WARNING: Install me to your user ONLY
##
## Failure to do so WILL break automatic updating
## and you WILL be left with and old and
## potentially broken qTox install!
##
########################################################

View File

@ -34,7 +34,8 @@ FORMS += \
src/widget/form/loadhistorydialog.ui \
src/widget/form/inputpassworddialog.ui \
src/widget/form/setpassworddialog.ui \
src/chatlog/content/filetransferwidget.ui
src/chatlog/content/filetransferwidget.ui \
src/widget/form/settings/advancedsettings.ui
CONFIG += c++11
@ -47,8 +48,24 @@ RESOURCES += res.qrc
GIT_VERSION = $$system(git rev-parse HEAD 2> /dev/null || echo "built without git")
DEFINES += GIT_VERSION=\"\\\"$$quote($$GIT_VERSION)\\\"\"
# date works on linux/mac, but it would hangs qmake on windows
# This hack returns 0 on batch (windows), but executes "date +%s" or return 0 if it fails on bash (linux/mac)
TIMESTAMP = $$system($1 2>null||echo 0||a;rm null;date +%s||echo 0) # I'm so sorry
DEFINES += TIMESTAMP=$$TIMESTAMP
DEFINES += LOG_TO_FILE
contains(DISABLE_PLATFORM_EXT, YES) {
} else {
DEFINES += QTOX_PLATFORM_EXT
}
contains(DISABLE_FILTER_AUDIO, YES) {
} else {
DEFINES += QTOX_FILTER_AUDIO
}
contains(JENKINS,YES) {
INCLUDEPATH += ./libs/include/
} else {
@ -60,13 +77,15 @@ win32 {
RC_FILE = windows/qtox.rc
LIBS += -liphlpapi -L$$PWD/libs/lib -lsodium -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -lopencv_core248 -lopencv_highgui248 -lopencv_imgproc248 -lOpenAL32 -lopus
LIBS += -lz -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -ljpeg -ltiff -lpng -ljasper -lIlmImf -lHalf -lws2_32
LIBS += -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -ljpeg -ltiff -lpng -ljasper -lIlmImf -lHalf -lws2_32 -lz
} else {
macx {
BUNDLEID = im.tox.qtox
ICON = img/icons/qtox.icns
QMAKE_INFO_PLIST = res/info.plist
QMAKE_INFO_PLIST = osx/info.plist
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui
contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation }
contains(DEFINES, QTOX_FILTER_AUDIO) { LIBS += -lfilteraudio }
} else {
# If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package
contains(STATICPKG, YES) {
@ -74,14 +93,25 @@ win32 {
INSTALLS += target
LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic
LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig
LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0
LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0
} else {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc
}
contains(DEFINES, QTOX_PLATFORM_EXT) {
LIBS += -lX11 -lXss
}
contains(DEFINES, QTOX_FILTER_AUDIO) {
contains(STATICPKG, YES) {
LIBS += -Wl,-Bstatic -lfilteraudio
} else {
LIBS += -lfilteraudio
}
}
contains(JENKINS, YES) {
LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -s
LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a ./libs/lib/libfilteraudio.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s
}
}
}
@ -152,7 +182,9 @@ HEADERS += src/widget/form/addfriendform.h \
src/chatlog/content/filetransferwidget.h \
src/chatlog/chatmessage.h \
src/chatlog/content/image.h \
src/chatlog/customtextdocument.h
src/chatlog/customtextdocument.h \
src/widget/form/settings/advancedform.h \
src/audio.h
SOURCES += \
src/widget/form/addfriendform.cpp \
@ -220,4 +252,19 @@ SOURCES += \
src/chatlog/content/filetransferwidget.cpp \
src/chatlog/chatmessage.cpp \
src/chatlog/content/image.cpp \
src/chatlog/customtextdocument.cpp
src/chatlog/customtextdocument.cpp\
src/widget/form/settings/advancedform.cpp \
src/audio.cpp
contains(DEFINES, QTOX_FILTER_AUDIO) {
HEADERS += src/audiofilterer.h
SOURCES += src/audiofilterer.cpp
}
contains(DEFINES, QTOX_PLATFORM_EXT) {
HEADERS += src/platform/timer.h
SOURCES += src/platform/timer_osx.cpp \
src/platform/timer_win.cpp \
src/platform/timer_x11.cpp
}

312
res.qrc
View File

@ -6,13 +6,28 @@
<file alias="DejaVuSans.ttf">res/DejaVuSans.ttf</file>
</qresource>
<qresource prefix="/">
<file>img/icon.png</file>
<file>audio/notification.pcm</file>
<file>audio/ToxicIncomingCall.pcm</file>
<file>img/add.png</file>
<file>img/avatar_mask.png</file>
<file>img/contact.png</file>
<file>img/contact_dark.png</file>
<file>img/group.png</file>
<file>img/group_2x.png</file>
<file>img/group_button.png</file>
<file>img/group_dark.png</file>
<file>img/icon.png</file>
<file>img/settings.png</file>
<file>img/settings/av.png</file>
<file>img/settings/general.png</file>
<file>img/settings/identity.png</file>
<file>img/settings/privacy.png</file>
<file>img/status/dot_away.png</file>
<file>img/status/dot_away_2x.png</file>
<file>img/status/dot_away_notification.png</file>
<file>img/status/dot_busy.png</file>
<file>img/status/dot_busy_2x.png</file>
<file>img/status/dot_busy_notification.png</file>
<file>img/status/dot_groupchat.png</file>
<file>img/status/dot_groupchat_newmessages.png</file>
<file>img/status/dot_groupchat_notification.png</file>
@ -22,156 +37,15 @@
<file>img/status/dot_online.png</file>
<file>img/status/dot_online_2x.png</file>
<file>img/status/dot_online_notification.png</file>
<file>img/add.png</file>
<file>img/settings.png</file>
<file>img/taskbar/dark/taskbar_online_2x.png</file>
<file>img/taskbar/dark/taskbar_idle_2x.png</file>
<file>img/taskbar/dark/taskbar_busy_2x.png</file>
<file>img/taskbar/dark/taskbar_offline_2x.png</file>
<file>img/taskbar/light/taskbar_online_2x.png</file>
<file>img/taskbar/light/taskbar_idle_2x.png</file>
<file>img/taskbar/light/taskbar_busy_2x.png</file>
<file>img/taskbar/light/taskbar_offline_2x.png</file>
<file>img/transfer.png</file>
<file>ui/acceptFileButton/default.png</file>
<file>ui/acceptFileButton/hover.png</file>
<file>ui/acceptFileButton/pressed.png</file>
<file>ui/acceptFileButton/style.css</file>
<file>ui/callButton/callButton.css</file>
<file>ui/callButton/callButton.png</file>
<file>ui/callButton/callButtonDisabled.png</file>
<file>ui/callButton/callButtonHover.png</file>
<file>ui/callButton/callButtonPressed.png</file>
<file>ui/callButton/callButtonRed.png</file>
<file>ui/callButton/callButtonRedHover.png</file>
<file>ui/callButton/callButtonRedPressed.png</file>
<file>ui/callButton/callButtonYellow.png</file>
<file>ui/callButton/callButtonYellowHover.png</file>
<file>ui/callButton/callButtonYellowPressed.png</file>
<file>ui/chatArea/chatArea.css</file>
<file>ui/chatArea/innerStyle.css</file>
<file>ui/chatArea/spinner.png</file>
<file>ui/chatArea/info.png</file>
<file>ui/chatArea/scrollBarDownArrow.png</file>
<file>ui/chatArea/scrollBarDownArrowHover.png</file>
<file>ui/chatArea/scrollBarDownArrowPressed.png</file>
<file>ui/chatArea/scrollBarHandle.png</file>
<file>ui/chatArea/scrollBarUpArrow.png</file>
<file>ui/chatArea/scrollBarUpArrowHover.png</file>
<file>ui/chatArea/scrollBarUpArrowPressed.png</file>
<file>ui/emoteButton/emoteButton.css</file>
<file>ui/emoteButton/emoteButton.png</file>
<file>ui/emoteButton/emoteButtonHover.png</file>
<file>ui/emoteButton/emoteButtonPressed.png</file>
<file>ui/fileButton/fileButton.css</file>
<file>ui/fileButton/fileButton.png</file>
<file>ui/fileButton/fileButtonHover.png</file>
<file>ui/fileButton/fileButtonPressed.png</file>
<file>ui/fileButton/fileButtonDisabled.png</file>
<file>ui/msgEdit/msgEdit.css</file>
<file>ui/pauseFileButton/default.png</file>
<file>ui/pauseFileButton/hover.png</file>
<file>ui/pauseFileButton/pressed.png</file>
<file>ui/pauseFileButton/style.css</file>
<file>ui/sendButton/sendButton.css</file>
<file>ui/sendButton/sendButton.png</file>
<file>ui/sendButton/sendButtonHover.png</file>
<file>ui/sendButton/sendButtonPressed.png</file>
<file>ui/stopFileButton/default.png</file>
<file>ui/stopFileButton/hover.png</file>
<file>ui/stopFileButton/pressed.png</file>
<file>ui/stopFileButton/style.css</file>
<file>ui/videoButton/videoButton.css</file>
<file>ui/videoButton/videoButton.png</file>
<file>ui/videoButton/videoButtonDisabled.png</file>
<file>ui/videoButton/videoButtonHover.png</file>
<file>ui/videoButton/videoButtonPressed.png</file>
<file>ui/videoButton/videoButtonRed.png</file>
<file>ui/videoButton/videoButtonRedHover.png</file>
<file>ui/videoButton/videoButtonRedPressed.png</file>
<file>ui/videoButton/videoButtonYellow.png</file>
<file>ui/videoButton/videoButtonYellowHover.png</file>
<file>ui/videoButton/videoButtonYellowPressed.png</file>
<file>img/group_dark.png</file>
<file>ui/window/applicationIcon.png</file>
<file>ui/friendList/friendList.css</file>
<file>ui/window/window.css</file>
<file>img/status/dot_busy.png</file>
<file>img/status/dot_busy_2x.png</file>
<file>img/status/dot_busy_notification.png</file>
<file>translations/fr.qm</file>
<file>translations/ru.qm</file>
<file>ui/fileTransferWidget/fileTransferWidget.css</file>
<file>ui/fileTransferInstance/red.css</file>
<file>ui/fileTransferInstance/green.css</file>
<file>ui/fileTransferInstance/grey.css</file>
<file>ui/fileTransferInstance/yellow.css</file>
<file>ui/fileTransferInstance/background_red.png</file>
<file>ui/fileTransferInstance/background_yellow.png</file>
<file>ui/fileTransferInstance/background_green.png</file>
<file>ui/fileTransferInstance/background_grey.png</file>
<file>ui/fileTransferInstance/pause_2x.png</file>
<file>ui/fileTransferInstance/no_2x.png</file>
<file>ui/fileTransferInstance/yes_2x.png</file>
<file>ui/fileTransferInstance/arrow_white_2x.png</file>
<file>ui/statusButton/dot_away.png</file>
<file>ui/statusButton/dot_busy.png</file>
<file>ui/statusButton/dot_idle.png</file>
<file>ui/statusButton/dot_online.png</file>
<file>ui/statusButton/statusButton.css</file>
<file>ui/statusButton/menu_indicator.png</file>
<file>translations/de.qm</file>
<file>translations/it.qm</file>
<file>ui/emoticonWidget/dot_page.png</file>
<file>ui/emoticonWidget/dot_page_current.png</file>
<file>ui/emoticonWidget/emoticonWidget.css</file>
<file>ui/emoticonWidget/dot_page_hover.png</file>
<file>ui/volButton/volButton.png</file>
<file>ui/volButton/volButtonHover.png</file>
<file>ui/volButton/volButtonPressed.png</file>
<file>ui/micButton/micButton.png</file>
<file>ui/micButton/micButtonDisabled.png</file>
<file>ui/micButton/micButtonHover.png</file>
<file>ui/micButton/micButtonPressed.png</file>
<file>ui/micButton/micButton.css</file>
<file>ui/volButton/volButton.css</file>
<file>ui/fileTransferInstance/acceptFileButton.png</file>
<file>ui/fileTransferInstance/pauseFileButton.png</file>
<file>ui/fileTransferInstance/pauseGreyFileButton.png</file>
<file>ui/fileTransferInstance/resumeFileButton.png</file>
<file>ui/fileTransferInstance/stopFileButton.png</file>
<file>ui/fileTransferInstance/emptyLGreenFileButton.png</file>
<file>ui/fileTransferInstance/emptyLRedFileButton.png</file>
<file>ui/fileTransferInstance/emptyRGreenFileButton.png</file>
<file>ui/fileTransferInstance/emptyRRedFileButton.png</file>
<file>audio/notification.pcm</file>
<file>audio/ToxicIncomingCall.pcm</file>
<file>img/settings/general.png</file>
<file>img/settings/identity.png</file>
<file>img/settings/privacy.png</file>
<file>img/settings/av.png</file>
<file>translations/pl.qm</file>
<file>translations/fi.qm</file>
<file>translations/mannol.qm</file>
<file>translations/uk.qm</file>
<file>img/avatar_mask.png</file>
<file>img/group_2x.png</file>
<file>ui/chatroomWidgets/genericChatroomWidget.css</file>
<file>ui/fileTransferInstance/sliverRTEdge.png</file>
<file>ui/window/statusPanel.css</file>
<file>ui/settings/mainContent.css</file>
<file>ui/settings/mainHead.css</file>
<file>translations/pirate.qm</file>
<file>ui/chatArea/chatHead.css</file>
<file>smileys/krepa098/angry.png</file>
<file>smileys/krepa098/cool.png</file>
<file>smileys/krepa098/crying.png</file>
<file>smileys/krepa098/emoticons.xml</file>
<file>smileys/krepa098/happy.png</file>
<file>smileys/krepa098/Kappa.png</file>
<file>smileys/krepa098/laugh_closed_eyes.png</file>
<file>smileys/krepa098/laugh.png</file>
<file>smileys/krepa098/plain.png</file>
<file>smileys/krepa098/raw.svg</file>
<file>smileys/krepa098/sad.png</file>
<file>smileys/krepa098/scared.png</file>
<file>smileys/krepa098/smile.png</file>
<file>smileys/krepa098/stunned.png</file>
<file>smileys/krepa098/tongue.png</file>
<file>smileys/krepa098/uncertain.png</file>
<file>smileys/krepa098/wink.png</file>
<file>smileys/cylgom/angel.png</file>
<file>smileys/cylgom/angry.png</file>
<file>smileys/cylgom/beer.png</file>
@ -227,8 +101,144 @@
<file>smileys/cylgom/XD.png</file>
<file>smileys/cylgom/XP.png</file>
<file>smileys/cylgom/yawn.png</file>
<file>smileys/krepa098/angry.png</file>
<file>smileys/krepa098/cool.png</file>
<file>smileys/krepa098/crying.png</file>
<file>smileys/krepa098/emoticons.xml</file>
<file>smileys/krepa098/happy.png</file>
<file>smileys/krepa098/Kappa.png</file>
<file>smileys/krepa098/laugh_closed_eyes.png</file>
<file>smileys/krepa098/laugh.png</file>
<file>smileys/krepa098/plain.png</file>
<file>smileys/krepa098/raw.svg</file>
<file>smileys/krepa098/sad.png</file>
<file>smileys/krepa098/scared.png</file>
<file>smileys/krepa098/smile.png</file>
<file>smileys/krepa098/stunned.png</file>
<file>smileys/krepa098/tongue.png</file>
<file>smileys/krepa098/uncertain.png</file>
<file>smileys/krepa098/wink.png</file>
<file>translations/bg.qm</file>
<file>translations/de.qm</file>
<file>translations/es.qm</file>
<file>translations/fi.qm</file>
<file>translations/fr.qm</file>
<file>translations/it.qm</file>
<file>translations/mannol.qm</file>
<file>translations/pirate.qm</file>
<file>translations/pl.qm</file>
<file>translations/ru.qm</file>
<file>translations/sv.qm</file>
<file>ui/fileTransferInstance/browse_path.png</file>
<file>translations/uk.qm</file>
<file>ui/acceptFileButton/default.png</file>
<file>ui/acceptFileButton/hover.png</file>
<file>ui/acceptFileButton/pressed.png</file>
<file>ui/acceptFileButton/style.css</file>
<file>ui/callButton/callButton.css</file>
<file>ui/callButton/callButton.png</file>
<file>ui/callButton/callButtonDisabled.png</file>
<file>ui/callButton/callButtonHover.png</file>
<file>ui/callButton/callButtonPressed.png</file>
<file>ui/callButton/callButtonRed.png</file>
<file>ui/callButton/callButtonRedHover.png</file>
<file>ui/callButton/callButtonRedPressed.png</file>
<file>ui/callButton/callButtonYellow.png</file>
<file>ui/callButton/callButtonYellowHover.png</file>
<file>ui/callButton/callButtonYellowPressed.png</file>
<file>ui/chatArea/chatArea.css</file>
<file>ui/chatArea/chatHead.css</file>
<file>ui/chatArea/innerStyle.css</file>
<file>ui/chatArea/spinner.png</file>
<file>ui/chatArea/info.png</file>
<file>ui/chatArea/scrollBarDownArrow.png</file>
<file>ui/chatArea/scrollBarDownArrowHover.png</file>
<file>ui/chatArea/scrollBarDownArrowPressed.png</file>
<file>ui/chatArea/scrollBarHandle.png</file>
<file>ui/chatArea/scrollBarUpArrow.png</file>
<file>ui/chatArea/scrollBarUpArrowHover.png</file>
<file>ui/chatArea/scrollBarUpArrowPressed.png</file>
<file>ui/chatroomWidgets/genericChatroomWidget.css</file>
<file>ui/emoteButton/emoteButton.css</file>
<file>ui/emoteButton/emoteButton.png</file>
<file>ui/emoteButton/emoteButtonHover.png</file>
<file>ui/emoteButton/emoteButtonPressed.png</file>
<file>ui/emoticonWidget/dot_page.png</file>
<file>ui/emoticonWidget/dot_page_current.png</file>
<file>ui/emoticonWidget/dot_page_hover.png</file>
<file>ui/emoticonWidget/emoticonWidget.css</file>
<file>ui/fileButton/fileButton.css</file>
<file>ui/fileButton/fileButton.png</file>
<file>ui/fileButton/fileButtonHover.png</file>
<file>ui/fileButton/fileButtonPressed.png</file>
<file>ui/fileButton/fileButtonDisabled.png</file>
<file>ui/fileTransferInstance/acceptFileButton.png</file>
<file>ui/fileTransferInstance/emptyLGreenFileButton.png</file>
<file>ui/fileTransferInstance/emptyLRedFileButton.png</file>
<file>ui/fileTransferInstance/emptyRGreenFileButton.png</file>
<file>ui/fileTransferInstance/emptyRRedFileButton.png</file>
<file>ui/fileTransferInstance/pauseFileButton.png</file>
<file>ui/fileTransferInstance/pauseGreyFileButton.png</file>
<file>ui/fileTransferInstance/resumeFileButton.png</file>
<file>ui/fileTransferInstance/stopFileButton.png</file>
<file>ui/fileTransferInstance/sliverRTEdge.png</file>
<file>ui/fileTransferWidget/fileTransferWidget.css</file>
<file>ui/friendList/friendList.css</file>
<file>ui/micButton/micButton.css</file>
<file>ui/micButton/micButton.png</file>
<file>ui/micButton/micButtonDisabled.png</file>
<file>ui/micButton/micButtonHover.png</file>
<file>ui/micButton/micButtonPressed.png</file>
<file>ui/msgEdit/msgEdit.css</file>
<file>ui/pauseFileButton/default.png</file>
<file>ui/pauseFileButton/hover.png</file>
<file>ui/pauseFileButton/pressed.png</file>
<file>ui/pauseFileButton/style.css</file>
<file>ui/sendButton/sendButton.css</file>
<file>ui/sendButton/sendButton.png</file>
<file>ui/sendButton/sendButtonHover.png</file>
<file>ui/sendButton/sendButtonPressed.png</file>
<file>ui/settings/mainContent.css</file>
<file>ui/settings/mainHead.css</file>
<file>ui/statusButton/dot_away.png</file>
<file>ui/statusButton/dot_busy.png</file>
<file>ui/statusButton/dot_idle.png</file>
<file>ui/statusButton/dot_online.png</file>
<file>ui/statusButton/menu_indicator.png</file>
<file>ui/statusButton/statusButton.css</file>
<file>ui/stopFileButton/default.png</file>
<file>ui/stopFileButton/hover.png</file>
<file>ui/stopFileButton/pressed.png</file>
<file>ui/stopFileButton/style.css</file>
<file>ui/videoButton/videoButton.css</file>
<file>ui/videoButton/videoButton.png</file>
<file>ui/videoButton/videoButtonDisabled.png</file>
<file>ui/videoButton/videoButtonHover.png</file>
<file>ui/videoButton/videoButtonPressed.png</file>
<file>ui/videoButton/videoButtonRed.png</file>
<file>ui/videoButton/videoButtonRedHover.png</file>
<file>ui/videoButton/videoButtonRedPressed.png</file>
<file>ui/videoButton/videoButtonYellow.png</file>
<file>ui/videoButton/videoButtonYellowHover.png</file>
<file>ui/videoButton/videoButtonYellowPressed.png</file>
<file>ui/fileTransferWidget/fileTransferWidget.css</file>
<file>ui/fileTransferInstance/red.css</file>
<file>ui/fileTransferInstance/green.css</file>
<file>ui/fileTransferInstance/grey.css</file>
<file>ui/fileTransferInstance/yellow.css</file>
<file>ui/fileTransferInstance/background_red.png</file>
<file>ui/fileTransferInstance/background_yellow.png</file>
<file>ui/fileTransferInstance/background_green.png</file>
<file>ui/fileTransferInstance/background_grey.png</file>
<file>ui/fileTransferInstance/pause_2x.png</file>
<file>ui/fileTransferInstance/no_2x.png</file>
<file>ui/fileTransferInstance/yes_2x.png</file>
<file>ui/fileTransferInstance/arrow_white_2x.png</file>
<file>ui/volButton/volButton.png</file>
<file>ui/volButton/volButtonHover.png</file>
<file>ui/volButton/volButtonPressed.png</file>
<file>ui/volButton/volButton.css</file>
<file>ui/window/applicationIcon.png</file>
<file>ui/window/statusPanel.css</file>
<file>ui/window/window.css</file>
</qresource>
</RCC>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleIconFile</key>
<string>qtox.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>qtox</string>
<key>CFBundleIdentifier</key>
<string>im.tox.qtox</string>
</dict>
</plist>

View File

@ -1,42 +1,86 @@
[DHT%20Server]
dhtServerList\size=9
dhtServerList\size=21
dhtServerList\1\name=Nikolai Toryzin
dhtServerList\1\userId=951C88B7E75C867418ACDB5D273821372BB5BD652740BCDF623A4FA293E75D2F
dhtServerList\1\address=192.254.75.98
dhtServerList\1\port=33445
dhtServerList\2\name=sonOfRa
dhtServerList\2\userId=04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F
dhtServerList\2\address=144.76.60.215
dhtServerList\2\port=33445
dhtServerList\3\name=stal
dhtServerList\3\userId=A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074
dhtServerList\3\address=23.226.230.47
dhtServerList\2\name=Nikolai Toryzin
dhtServerList\2\userId=2A4B50D1D525DA2E669592A20C327B5FAD6C7E5962DC69296F9FEC77C4436E4E
dhtServerList\2\address=31.7.57.236
dhtServerList\2\port=443
dhtServerList\3\name=sonOfRa
dhtServerList\3\userId=04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F
dhtServerList\3\address=144.76.60.215
dhtServerList\3\port=33445
dhtServerList\4\name=aitjcize
dhtServerList\4\userId=7F9C31FE850E97CEFD4C4591DF93FC757C7C12549DDD55F8EEAECC34FE76C029
dhtServerList\4\address=54.199.139.199
dhtServerList\4\name=stal
dhtServerList\4\userId=A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074
dhtServerList\4\address=23.226.230.47
dhtServerList\4\port=33445
dhtServerList\5\name=astonex
dhtServerList\5\userId=B98A2CEAA6C6A2FADC2C3632D284318B60FE5375CCB41EFA081AB67F500C1B0B
dhtServerList\5\address=37.59.102.176
dhtServerList\5\userId=10B20C49ACBD968D7C80F2E8438F92EA51F189F4E70CFBBB2C2C8C799E97F03E
dhtServerList\5\address=178.62.125.224
dhtServerList\5\port=33445
dhtServerList\6\name=nurupo
dhtServerList\6\userId=F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67
dhtServerList\6\address=192.210.149.121
dhtServerList\6\name=mousey
dhtServerList\6\userId=5EB67C51D3FF5A9D528D242B669036ED2A30F8A60E674C45E7D43010CB2E1331
dhtServerList\6\address=37.187.46.132
dhtServerList\6\port=33445
dhtServerList\7\name=mousey
dhtServerList\7\userId=5EB67C51D3FF5A9D528D242B669036ED2A30F8A60E674C45E7D43010CB2E1331
dhtServerList\7\address=37.187.46.132
dhtServerList\7\name=SylvieLorxu
dhtServerList\7\userId=4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057
dhtServerList\7\address=178.21.112.187
dhtServerList\7\port=33445
dhtServerList\8\name=Proplex
dhtServerList\8\userId=7BE3951B97CA4B9ECDDA768E8C52BA19E9E2690AB584787BF4C90E04DBB75111
dhtServerList\8\address=107.161.17.51
dhtServerList\8\name=Munrek
dhtServerList\8\userId=E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354
dhtServerList\8\address=195.154.119.113
dhtServerList\8\port=33445
dhtServerList\9\name=SylvieLorxu
dhtServerList\9\userId=4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057
dhtServerList\9\address=178.21.112.187
dhtServerList\9\name=nurupo
dhtServerList\9\userId=F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67
dhtServerList\9\address=192.210.149.121
dhtServerList\9\port=33445
dhtServerList\10\name=Unknown (uTox)
dhtServerList\10\userId=7187969BB10B54C98538BAE94C069CE5C84E650D54F7E596543D8FB1ECF4CF23
dhtServerList\10\address=95.85.13.245
dhtServerList\10\name=aitjcize
dhtServerList\10\userId=7F9C31FE850E97CEFD4C4591DF93FC757C7C12549DDD55F8EEAECC34FE76C029
dhtServerList\10\address=54.199.139.199
dhtServerList\10\port=33445
dhtServerList\11\name=Jfreegman
dhtServerList\11\userId=8CD087E31C67568103E8C2A28653337E90E6B8EDA0D765D57C6B5172B4F1F04C
dhtServerList\11\address=104.219.184.206
dhtServerList\11\port=443
dhtServerList\12\name=bunslow
dhtServerList\12\userId=93574A3FAB7D612FEA29FD8D67D3DD10DFD07A075A5D62E8AF3DD9F5D0932E11
dhtServerList\12\address=76.191.23.96
dhtServerList\12\port=33445
dhtServerList\13\name=Martin Schröder
dhtServerList\13\userId=F5A1A38EFB6BD3C2C8AF8B10D85F0F89E931704D349F1D0720C3C4059AF2440A
dhtServerList\13\address=46.38.239.179
dhtServerList\13\port=33445
dhtServerList\14\name=lkwg82
dhtServerList\14\userId=2C308B4518862740AD9A121598BCA7713AFB25858B747313A4D073E2F6AC506C
dhtServerList\14\address=144.76.93.230
dhtServerList\14\port=33445
dhtServerList\15\name=Impyy
dhtServerList\15\userId=788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B
dhtServerList\15\address=178.62.250.138
dhtServerList\15\port=33445
dhtServerList\16\name=Thierry Thomas
dhtServerList\16\userId=7A2306BFBA665E5480AE59B31E116BE9C04DCEFE04D9FE25082316FA34B4DA0C
dhtServerList\16\address=78.225.128.39
dhtServerList\16\port=33445
dhtServerList\17\name=Manolis
dhtServerList\17\userId=461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F
dhtServerList\17\address=130.133.110.14
dhtServerList\17\port=33445
dhtServerList\18\name=lawk1
dhtServerList\18\userId=58D2DE4B169502669941E50780C1630FAA48A0B7026D6F4066C320D47AC6401E
dhtServerList\18\address=178.62.150.106
dhtServerList\18\port=33445
dhtServerList\19\name=noisykeyboard
dhtServerList\19\userId=5918AC3C06955962A75AD7DF4F80A5D7C34F7DB9E1498D2E0495DE35B3FE8A57
dhtServerList\19\address=104.167.101.29
dhtServerList\19\port=33445
dhtServerList\20\name=aceawan
dhtServerList\20\userId=391C96CB67AE893D4782B8E4495EB9D89CF1031F48460C06075AA8CE76D50A21
dhtServerList\20\address=195.154.109.148
dhtServerList\20\port=33445
dhtServerList\21\name=pastly
dhtServerList\21\userId=3E1FFDEB667BFF549F619EC6737834762124F50A89C8D0DBF1DDF64A2DD6CD1B
dhtServerList\21\address=192.3.173.88
dhtServerList\21\port=33445

View File

@ -2,12 +2,12 @@
if which apt-get; then
sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev \
libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev
libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev qttools5-dev-tools qtchooser libxss-dev
elif which pacman; then
sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx
sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx libxss
elif which yum; then
yum groupinstall "Development Tools"
yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libtool autoconf automake check check-devel
yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libtool autoconf automake check check-devel libXScrnSaver-devel
else
echo "Unknown package manager, attempting to compile anyways"
fi

212
src/audio.cpp Normal file
View File

@ -0,0 +1,212 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include "audio.h"
#include "src/core.h"
#include <QDebug>
#include <QThread>
#include <cassert>
std::atomic<int> Audio::userCount{0};
Audio* Audio::instance{nullptr};
QThread* Audio::audioThread{nullptr};
ALCdevice* Audio::alInDev{nullptr};
ALCdevice* Audio::alOutDev{nullptr};
ALCcontext* Audio::alContext{nullptr};
ALuint Audio::alMainSource{0};
Audio& Audio::getInstance()
{
if (!instance)
{
instance = new Audio();
audioThread = new QThread(instance);
audioThread->setObjectName("qTox Audio");
audioThread->start();
instance->moveToThread(audioThread);
}
return *instance;
}
void Audio::suscribeInput()
{
if (!userCount++ && alInDev)
alcCaptureStart(alInDev);
}
void Audio::unsuscribeInput()
{
if (!--userCount && alInDev)
alcCaptureStop(alInDev);
}
void Audio::openInput(const QString& inDevDescr)
{
auto* tmp = alInDev;
alInDev = nullptr;
if (tmp)
alcCaptureCloseDevice(tmp);
int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
if (inDevDescr.isEmpty())
alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag,
(av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4)
/ 1000 * av_DefaultSettings.audio_channels);
else
alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, stereoFlag,
(av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4)
/ 1000 * av_DefaultSettings.audio_channels);
if (!alInDev)
qWarning() << "Audio: Cannot open input audio device";
else
qDebug() << "Audio: Opening audio input "<<inDevDescr;
Core::getInstance()->resetCallSources(); // Force to regen each group call's sources
// Restart the capture if necessary
if (userCount.load() != 0 && alInDev)
alcCaptureStart(alInDev);
}
void Audio::openOutput(const QString& outDevDescr)
{
auto* tmp = alOutDev;
alOutDev = nullptr;
if (outDevDescr.isEmpty())
alOutDev = alcOpenDevice(nullptr);
else
alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str());
if (!alOutDev)
{
qWarning() << "Audio: Cannot open output audio device";
}
else
{
if (alContext)
{
alcMakeContextCurrent(nullptr);
alcDestroyContext(alContext);
}
if (tmp)
alcCloseDevice(tmp);
alContext=alcCreateContext(alOutDev,nullptr);
if (!alcMakeContextCurrent(alContext))
{
qWarning() << "Audio: Cannot create output audio context";
alcCloseDevice(alOutDev);
}
else
alGenSources(1, &alMainSource);
qDebug() << "Audio: Opening audio output "<<outDevDescr;
}
Core::getInstance()->resetCallSources(); // Force to regen each group call's sources
}
void Audio::closeInput()
{
if (alInDev)
alcCaptureCloseDevice(alInDev);
userCount = 0;
}
void Audio::closeOutput()
{
if (alContext)
{
alcMakeContextCurrent(nullptr);
alcDestroyContext(alContext);
}
if (alOutDev)
alcCloseDevice(alOutDev);
}
void Audio::playMono16Sound(const QByteArray& data)
{
ALuint buffer;
alGenBuffers(1, &buffer);
alBufferData(buffer, AL_FORMAT_MONO16, data.data(), data.size(), 44100);
alSourcei(alMainSource, AL_BUFFER, buffer);
alSourcePlay(alMainSource);
alDeleteBuffers(1, &buffer);
}
void Audio::playGroupAudioQueued(Tox*,int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate,void*)
{
QMetaObject::invokeMethod(instance, "playGroupAudio", Qt::BlockingQueuedConnection,
Q_ARG(int,group), Q_ARG(int,peer), Q_ARG(const int16_t*,data),
Q_ARG(unsigned,samples), Q_ARG(uint8_t,channels), Q_ARG(unsigned,sample_rate));
}
void Audio::playGroupAudio(int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate)
{
assert(QThread::currentThread() == audioThread);
ToxGroupCall& call = Core::groupCalls[group];
if (!call.active || call.muteVol)
return;
if (!call.alSources.contains(peer))
alGenSources(1, &call.alSources[peer]);
playAudioBuffer(call.alSources[peer], data, samples, channels, sample_rate);
}
void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate)
{
assert(channels == 1 || channels == 2);
ALuint bufid;
ALint processed = 0, queued = 16;
alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued);
alSourcei(alSource, AL_LOOPING, AL_FALSE);
if (processed)
{
ALuint bufids[processed];
alSourceUnqueueBuffers(alSource, processed, bufids);
alDeleteBuffers(processed - 1, bufids + 1);
bufid = bufids[0];
}
else if (queued < 16)
{
alGenBuffers(1, &bufid);
}
else
{
qDebug() << "Audio: Dropped frame";
return;
}
alBufferData(bufid, (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data,
samples * 2 * channels, sampleRate);
alSourceQueueBuffers(alSource, 1, &bufid);
ALint state;
alGetSourcei(alSource, AL_SOURCE_STATE, &state);
if (state != AL_PLAYING)
alSourcePlay(alSource);
}

82
src/audio.h Normal file
View File

@ -0,0 +1,82 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifndef AUDIO_H
#define AUDIO_H
#include <QObject>
#include <QHash>
#include <atomic>
#if defined(__APPLE__) && defined(__MACH__)
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
class QString;
class QByteArray;
class QTimer;
class QThread;
struct Tox;
class Audio : QObject
{
Q_OBJECT
public:
static Audio& getInstance(); ///< Returns the singleton's instance. Will construct on first call.
static void suscribeInput(); ///< Call when you need to capture sound from the open input device.
static void unsuscribeInput(); ///< Call once you don't need to capture on the open input device anymore.
static void openInput(const QString& inDevDescr); ///< Open an input device, use before suscribing
static void openOutput(const QString& outDevDescr); ///< Open an output device
static void closeInput(); ///< Close an input device, please don't use unless everyone's unsuscribed
static void closeOutput(); ///< Close an output device
static void playMono16Sound(const QByteArray& data); ///< Play a 44100Hz mono 16bit PCM sound
/// May be called from any thread, will always queue a call to playGroupAudio
/// The first and last argument are ignored, but allow direct compatibility with toxcore
static void playGroupAudioQueued(Tox*, int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate, void*);
public slots:
/// Must be called from the audio thread, plays a group call's received audio
void playGroupAudio(int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate);
public:
static QThread* audioThread;
static ALCdevice* alOutDev, *alInDev;
static ALCcontext* alContext;
static ALuint alMainSource;
private:
explicit Audio()=default;
static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
private:
static Audio* instance;
static std::atomic<int> userCount;
};
#endif // AUDIO_H

53
src/audiofilterer.cpp Normal file
View File

@ -0,0 +1,53 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifdef QTOX_FILTER_AUDIO
#include "audiofilterer.h"
extern "C"{
#include <filter_audio.h>
}
void AudioFilterer::startFilter(unsigned int fs)
{
closeFilter();
filter = new_filter_audio(fs);
}
void AudioFilterer::closeFilter()
{
if (filter)
kill_filter_audio(filter);
filter = nullptr;
}
void AudioFilterer::filterAudio(int16_t* data, int framesize)
{
if (!filter)
return;
filter_audio(filter, (int16_t*) data, framesize);
}
AudioFilterer::~AudioFilterer()
{
closeFilter();
}
#endif // QTOX_FILTER_AUDIO

41
src/audiofilterer.h Normal file
View File

@ -0,0 +1,41 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifdef QTOX_FILTER_AUDIO
#ifndef AUDIOFILTERER_H
#define AUDIOFILTERER_H
#include <cstdint>
#ifndef _FILTER_AUDIO
typedef struct Filter_Audio Filter_Audio;
#endif
class AudioFilterer
{
public:
explicit AudioFilterer() = default;
~AudioFilterer();
void startFilter(unsigned int fs);
void filterAudio(int16_t* data, int framesize);
void closeFilter();
private:
struct Filter_Audio* filter{nullptr};
};
#endif // AUDIOFILTERER_H
#endif // QTOX_FILTER_AUDIO

View File

@ -46,14 +46,13 @@ unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] =
#elif defined(Q_OS_OSX)
const QString AutoUpdater::platform = "osx";
const QString AutoUpdater::updaterBin = "installer -store -pkg "+Settings::getInstance().getSettingsDirPath()
+"/update/qtox.pkg -target /";
const QString AutoUpdater::updaterBin = "/Applications/qtox.app/Contents/MacOS/updater";
const QString AutoUpdater::updateServer = "https://dist-build.tox.im";
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] =
{
0xa5, 0x80, 0xf3, 0xb7, 0xd0, 0x10, 0xc0, 0xf9, 0xd6, 0xcf, 0x48, 0x15, 0x99, 0x70, 0x92, 0x49,
0xf6, 0xe8, 0xe5, 0xe2, 0x6c, 0x73, 0x8c, 0x48, 0x25, 0xed, 0x01, 0x72, 0xf7, 0x6c, 0x17, 0x28
0x12, 0x86, 0x25, 0x05, 0xb8, 0x9b, 0x39, 0x6f, 0xf1, 0xb1, 0xc4, 0x4d, 0x6f, 0x39, 0x35, 0x4d,
0xea, 0xdf, 0x6c, 0x97, 0x98, 0x7d, 0x6f, 0x1c, 0x29, 0xf5, 0xb2, 0x3a, 0x5b, 0x78, 0xc1, 0x34
};
#else
@ -68,20 +67,22 @@ const QString AutoUpdater::filesURI = AutoUpdater::updateServer+"/qtox/"+AutoUpd
bool AutoUpdater::isUpdateAvailable()
{
QString newVersion = getUpdateVersion();
if (newVersion.isEmpty() || newVersion == GIT_VERSION)
VersionInfo newVersion = getUpdateVersion();
if (newVersion.timestamp <= TIMESTAMP
|| newVersion.versionString.isEmpty() || newVersion.versionString == GIT_VERSION)
return false;
else
return true;
}
QString AutoUpdater::getUpdateVersion()
AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
{
QString version;
VersionInfo versionInfo;
versionInfo.timestamp = 0;
// Updates only for supported platforms
if (platform.isEmpty())
return version;
return versionInfo;
QNetworkAccessManager *manager = new QNetworkAccessManager;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(checkURI)));
@ -93,20 +94,20 @@ QString AutoUpdater::getUpdateVersion()
qWarning() << "AutoUpdater: getUpdateVersion: network error: "<<reply->errorString();
reply->deleteLater();
manager->deleteLater();
return version;
return versionInfo;
}
QByteArray data = reply->readAll();
reply->deleteLater();
manager->deleteLater();
if (data.size() < (int)(1+crypto_sign_BYTES))
return version;
return versionInfo;
// Check updater protocol version
if ((int)data[0] != '1')
if ((int)data[0] != '2')
{
qWarning() << "AutoUpdater: getUpdateVersion: Bad version "<<(uint8_t)data[0];
return version;
return versionInfo;
}
// Check the signature
@ -118,12 +119,16 @@ QString AutoUpdater::getUpdateVersion()
if (crypto_sign_verify_detached(sig, msg, msgData.size(), key) != 0)
{
qCritical() << "AutoUpdater: getUpdateVersion: RECEIVED FORGED VERSION FILE FROM "<<updateServer;
return version;
return versionInfo;
}
version = msgData;
int sepPos = msgData.indexOf('!');
versionInfo.timestamp = QString(msgData.left(sepPos)).toInt();
versionInfo.versionString = msgData.mid(sepPos+1);
return version;
qDebug() << "timestamp:"<<versionInfo.timestamp << ", str:"<<versionInfo.versionString;
return versionInfo;
}
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
@ -387,13 +392,13 @@ void AutoUpdater::installLocalUpdate()
// Workaround QTBUG-7645
// QProcess fails silently when elevation is required instead of showing a UAC prompt on Win7/Vista
#ifdef Q_OS_WIN
int result = (int)::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
if (SE_ERR_ACCESSDENIED == result)
HINSTANCE result = ::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
if (result == (HINSTANCE)SE_ERR_ACCESSDENIED)
{
// Requesting elevation
result = (int)::ShellExecuteA(0, "runas", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
result = ::ShellExecuteA(0, "runas", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
}
if (result <= 32)
if (result <= (HINSTANCE)32)
{
goto fail;
}
@ -423,7 +428,7 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
return;
if (Widget::getInstance()->askMsgboxQuestion(QObject::tr("Update", "The title of a message box"),
QObject::tr("An update is available, do you want to download it now ?\nIt will be installed when qTox restarts.")))
QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts.")))
{
downloadUpdate();
}

View File

@ -27,7 +27,7 @@
#ifdef Q_OS_WIN
#define AUTOUPDATE_ENABLED 1
#elif defined(Q_OS_OSX)
#define AUTOUPDATE_ENABLED 0
#define AUTOUPDATE_ENABLED 1
#else
#define AUTOUPDATE_ENABLED 0
#endif
@ -58,6 +58,12 @@ public:
QByteArray data;
};
struct VersionInfo
{
uint64_t timestamp;
QString versionString;
};
public:
/// Connects to the qTox update server, if an updat is found shows a dialog to the user asking to download it
/// Runs asynchronously in its own thread, and will return immediatly
@ -66,9 +72,9 @@ public:
/// Connects to the qTox update server, returns true if an update is available for download
/// Will call getUpdateVersion, and as such may block and processEvents
static bool isUpdateAvailable();
/// Fetch the version string of the last update available from the qTox update server
/// Fetch the version info of the last update available from the qTox update server
/// Will try to follow qTox's proxy settings, may block and processEvents
static QString getUpdateVersion();
static VersionInfo getUpdateVersion();
/// Will try to download an update, if successful returns true and qTox will apply it after a restart
/// Will try to follow qTox's proxy settings, may block and processEvents
static bool downloadUpdate();

View File

@ -20,6 +20,7 @@
#include "misc/settings.h"
#include "widget/widget.h"
#include "historykeeper.h"
#include "src/audio.h"
#include <tox/tox.h>
#include <tox/toxencryptsave.h>
@ -45,12 +46,20 @@ const QString Core::CONFIG_FILE_NAME = "data";
const QString Core::TOX_EXT = ".tox";
QList<ToxFile> Core::fileSendQueue;
QList<ToxFile> Core::fileRecvQueue;
QHash<int, ToxGroupCall> Core::groupCalls;
QThread* Core::coreThread{nullptr};
Core::Core(Camera* cam, QThread *coreThread, QString loadPath) :
#define MAX_GROUP_MESSAGE_LEN 1024
Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) :
tox(nullptr), camera(cam), loadPath(loadPath), ready{false}
{
qDebug() << "Core: loading Tox from" << loadPath;
coreThread = CoreThread;
Audio::getInstance();
videobuf = new uint8_t[videobufsize];
for (int i = 0; i < ptCounter; i++)
@ -65,6 +74,8 @@ Core::Core(Camera* cam, QThread *coreThread, QString loadPath) :
for (int i=0; i<TOXAV_MAX_CALLS;i++)
{
calls[i].active = false;
calls[i].alSource = 0;
calls[i].sendAudioTimer = new QTimer();
calls[i].sendVideoTimer = new QTimer();
calls[i].sendAudioTimer->moveToThread(coreThread);
@ -74,42 +85,22 @@ Core::Core(Camera* cam, QThread *coreThread, QString loadPath) :
// OpenAL init
QString outDevDescr = Settings::getInstance().getOutDev(); ;
if (outDevDescr.isEmpty())
alOutDev = alcOpenDevice(nullptr);
else
alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str());
if (!alOutDev)
{
qWarning() << "Core: Cannot open output audio device";
}
else
{
alContext=alcCreateContext(alOutDev,nullptr);
if (!alcMakeContextCurrent(alContext))
{
qWarning() << "Core: Cannot create output audio context";
alcCloseDevice(alOutDev);
}
else
alGenSources(1, &alMainSource);
}
Audio::openOutput(outDevDescr);
QString inDevDescr = Settings::getInstance().getInDev();
if (inDevDescr.isEmpty())
alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, AL_FORMAT_MONO16,
(av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) / 1000);
else
alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, AL_FORMAT_MONO16,
(av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) / 1000);
if (!alInDev)
qWarning() << "Core: Cannot open input audio device";
Audio::openInput(inDevDescr);
}
Core::~Core()
{
if (tox) {
clearPassword(Core::ptMain);
clearPassword(Core::ptHistory);
if (tox)
{
toxav_kill(toxav);
toxav = nullptr;
tox_kill(tox);
tox = nullptr;
}
if (videobuf)
@ -118,18 +109,8 @@ Core::~Core()
videobuf=nullptr;
}
if (alContext)
{
alcMakeContextCurrent(nullptr);
alcDestroyContext(alContext);
}
if (alOutDev)
alcCloseDevice(alOutDev);
if (alInDev)
alcCaptureCloseDevice(alInDev);
clearPassword(Core::ptMain);
clearPassword(Core::ptHistory);
Audio::closeInput();
Audio::closeOutput();
}
Core* Core::getInstance()
@ -142,7 +123,7 @@ void Core::make_tox()
// IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options.
bool enableIPv6 = Settings::getInstance().getEnableIPv6();
bool forceTCP = Settings::getInstance().getForceTCP();
bool useProxy = Settings::getInstance().getUseProxy();
ProxyType proxyType = Settings::getInstance().getProxyType();
if (enableIPv6)
qDebug() << "Core starting with IPv6 enabled";
@ -154,11 +135,11 @@ void Core::make_tox()
toxOptions.udp_disabled = forceTCP;
// No proxy by default
toxOptions.proxy_enabled = false;
toxOptions.proxy_type = TOX_PROXY_NONE;
toxOptions.proxy_address[0] = 0;
toxOptions.proxy_port = 0;
if (useProxy)
if (proxyType != ProxyType::ptNone)
{
QString proxyAddr = Settings::getInstance().getProxyAddr();
int proxyPort = Settings::getInstance().getProxyPort();
@ -170,7 +151,11 @@ void Core::make_tox()
else if (proxyAddr != "" && proxyPort > 0)
{
qDebug() << "Core: using proxy" << proxyAddr << ":" << proxyPort;
toxOptions.proxy_enabled = true;
// protection against changings in TOX_PROXY_TYPE enum
if (proxyType == ProxyType::ptSOCKS5)
toxOptions.proxy_type = TOX_PROXY_SOCKS5;
else if (proxyType == ProxyType::ptHTTP)
toxOptions.proxy_type = TOX_PROXY_HTTP;
uint16_t sz = CString::fromString(proxyAddr, (unsigned char*)toxOptions.proxy_address);
toxOptions.proxy_address[sz] = 0;
toxOptions.proxy_port = proxyPort;
@ -186,7 +171,7 @@ void Core::make_tox()
tox = tox_new(&toxOptions);
if (tox == nullptr)
{
if (toxOptions.proxy_enabled)
if (toxOptions.proxy_type != TOX_PROXY_NONE)
{
//QMessageBox::critical(Widget::getInstance(), tr("Proxy failure", "popup title"),
//tr("toxcore failed to start with your proxy settings. qTox cannot run; please modify your "
@ -204,7 +189,7 @@ void Core::make_tox()
else
qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery may not work properly.";
}
else if (toxOptions.proxy_enabled)
else if (toxOptions.proxy_type != TOX_PROXY_NONE)
{
emit badProxy();
return;
@ -262,6 +247,7 @@ void Core::start()
tox_callback_group_invite(tox, onGroupInvite, this);
tox_callback_group_message(tox, onGroupMessage, this);
tox_callback_group_namelist_change(tox, onGroupNamelistChange, this);
tox_callback_group_title(tox, onGroupTitleChange, this);
tox_callback_group_action(tox, onGroupAction, this);
tox_callback_file_send_request(tox, onFileSendRequestCallback, this);
tox_callback_file_control(tox, onFileControlCallback, this);
@ -276,14 +262,13 @@ void Core::start()
toxav_register_callstate_callback(toxav, onAvReject, av_OnReject, this);
toxav_register_callstate_callback(toxav, onAvEnd, av_OnEnd, this);
toxav_register_callstate_callback(toxav, onAvRinging, av_OnRinging, this);
toxav_register_callstate_callback(toxav, onAvStarting, av_OnStarting, this);
toxav_register_callstate_callback(toxav, onAvEnding, av_OnEnding, this);
toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnMediaChange, this);
toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnPeerCSChange, this);
toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnSelfCSChange, this);
toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this);
toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this);
toxav_register_audio_recv_callback(toxav, playCallAudio, this);
toxav_register_video_recv_callback(toxav, playCallVideo, this);
toxav_register_audio_callback(toxav, playCallAudio, this);
toxav_register_video_callback(toxav, playCallVideo, this);
QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString());
if (!pic.isNull() && !pic.size().isEmpty())
@ -319,6 +304,7 @@ void Core::process()
static int tolerance = CORE_DISCONNECT_TOLERANCE;
tox_do(tox);
toxav_do(toxav);
#ifdef DEBUG
//we want to see the debug messages immediately
@ -332,7 +318,7 @@ void Core::process()
bootstrapDht();
}
toxTimer->start(tox_do_interval(tox));
toxTimer->start(qMin(tox_do_interval(tox), toxav_do_interval(toxav)));
}
bool Core::checkConnection()
@ -362,6 +348,11 @@ void Core::bootstrapDht()
QList<Settings::DhtServer> dhtServerList = s.getDhtServerList();
int listSize = dhtServerList.size();
if (listSize == 0)
{
qDebug() << "Settings: no bootstrap list?!?";
return;
}
static int j = qrand() % listSize;
qDebug() << "Core: Bootstraping to the DHT ...";
@ -475,21 +466,21 @@ void Core::onAction(Tox*/* tox*/, int friendId, const uint8_t *cMessage, uint16_
void Core::onGroupAction(Tox*, int groupnumber, int peernumber, const uint8_t *action, uint16_t length, void* _core)
{
Core* core = static_cast<Core*>(_core);
emit core->groupMessageReceived(groupnumber, CString::toString(action, length),
core->getGroupPeerName(groupnumber, peernumber), true);
emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(action, length), true);
}
void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length,void *core)
{
QByteArray pk((char*)data, length);
if (type == TOX_GROUPCHAT_TYPE_TEXT)
{
qDebug() << QString("Core: Text group invite by %1").arg(friendnumber);
emit static_cast<Core*>(core)->groupInviteReceived(friendnumber,type,data,length);
emit static_cast<Core*>(core)->groupInviteReceived(friendnumber,type,pk);
}
else if (type == TOX_GROUPCHAT_TYPE_AV)
{
qDebug() << QString("Core: AV group invite by %1").arg(friendnumber);
emit static_cast<Core*>(core)->groupInviteReceived(friendnumber,type,data,length);
emit static_cast<Core*>(core)->groupInviteReceived(friendnumber,type,pk);
}
else
{
@ -500,8 +491,8 @@ void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *da
void Core::onGroupMessage(Tox*, int groupnumber, int peernumber, const uint8_t * message, uint16_t length, void *_core)
{
Core* core = static_cast<Core*>(_core);
emit core->groupMessageReceived(groupnumber, CString::toString(message, length),
core->getGroupPeerName(groupnumber, peernumber), false);
emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(message, length), false);
}
void Core::onGroupNamelistChange(Tox*, int groupnumber, int peernumber, uint8_t change, void *core)
@ -510,6 +501,16 @@ void Core::onGroupNamelistChange(Tox*, int groupnumber, int peernumber, uint8_t
emit static_cast<Core*>(core)->groupNamelistChanged(groupnumber, peernumber, change);
}
void Core::onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8_t* title, uint8_t len, void* _core)
{
qDebug() << "Core: group" << groupnumber << "title changed by" << peernumber;
Core* core = static_cast<Core*>(_core);
QString author;
if (peernumber >= 0)
author = core->getGroupPeerName(groupnumber, peernumber);
emit core->groupTitleChanged(groupnumber, author, CString::toString(title, len));
}
void Core::onFileSendRequestCallback(Tox*, int32_t friendnumber, uint8_t filenumber, uint64_t filesize,
const uint8_t *filename, uint16_t filename_length, void *core)
{
@ -732,7 +733,7 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag
{
const QString userId = friendAddress.mid(0, TOX_CLIENT_ID_SIZE * 2);
if(hasFriendWithAddress(friendAddress))
if (hasFriendWithAddress(friendAddress))
{
emit failedToAddFriend(userId, QString(tr("Friend is already added")));
}
@ -783,7 +784,7 @@ void Core::sendTyping(int friendId, bool typing)
void Core::sendGroupMessage(int groupId, const QString& message)
{
QList<CString> cMessages = splitMessage(message);
QList<CString> cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN);
for (auto &cMsg :cMessages)
{
@ -796,7 +797,7 @@ void Core::sendGroupMessage(int groupId, const QString& message)
void Core::sendGroupAction(int groupId, const QString& message)
{
QList<CString> cMessages = splitMessage(message);
QList<CString> cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN);
for (auto &cMsg :cMessages)
{
@ -807,6 +808,14 @@ void Core::sendGroupAction(int groupId, const QString& message)
}
}
void Core::changeGroupTitle(int groupId, const QString& title)
{
CString cTitle(title);
int err = tox_group_set_title(tox, groupId, cTitle.data(), cTitle.size());
if (!err)
emit groupTitleChanged(groupId, getUsername(), title);
}
void Core::sendFile(int32_t friendId, QString Filename, QString FilePath, long long filesize)
{
QMutexLocker mlocker(&fileSendMutex);
@ -1007,6 +1016,9 @@ void Core::removeGroup(int groupId, bool fake)
if (!tox || fake)
return;
tox_del_groupchat(tox, groupId);
if (groupCalls[groupId].active)
leaveGroupCall(groupId);
}
QString Core::getUsername() const
@ -1255,7 +1267,7 @@ bool Core::loadConfiguration(QString path)
if (!HistoryKeeper::checkPassword())
{
if (QMessageBox::Ok == Widget::getInstance()->showWarningMsgBox(tr("Encrypted log"),
tr("Your history is encrypted with different password\nDo you want to try another password?"),
tr("Your history is encrypted with different password.\nDo you want to try another password?"),
QMessageBox::Ok | QMessageBox::Cancel))
{
error = true;
@ -1265,7 +1277,7 @@ bool Core::loadConfiguration(QString path)
{
error = false;
clearPassword(ptHistory);
Widget::getInstance()->showWarningMsgBox(tr("Loggin"), tr("Due to incorret password logging will be disabled"));
Widget::getInstance()->showWarningMsgBox(tr("History"), tr("Due to incorret password history will be disabled."));
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false);
}
@ -1366,12 +1378,13 @@ void Core::switchConfiguration(const QString& profile)
qDebug() << "Core: creating new Id";
else
qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile;
saveConfiguration();
ready = false;
clearPassword(ptMain);
clearPassword(ptHistory);
ready = false;
toxTimer->stop();
Widget::getInstance()->setEnabledThreadsafe(false);
if (tox) {
@ -1380,6 +1393,7 @@ void Core::switchConfiguration(const QString& profile)
tox_kill(tox);
tox = nullptr;
}
emit selfAvatarChanged(QPixmap(":/img/contact_dark.png"));
emit blockingClearContacts(); // we need this to block, but signals are required for thread safety
@ -1387,7 +1401,12 @@ void Core::switchConfiguration(const QString& profile)
loadPath = "";
else
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
Settings::getInstance().setCurrentProfile(profile);
// the new profile needs to be set before resetting the settings, so that
// we don't load the old profile's profile.ini
Settings::getInstance().setCurrentProfile(profile);
Settings::getInstance().save(false); // save new profile, but don't write old profile info to newprofile.ini
Settings::resetInstance();
HistoryKeeper::getInstance()->resetInstance();
start();
@ -1458,6 +1477,22 @@ QString Core::getGroupPeerName(int groupId, int peerId) const
return name;
}
ToxID Core::getGroupPeerToxID(int groupId, int peerId) const
{
ToxID peerToxID;
uint8_t rawID[TOX_CLIENT_ID_SIZE];
int res = tox_group_peer_pubkey(tox, groupId, peerId, rawID);
if (res == -1)
{
qWarning() << "Core::getGroupPeerToxID: Unknown error";
return peerToxID;
}
peerToxID = ToxID::fromString(CUserId::toString(rawID));
return peerToxID;
}
QList<QString> Core::getGroupPeerNames(int groupId) const
{
QList<QString> names;
@ -1490,7 +1525,8 @@ int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* frien
else if (type == TOX_GROUPCHAT_TYPE_AV)
{
qDebug() << QString("Trying to join AV groupchat invite sent by friend %1").arg(friendnumber);
return toxav_join_av_groupchat(tox, friendnumber, friend_group_public_key, length, playGroupAudio, const_cast<Core*>(this));
return toxav_join_av_groupchat(tox, friendnumber, friend_group_public_key, length,
&Audio::playGroupAudioQueued, const_cast<Core*>(this));
}
else
{
@ -1630,17 +1666,23 @@ void Core::groupInviteFriend(int friendId, int groupId)
void Core::createGroup(uint8_t type)
{
if (type == TOX_GROUPCHAT_TYPE_TEXT)
{
emit emptyGroupCreated(tox_add_groupchat(tox));
}
else if (type == TOX_GROUPCHAT_TYPE_AV)
emit emptyGroupCreated(toxav_add_av_groupchat(tox, playGroupAudio, this));
{
emit emptyGroupCreated(toxav_add_av_groupchat(tox, &Audio::playGroupAudioQueued, this));
}
else
{
qWarning() << "Core::createGroup: Unknown type "<<type;
}
}
bool Core::hasFriendWithAddress(const QString &addr) const
{
// Valid length check
if(addr.length() != (TOX_FRIEND_ADDRESS_SIZE * 2))
if (addr.length() != (TOX_FRIEND_ADDRESS_SIZE * 2))
{
return false;
}
@ -1652,7 +1694,7 @@ bool Core::hasFriendWithAddress(const QString &addr) const
bool Core::hasFriendWithPublicKey(const QString &pubkey) const
{
// Valid length check
if(pubkey.length() != (TOX_CLIENT_ID_SIZE * 2))
if (pubkey.length() != (TOX_CLIENT_ID_SIZE * 2))
{
return false;
}
@ -1669,7 +1711,7 @@ bool Core::hasFriendWithPublicKey(const QString &pubkey) const
QString addrOrId = getFriendAddress(ids[i]);
// Set true if found
if(addrOrId.toUpper().startsWith(pubkey.toUpper()))
if (addrOrId.toUpper().startsWith(pubkey.toUpper()))
{
found = true;
break;
@ -1704,17 +1746,17 @@ QString Core::getFriendUsername(int friendnumber) const
return CString::toString(name, tox_get_name_size(tox, friendnumber));
}
QList<CString> Core::splitMessage(const QString &message)
QList<CString> Core::splitMessage(const QString &message, int maxLen)
{
QList<CString> splittedMsgs;
QByteArray ba_message(message.toUtf8());
while (ba_message.size() > TOX_MAX_MESSAGE_LENGTH)
while (ba_message.size() > maxLen)
{
int splitPos = ba_message.lastIndexOf(' ', TOX_MAX_MESSAGE_LENGTH - 1);
int splitPos = ba_message.lastIndexOf(' ', maxLen - 1);
if (splitPos <= 0)
{
splitPos = TOX_MAX_MESSAGE_LENGTH;
splitPos = maxLen;
if (ba_message[splitPos] & 0x80)
{
do {
@ -1832,3 +1874,28 @@ bool Core::isReady()
{
return ready;
}
void Core::setNospam(uint32_t nospam)
{
uint8_t *nspm = reinterpret_cast<uint8_t*>(&nospam);
std::reverse(nspm, nspm + 4);
tox_set_nospam(tox, nospam);
}
void Core::resetCallSources()
{
for (ToxGroupCall& call : groupCalls)
call.alSources.clear();
for (ToxCall& call : calls)
{
if (call.active && call.alSource)
{
ALuint tmp = call.alSource;
call.alSource = 0;
alDeleteSources(1, &tmp);
alGenSources(1, &call.alSource);
}
}
}

View File

@ -33,6 +33,9 @@ class QTimer;
class QString;
class CString;
class VideoSource;
#ifdef QTOX_FILTER_AUDIO
class AudioFilterer;
#endif
class Core : public QObject
{
@ -47,12 +50,13 @@ public:
static const QString TOX_EXT;
static const QString CONFIG_FILE_NAME;
static QString sanitize(QString name);
static QList<CString> splitMessage(const QString &message);
static QList<CString> splitMessage(const QString &message, int maxLen);
QString getPeerName(const ToxID& id) const;
int getGroupNumberPeers(int groupId) const; ///< Return the number of peers in the group chat on success, or -1 on failure
QString getGroupPeerName(int groupId, int peerId) const; ///< Get the name of a peer of a group
ToxID getGroupPeerToxID(int groupId, int peerId) const; ///< Get the ToxID of a peer of a group
QList<QString> getGroupPeerNames(int groupId) const; ///< Get the names of the peers of a group
QString getFriendAddress(int friendNumber) const; ///< Get the full address if known, or Tox ID of a friend
QString getFriendUsername(int friendNumber) const; ///< Get the username of a friend
@ -76,6 +80,8 @@ public:
bool isPasswordSet(PasswordType passtype);
bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first
void resetCallSources(); ///< Forces to regenerate each call's audio sources
public slots:
void start(); ///< Initializes the core, must be called before anything else
void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer
@ -95,10 +101,11 @@ public slots:
void setStatusMessage(const QString& message);
void setAvatar(uint8_t format, const QByteArray& data);
int sendMessage(int friendId, const QString& message);
int sendMessage(int friendId, const QString& message);
void sendGroupMessage(int groupId, const QString& message);
void sendGroupAction(int groupId, const QString& message);
int sendAction(int friendId, const QString& action);
void changeGroupTitle(int groupId, const QString& title);
int sendAction(int friendId, const QString& action);
void sendTyping(int friendId, bool typing);
void sendFile(int32_t friendId, QString Filename, QString FilePath, long long filesize);
@ -117,6 +124,17 @@ public slots:
void micMuteToggle(int callId);
void volMuteToggle(int callId);
void setNospam(uint32_t nospam);
static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.
static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call. Call from the GUI thread.
static void disableGroupCallMic(int groupId);
static void disableGroupCallVol(int groupId);
static void enableGroupCallMic(int groupId);
static void enableGroupCallVol(int groupId);
static bool isGroupCallMicEnabled(int groupId);
static bool isGroupCallVolEnabled(int groupId);
void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr);
void clearPassword(PasswordType passtype);
QByteArray encryptData(const QByteArray& data, PasswordType passtype);
@ -145,9 +163,10 @@ signals:
void friendLastSeenChanged(int friendId, const QDateTime& dateTime);
void emptyGroupCreated(int groupnumber);
void groupInviteReceived(int friendnumber, uint8_t type, const uint8_t *group_public_key,uint16_t length);
void groupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction);
void groupInviteReceived(int friendnumber, uint8_t type, QByteArray publicKey);
void groupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction);
void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change);
void groupTitleChanged(int groupnumber, const QString& author, const QString& title);
void usernameSet(const QString& username);
void statusMessageSet(const QString& message);
@ -157,6 +176,7 @@ signals:
void messageSentResult(int friendId, const QString& message, int messageId);
void groupSentResult(int groupId, const QString& message, int result);
void actionSentResult(int friendId, const QString& action, int success);
void receiptRecieved(int friedId, int receipt);
@ -212,6 +232,7 @@ private:
static void onGroupInvite(Tox *tox, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length,void *userdata);
static void onGroupMessage(Tox *tox, int groupnumber, int friendgroupnumber, const uint8_t * message, uint16_t length, void *userdata);
static void onGroupNamelistChange(Tox *tox, int groupnumber, int peernumber, uint8_t change, void *userdata);
static void onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8_t* title, uint8_t len, void* _core);
static void onFileSendRequestCallback(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize,
const uint8_t *filename, uint16_t filename_length, void *userdata);
static void onFileControlCallback(Tox *tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber,
@ -227,21 +248,18 @@ private:
static void onAvReject(void* toxav, int32_t call_index, void* core);
static void onAvEnd(void* toxav, int32_t call_index, void* core);
static void onAvRinging(void* toxav, int32_t call_index, void* core);
static void onAvStarting(void* toxav, int32_t call_index, void* core);
static void onAvEnding(void* toxav, int32_t call_index, void* core);
static void onAvRequestTimeout(void* toxav, int32_t call_index, void* core);
static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core);
static void onAvMediaChange(void *toxav, int32_t call_index, void* core);
static void playGroupAudio(Tox* tox, int groupnumber, int friendgroupnumber, const int16_t* out_audio,
unsigned out_audio_samples, uint8_t decoder_channels, unsigned audio_sample_rate, void* userdata);
static void sendGroupCallAudio(int groupId, ToxAv* toxav);
static void prepareCall(int friendId, int callId, ToxAv *toxav, bool videoEnabled);
static void cleanupCall(int callId);
static void playCallAudio(ToxAv *toxav, int32_t callId, int16_t *data, int samples, void *user_data); // Callback
static void playCallAudio(void *toxav, int32_t callId, const int16_t *data, uint16_t samples, void *user_data); // Callback
static void sendCallAudio(int callId, ToxAv* toxav);
static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
static void playCallVideo(ToxAv* toxav, int32_t callId, vpx_image_t* img, void *user_data);
static void playCallVideo(void *toxav, int32_t callId, const vpx_image_t* img, void *user_data);
void sendCallVideo(int callId);
bool checkConnection();
@ -267,7 +285,11 @@ private:
QList<DhtServer> dhtServerList;
int dhtServerId;
static QList<ToxFile> fileSendQueue, fileRecvQueue;
static ToxCall calls[];
static ToxCall calls[TOXAV_MAX_CALLS];
#ifdef QTOX_FILTER_AUDIO
static AudioFilterer * filterer[TOXAV_MAX_CALLS];
#endif
static QHash<int, ToxGroupCall> groupCalls; // Maps group IDs to ToxGroupCalls
QMutex fileSendMutex, messageSendMutex;
bool ready;
@ -276,10 +298,9 @@ private:
static const int videobufsize;
static uint8_t* videobuf;
static ALCdevice* alOutDev, *alInDev;
static ALCcontext* alContext;
public:
static ALuint alMainSource;
static QThread *coreThread;
friend class Audio; ///< Audio can access our calls directly to reduce latency
};
#endif // CORE_HPP

View File

@ -16,17 +16,21 @@
#include "core.h"
#include "video/camera.h"
#include "audio.h"
#ifdef QTOX_FILTER_AUDIO
#include "audiofilterer.h"
#endif
#include "misc/settings.h"
#include <QDebug>
#include <QTimer>
ToxCall Core::calls[TOXAV_MAX_CALLS];
#ifdef QTOX_FILTER_AUDIO
AudioFilterer * Core::filterer[TOXAV_MAX_CALLS] { nullptr};
#endif
const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4};
uint8_t* Core::videobuf;
ALCdevice* Core::alOutDev, *Core::alInDev;
ALCcontext* Core::alContext;
ALuint Core::alMainSource;
bool Core::anyActiveCalls()
{
for (auto& call : calls)
@ -48,11 +52,12 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled
calls[callId].codecSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH;
calls[callId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT;
calls[callId].videoEnabled = videoEnabled;
toxav_prepare_transmission(toxav, callId, av_jbufdc, av_VADd, videoEnabled);
int r = toxav_prepare_transmission(toxav, callId, videoEnabled);
if (r < 0)
qWarning() << QString("Error starting call %1: toxav_prepare_transmission failed with %2").arg(callId).arg(r);
// Audio
alGenSources(1, &calls[callId].alSource);
alcCaptureStart(alInDev);
Audio::suscribeInput();
// Go
calls[callId].active = true;
@ -67,6 +72,19 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled
calls[callId].sendVideoTimer->start();
Camera::getInstance()->subscribe();
}
#ifdef QTOX_FILTER_AUDIO
if (Settings::getInstance().getFilterAudio())
{
Core::filterer[callId] = new AudioFilterer();
filterer[callId]->startFilter(48000);
}
else
{
delete filterer[callId];
filterer[callId] = nullptr;
}
#endif
}
void Core::onAvMediaChange(void* toxav, int32_t callId, void* core)
@ -81,7 +99,7 @@ void Core::onAvMediaChange(void* toxav, int32_t callId, void* core)
qDebug() << "Core: Received media change from friend "<<friendId;
if (settings.call_type == TypeAudio)
if (settings.call_type == av_TypeAudio)
{
calls[callId].videoEnabled = false;
calls[callId].sendVideoTimer->stop();
@ -113,14 +131,14 @@ void Core::answerCall(int callId)
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, callId, 0, transSettings);
if (err != ErrorNone)
if (err != av_ErrorNone)
{
qWarning() << "Core::answerCall: error getting call settings";
delete transSettings;
return;
}
if (transSettings->call_type == TypeVideo)
if (transSettings->call_type == av_TypeVideo)
{
qDebug() << QString("Core: answering call %1 with video").arg(callId);
toxav_answer(toxav, callId, transSettings);
@ -150,7 +168,7 @@ void Core::startCall(int friendId, bool video)
if (video)
{
qDebug() << QString("Core: Starting new call with %1 with video").arg(friendId);
cSettings.call_type = TypeVideo;
cSettings.call_type = av_TypeVideo;
if (toxav_call(toxav, &callId, friendId, &cSettings, TOXAV_RINGING_TIME) == 0)
{
calls[callId].videoEnabled=true;
@ -165,7 +183,7 @@ void Core::startCall(int friendId, bool video)
else
{
qDebug() << QString("Core: Starting new call with %1 without video").arg(friendId);
cSettings.call_type = TypeAudio;
cSettings.call_type = av_TypeAudio;
if (toxav_call(toxav, &callId, friendId, &cSettings, TOXAV_RINGING_TIME) == 0)
{
calls[callId].videoEnabled=false;
@ -176,7 +194,6 @@ void Core::startCall(int friendId, bool video)
emit avCallFailed(friendId);
return;
}
}
}
@ -196,18 +213,21 @@ void Core::cleanupCall(int callId)
calls[callId].sendVideoTimer->stop();
if (calls[callId].videoEnabled)
Camera::getInstance()->unsubscribe();
alcCaptureStop(alInDev);
Audio::unsuscribeInput();
}
void Core::playCallAudio(ToxAv* toxav, int32_t callId, int16_t *data, int samples, void *user_data)
void Core::playCallAudio(void* toxav, int32_t callId, const int16_t *data, uint16_t samples, void *user_data)
{
Q_UNUSED(user_data);
if (!calls[callId].active)
return;
if (!calls[callId].alSource)
alGenSources(1, &calls[callId].alSource);
ToxAvCSettings dest;
if(toxav_get_peer_csettings(toxav, callId, 0, &dest) == 0)
if (toxav_get_peer_csettings((ToxAv*)toxav, callId, 0, &dest) == 0)
playAudioBuffer(calls[callId].alSource, data, samples, dest.audio_channels, dest.audio_sample_rate);
}
@ -216,41 +236,51 @@ void Core::sendCallAudio(int callId, ToxAv* toxav)
if (!calls[callId].active)
return;
if (calls[callId].muteMic)
if (calls[callId].muteMic || !Audio::alInDev)
{
calls[callId].sendAudioTimer->start();
return;
}
int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000;
uint8_t buf[framesize*2], dest[framesize*2];
const int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels;
const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels;
uint8_t buf[bufsize], dest[bufsize];
bool frame = false;
ALint samples;
alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if(samples >= framesize)
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if (samples >= framesize)
{
alcCaptureSamples(alInDev, buf, framesize);
memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind)
alcCaptureSamples(Audio::alInDev, buf, framesize);
frame = 1;
}
if(frame)
if (frame)
{
int r;
if((r = toxav_prepare_audio_frame(toxav, callId, dest, framesize*2, (int16_t*)buf, framesize)) < 0)
if ((r = toxav_prepare_audio_frame(toxav, callId, dest, framesize*2, (int16_t*)buf, framesize)) < 0)
{
qDebug() << "Core: toxav_prepare_audio_frame error";
calls[callId].sendAudioTimer->start();
return;
}
if((r = toxav_send_audio(toxav, callId, dest, r)) < 0)
#ifdef QTOX_FILTER_AUDIO
if (filterer[callId])
{
filterer[callId]->filterAudio((int16_t*) buf, framesize);
}
#endif
if ((r = toxav_send_audio(toxav, callId, dest, r)) < 0)
{
qDebug() << "Core: toxav_send_audio error";
}
}
calls[callId].sendAudioTimer->start();
}
void Core::playCallVideo(ToxAv*, int32_t callId, vpx_image_t* img, void *user_data)
void Core::playCallVideo(void*, int32_t callId, const vpx_image_t* img, void *user_data)
{
Q_UNUSED(user_data);
@ -258,8 +288,6 @@ void Core::playCallVideo(ToxAv*, int32_t callId, vpx_image_t* img, void *user_da
return;
calls[callId].videoSource.pushVPXFrame(img);
vpx_img_free(img);
}
void Core::sendCallVideo(int callId)
@ -271,7 +299,7 @@ void Core::sendCallVideo(int callId)
if (frame.w && frame.h)
{
int result;
if((result = toxav_prepare_video_frame(toxav, callId, videobuf, videobufsize, &frame)) < 0)
if ((result = toxav_prepare_video_frame(toxav, callId, videobuf, videobufsize, &frame)) < 0)
{
qDebug() << QString("Core: toxav_prepare_video_frame: error %1").arg(result);
vpx_img_free(&frame);
@ -279,7 +307,7 @@ void Core::sendCallVideo(int callId)
return;
}
if((result = toxav_send_video(toxav, callId, (uint8_t*)videobuf, result)) < 0)
if ((result = toxav_send_video(toxav, callId, (uint8_t*)videobuf, result)) < 0)
qDebug() << QString("Core: toxav_send_video error: %1").arg(result);
vpx_img_free(&frame);
@ -294,14 +322,16 @@ void Core::sendCallVideo(int callId)
void Core::micMuteToggle(int callId)
{
if (calls[callId].active) {
if (calls[callId].active)
{
calls[callId].muteMic = !calls[callId].muteMic;
}
}
void Core::volMuteToggle(int callId)
{
if (calls[callId].active) {
if (calls[callId].active)
{
calls[callId].muteVol = !calls[callId].muteVol;
alSourcef(calls[callId].alSource, AL_GAIN, calls[callId].muteVol ? 0.f : 1.f);
}
@ -321,6 +351,15 @@ void Core::onAvCancel(void* _toxav, int32_t callId, void* core)
calls[callId].active = false;
#ifdef QTOX_FILTER_AUDIO
if (filterer[callId])
{
filterer[callId]->closeFilter();
delete filterer[callId];
filterer[callId] = nullptr;
}
#endif
emit static_cast<Core*>(core)->avCancel(friendId, callId);
}
@ -379,58 +418,58 @@ void Core::onAvRinging(void* _toxav, int32_t call_index, void* core)
}
}
void Core::onAvStarting(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
//void Core::onAvStarting(void* _toxav, int32_t call_index, void* core)
//{
// ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV starting";
return;
}
// int friendId = toxav_get_peer_id(toxav, call_index, 0);
// if (friendId < 0)
// {
// qWarning() << "Core: Received invalid AV starting";
// return;
// }
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
if (err != ErrorNone)
{
qWarning() << "Core::onAvStarting: error getting call type";
delete transSettings;
return;
}
// ToxAvCSettings* transSettings = new ToxAvCSettings;
// int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
// if (err != ErrorNone)
// {
// qWarning() << "Core::onAvStarting: error getting call type";
// delete transSettings;
// return;
// }
if (transSettings->call_type == TypeVideo)
{
qDebug() << QString("Core: AV starting from %1 with video").arg(friendId);
prepareCall(friendId, call_index, toxav, true);
emit static_cast<Core*>(core)->avStarting(friendId, call_index, true);
}
else
{
qDebug() << QString("Core: AV starting from %1 without video").arg(friendId);
prepareCall(friendId, call_index, toxav, false);
emit static_cast<Core*>(core)->avStarting(friendId, call_index, false);
}
// if (transSettings->call_type == TypeVideo)
// {
// qDebug() << QString("Core: AV starting from %1 with video").arg(friendId);
// prepareCall(friendId, call_index, toxav, true);
// emit static_cast<Core*>(core)->avStarting(friendId, call_index, true);
// }
// else
// {
// qDebug() << QString("Core: AV starting from %1 without video").arg(friendId);
// prepareCall(friendId, call_index, toxav, false);
// emit static_cast<Core*>(core)->avStarting(friendId, call_index, false);
// }
delete transSettings;
}
// delete transSettings;
//}
void Core::onAvEnding(void* _toxav, int32_t call_index, void* core)
{
ToxAv* toxav = static_cast<ToxAv*>(_toxav);
//void Core::onAvEnding(void* _toxav, int32_t call_index, void* core)
//{
// ToxAv* toxav = static_cast<ToxAv*>(_toxav);
int friendId = toxav_get_peer_id(toxav, call_index, 0);
if (friendId < 0)
{
qWarning() << "Core: Received invalid AV ending";
return;
}
qDebug() << QString("Core: AV ending from %1").arg(friendId);
// int friendId = toxav_get_peer_id(toxav, call_index, 0);
// if (friendId < 0)
// {
// qWarning() << "Core: Received invalid AV ending";
// return;
// }
// qDebug() << QString("Core: AV ending from %1").arg(friendId);
cleanupCall(call_index);
// cleanupCall(call_index);
emit static_cast<Core*>(core)->avEnding(friendId, call_index);
}
// emit static_cast<Core*>(core)->avEnding(friendId, call_index);
//}
void Core::onAvRequestTimeout(void* _toxav, int32_t call_index, void* core)
{
@ -480,14 +519,14 @@ void Core::onAvInvite(void* _toxav, int32_t call_index, void* core)
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
if (err != ErrorNone)
if (err != av_ErrorNone)
{
qWarning() << "Core::onAvInvite: error getting call type";
delete transSettings;
return;
}
if (transSettings->call_type == TypeVideo)
if (transSettings->call_type == av_TypeVideo)
{
qDebug() << QString("Core: AV invite from %1 with video").arg(friendId);
emit static_cast<Core*>(core)->avInvite(friendId, call_index, true);
@ -514,14 +553,14 @@ void Core::onAvStart(void* _toxav, int32_t call_index, void* core)
ToxAvCSettings* transSettings = new ToxAvCSettings;
int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings);
if (err != ErrorNone)
if (err != av_ErrorNone)
{
qWarning() << "Core::onAvStart: error getting call type";
delete transSettings;
return;
}
if (transSettings->call_type == TypeVideo)
if (transSettings->call_type == av_TypeVideo)
{
qDebug() << QString("Core: AV start from %1 with video").arg(friendId);
prepareCall(friendId, call_index, toxav, true);
@ -540,26 +579,26 @@ void Core::onAvStart(void* _toxav, int32_t call_index, void* core)
// This function's logic was shamelessly stolen from uTox
void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate)
{
if(!channels || channels > 2)
if (!channels || channels > 2)
{
qWarning() << "Core::playAudioBuffer: trying to play on "<<channels<<" channels! Giving up.";
return;
}
ALuint bufid;
ALint processed, queued;
ALint processed = 0, queued = 16;
alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued);
alSourcei(alSource, AL_LOOPING, AL_FALSE);
if(processed)
if (processed)
{
ALuint bufids[processed];
alSourceUnqueueBuffers(alSource, processed, bufids);
alDeleteBuffers(processed - 1, bufids + 1);
bufid = bufids[0];
}
else if(queued < 16)
else if (queued < 32)
{
alGenBuffers(1, &bufid);
}
@ -575,10 +614,10 @@ void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, un
ALint state;
alGetSourcei(alSource, AL_SOURCE_STATE, &state);
if(state != AL_PLAYING)
if (state != AL_PLAYING)
{
alSourcePlay(alSource);
qDebug() << "Core: Starting audio source " << (int)alSource;
//qDebug() << "Core: Starting audio source " << (int)alSource;
}
}
@ -587,10 +626,109 @@ VideoSource *Core::getVideoSourceFromCall(int callNumber)
return &calls[callNumber].videoSource;
}
void Core::playGroupAudio(Tox* /*tox*/, int /*groupnumber*/, int /*friendgroupnumber*/, const int16_t* out_audio,
unsigned out_audio_samples, uint8_t decoder_channels, unsigned audio_sample_rate, void* /*userdata*/)
void Core::joinGroupCall(int groupId)
{
/// TODO: FIXME: Don't play groupchat audio on the main source!
/// We'll need some sort of call[] array but for groupchats, when that's done use this source
playAudioBuffer(alMainSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate);
qDebug() << QString("Core: Joining group call %1").arg(groupId);
groupCalls[groupId].groupId = groupId;
groupCalls[groupId].muteMic = false;
groupCalls[groupId].muteVol = false;
// the following three lines are also now redundant from startCall, but are
// necessary there for outbound and here for inbound
groupCalls[groupId].codecSettings = av_DefaultSettings;
groupCalls[groupId].codecSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH;
groupCalls[groupId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT;
// Audio
Audio::suscribeInput();
// Go
Core* core = Core::getInstance();
ToxAv* toxav = core->toxav;
groupCalls[groupId].sendAudioTimer = new QTimer();
groupCalls[groupId].active = true;
groupCalls[groupId].sendAudioTimer->setInterval(5);
groupCalls[groupId].sendAudioTimer->setSingleShot(true);
connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=](){sendGroupCallAudio(groupId,toxav);});
groupCalls[groupId].sendAudioTimer->start();
}
void Core::leaveGroupCall(int groupId)
{
qDebug() << QString("Core: Leaving group call %1").arg(groupId);
groupCalls[groupId].active = false;
disconnect(groupCalls[groupId].sendAudioTimer,0,0,0);
groupCalls[groupId].sendAudioTimer->stop();
groupCalls[groupId].alSources.clear();
Audio::unsuscribeInput();
delete groupCalls[groupId].sendAudioTimer;
}
void Core::sendGroupCallAudio(int groupId, ToxAv* toxav)
{
if (!groupCalls[groupId].active)
return;
if (groupCalls[groupId].muteMic || !Audio::alInDev)
{
groupCalls[groupId].sendAudioTimer->start();
return;
}
const int framesize = (groupCalls[groupId].codecSettings.audio_frame_duration * groupCalls[groupId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels;
const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels;
uint8_t buf[bufsize];
bool frame = false;
ALint samples;
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if (samples >= framesize)
{
memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind)
alcCaptureSamples(Audio::alInDev, buf, framesize);
frame = 1;
}
if (frame)
{
int r;
if ((r = toxav_group_send_audio(toxav_get_tox(toxav), groupId, (int16_t*)buf,
framesize, av_DefaultSettings.audio_channels, av_DefaultSettings.audio_sample_rate)) < 0)
{
qDebug() << "Core: toxav_group_send_audio error";
groupCalls[groupId].sendAudioTimer->start();
return;
}
}
groupCalls[groupId].sendAudioTimer->start();
}
void Core::disableGroupCallMic(int groupId)
{
groupCalls[groupId].muteMic = true;
}
void Core::disableGroupCallVol(int groupId)
{
groupCalls[groupId].muteVol = true;
}
void Core::enableGroupCallMic(int groupId)
{
groupCalls[groupId].muteMic = false;
}
void Core::enableGroupCallVol(int groupId)
{
groupCalls[groupId].muteVol = false;
}
bool Core::isGroupCallMicEnabled(int groupId)
{
return !groupCalls[groupId].muteMic;
}
bool Core::isGroupCallVolEnabled(int groupId)
{
return !groupCalls[groupId].muteVol;
}

View File

@ -1,6 +1,7 @@
#ifndef COREAV_H
#define COREAV_H
#include <QHash>
#include <tox/toxav.h>
#include "video/netvideosource.h"
@ -16,7 +17,6 @@ class QTimer;
struct ToxCall
{
public:
ToxAvCSettings codecSettings;
QTimer *sendAudioTimer, *sendVideoTimer;
int callId;
@ -29,4 +29,15 @@ public:
NetVideoSource videoSource;
};
struct ToxGroupCall
{
ToxAvCSettings codecSettings;
QTimer *sendAudioTimer;
int groupId;
bool active = false;
bool muteMic;
bool muteVol;
QHash<int, ALuint> alSources;
};
#endif // COREAV_H

View File

@ -18,6 +18,8 @@
#include "core.h"
#include "misc/settings.h"
#include "misc/style.h"
#include "src/friendlist.h"
#include "src/friend.h"
#include <math.h>
#include <QFileDialog>
#include <QMessageBox>
@ -208,9 +210,9 @@ bool isFileWritable(QString& path)
void FileTransferInstance::acceptRecvRequest()
{
QString path = Settings::getInstance().getAutoAcceptDir(Core::getInstance()->getFriendAddress(friendId));
QString path = Settings::getInstance().getAutoAcceptDir(FriendList::findFriend(friendId)->getToxID());
if (path.isEmpty())
if (path.isEmpty() && Settings::getInstance().getAutoSaveEnabled())
path = Settings::getInstance().getGlobalAutoAcceptDir();
if (!path.isEmpty())

View File

@ -19,15 +19,22 @@
#include "widget/friendwidget.h"
#include "widget/form/chatform.h"
#include "widget/widget.h"
#include "src/core.h"
#include "src/misc/settings.h"
Friend::Friend(int FriendId, QString UserId)
Friend::Friend(int FriendId, const ToxID &UserId)
: friendId(FriendId)
{
hasNewEvents = 0;
friendStatus = Status::Offline;
userID = ToxID::fromString(UserId);
userName = UserId;
widget = new FriendWidget(friendId, UserId);
userID = UserId;
userName = Core::getInstance()->getPeerName(UserId);
if (userName.size() == 0)
userName = UserId.publicKey;
userAlias = Settings::getInstance().getFriendAlias(UserId);
widget = new FriendWidget(friendId, getDisplayedName());
chatForm = new ChatForm(this);
}
@ -46,7 +53,7 @@ void Friend::setName(QString name)
chatForm->setName(name);
if (widget->isActive())
Widget::getInstance()->setWindowTitle(name + " - qTox");
Widget::getInstance()->setWindowTitle(name);
}
}
@ -57,6 +64,9 @@ void Friend::setAlias(QString name)
widget->setName(dispName);
chatForm->setName(dispName);
if (widget->isActive())
Widget::getInstance()->setWindowTitle(dispName);
}
void Friend::setStatusMessage(QString message)

View File

@ -26,7 +26,7 @@ class ChatForm;
struct Friend
{
public:
Friend(int FriendId, QString UserId);
Friend(int FriendId, const ToxID &UserId);
~Friend();
void setName(QString name);

View File

@ -24,7 +24,7 @@
QHash<int, Friend*> FriendList::friendList;
QHash<QString, int> FriendList::tox2id;
Friend* FriendList::addFriend(int friendId, const QString& userId)
Friend* FriendList::addFriend(int friendId, const ToxID& userId)
{
auto friendChecker = friendList.find(friendId);
if (friendChecker != friendList.end())
@ -32,7 +32,7 @@ Friend* FriendList::addFriend(int friendId, const QString& userId)
Friend* newfriend = new Friend(friendId, userId);
friendList[friendId] = newfriend;
tox2id[userId] = friendId;
tox2id[userId.publicKey] = friendId;
return newfriend;
}
@ -61,17 +61,18 @@ void FriendList::clear()
{
for (auto friendptr : friendList)
delete friendptr;
friendList.clear();
}
Friend* FriendList::findFriend(QString userId)
Friend* FriendList::findFriend(const ToxID& userId)
{
auto id = tox2id.find(userId);
auto id = tox2id.find(userId.publicKey);
if (id != tox2id.end())
{
Friend *f = findFriend(*id);
if (!f)
return nullptr;
if (f->getToxID() == ToxID::fromString(userId))
if (f->getToxID() == userId)
return f;
}

View File

@ -21,14 +21,14 @@ template <class T> class QList;
template <class A, class B> class QHash;
struct Friend;
class QString;
struct ToxID;
class FriendList
{
public:
FriendList();
static Friend* addFriend(int friendId, const QString& userId);
static Friend* addFriend(int friendId, const ToxID &userId);
static Friend* findFriend(int friendId);
static Friend* findFriend(QString userId);
static Friend* findFriend(const ToxID &userId);
static QList<Friend*> getAllFriends();
static void removeFriend(int friendId, bool fake = false);
static void clear();

View File

@ -20,11 +20,12 @@
#include "friendlist.h"
#include "friend.h"
#include "core.h"
#include "widget/widget.h"
#include <QDebug>
#include <QTimer>
Group::Group(int GroupId, QString Name)
: groupId(GroupId), nPeers{0}
Group::Group(int GroupId, QString Name, bool IsAvGroupchat)
: groupId(GroupId), nPeers{0}, avGroupchat{IsAvGroupchat}
{
widget = new GroupWidget(groupId, Name);
chatForm = new GroupChatForm(this);
@ -42,6 +43,7 @@ Group::~Group()
delete widget;
}
/*
void Group::addPeer(int peerId, QString name)
{
if (peers.contains(peerId))
@ -62,10 +64,119 @@ void Group::removePeer(int peerId)
widget->onUserListChanged();
chatForm->onUserListChanged();
}
*/
void Group::updatePeer(int peerId, QString name)
{
ToxID id = Core::getInstance()->getGroupPeerToxID(groupId, peerId);
QString toxid = id.publicKey;
peers[peerId] = name;
toxids[toxid] = name;
Friend *f = FriendList::findFriend(id);
if (f)
{
peers[peerId] = f->getDisplayedName();
toxids[toxid] = f->getDisplayedName();
}
widget->onUserListChanged();
chatForm->onUserListChanged();
}
void Group::setName(const QString& name)
{
widget->setName(name);
chatForm->setName(name);
if (widget->isActive())
Widget::getInstance()->setWindowTitle(name);
}
void Group::regeneratePeerList()
{
QList<QString> peerLst = Core::getInstance()->getGroupPeerNames(groupId);
peers.clear();
toxids.clear();
nPeers = peerLst.size();
for (int i = 0; i < peerLst.size(); i++)
{
ToxID id = Core::getInstance()->getGroupPeerToxID(groupId, i);
QString toxid = id.publicKey;
peers[i] = peerLst.at(i);
toxids[toxid] = peerLst.at(i);
Friend *f = FriendList::findFriend(id);
if (f)
{
peers[i] = f->getDisplayedName();
toxids[toxid] = f->getDisplayedName();
}
}
widget->onUserListChanged();
chatForm->onUserListChanged();
}
bool Group::isAvGroupchat() const
{
return avGroupchat;
}
int Group::getGroupId() const
{
return groupId;
}
int Group::getPeersCount() const
{
return nPeers;
}
GroupChatForm *Group::getChatForm()
{
return chatForm;
}
GroupWidget *Group::getGroupWidget()
{
return widget;
}
QStringList Group::getPeerList() const
{
return peers.values();
}
void Group::setEventFlag(int f)
{
hasNewMessages = f;
}
int Group::getEventFlag() const
{
return hasNewMessages;
}
void Group::setMentionedFlag(int f)
{
userWasMentioned = f;
}
int Group::getMentionedFlag() const
{
return userWasMentioned;
}
QString Group::resolveToxID(const ToxID &id) const
{
QString key = id.publicKey;
auto it = toxids.find(key);
if (it != toxids.end())
{
return *it;
}
return QString();
}

View File

@ -25,24 +25,50 @@
struct Friend;
class GroupWidget;
class GroupChatForm;
struct ToxID;
class Group : public QObject
{
Q_OBJECT
public:
Group(int GroupId, QString Name);
~Group();
Group(int GroupId, QString Name, bool IsAvGroupchat);
virtual ~Group();
bool isAvGroupchat() const;
int getGroupId() const;
int getPeersCount() const;
void regeneratePeerList();
QStringList getPeerList() const;
GroupChatForm *getChatForm();
GroupWidget *getGroupWidget();
void setEventFlag(int f);
int getEventFlag() const;
void setMentionedFlag(int f);
int getMentionedFlag() const;
/*
void addPeer(int peerId, QString name);
void removePeer(int peerId);
void updatePeer(int peerId, QString newName);
*/
public:
int groupId;
QMap<int,QString> peers;
int nPeers;
void updatePeer(int peerId, QString newName);
void setName(const QString& name);
QString resolveToxID(const ToxID &id) const;
private:
GroupWidget* widget;
GroupChatForm* chatForm;
QMap<int, QString> peers;
QMap<QString, QString> toxids;
int hasNewMessages, userWasMentioned;
int groupId;
int nPeers;
bool avGroupchat;
};
#endif // GROUP_H

View File

@ -16,32 +16,54 @@
#include "grouplist.h"
#include "group.h"
#include <QHash>
#include <QDebug>
QList<Group*> GroupList::groupList;
QHash<int, Group*> GroupList::groupList;
Group* GroupList::addGroup(int groupId, const QString& name)
Group* GroupList::addGroup(int groupId, const QString& name, bool isAvGroupchat)
{
Group* newGroup = new Group(groupId, name);
groupList.append(newGroup);
auto checker = groupList.find(groupId);
if (checker != groupList.end())
qWarning() << "GroupList::addGroup: groupId already taken";
Group* newGroup = new Group(groupId, name, isAvGroupchat);
groupList[groupId] = newGroup;
return newGroup;
}
Group* GroupList::findGroup(int groupId)
{
for (Group* g : groupList)
if (g->groupId == groupId)
return g;
auto g_it = groupList.find(groupId);
if (g_it != groupList.end())
return *g_it;
return nullptr;
}
void GroupList::removeGroup(int groupId, bool /*fake*/)
{
for (int i=0; i<groupList.size(); i++)
auto g_it = groupList.find(groupId);
if (g_it != groupList.end())
{
if (groupList[i]->groupId == groupId)
{
groupList.removeAt(i);
return;
}
groupList.erase(g_it);
}
}
QList<Group *> GroupList::getAllGroups()
{
QList<Group*> res;
for (auto it : groupList)
res.append(it);
return res;
}
void GroupList::clear()
{
for (auto groupptr : groupList)
delete groupptr;
groupList.clear();
}

View File

@ -17,21 +17,22 @@
#ifndef GROUPLIST_H
#define GROUPLIST_H
template <typename T>
class QList;
template <class A, class B> class QHash;
template <class T> class QList;
class Group;
class QString;
class GroupList
{
public:
GroupList();
static Group* addGroup(int groupId, const QString& name);
static Group* addGroup(int groupId, const QString& name, bool isAvGroupchat);
static Group* findGroup(int groupId);
static void removeGroup(int groupId, bool fake = false);
static QList<Group*> getAllGroups();
static void clear();
public:
static QList<Group*> groupList;
private:
static QHash<int, Group*> groupList;
};
#endif // GROUPLIST_H

View File

@ -122,7 +122,7 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) :
if (idCur != idMax)
{
QString cmd = QString("INSERT INTO sent_status (id, status) VALUES (%1, 1)").arg(idMax);
QString cmd = QString("INSERT INTO sent_status (id, status) VALUES (%1, 1);").arg(idMax);
db->exec(cmd);
}
}
@ -130,6 +130,8 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) :
updateChatsID();
updateAliases();
setSyncType(Settings::getInstance().getDbSyncType());
QSqlQuery sqlAnswer = db->exec("select seq from sqlite_sequence where name=\"history\";");
sqlAnswer.first();
messageID = sqlAnswer.value(0).toInt();
@ -145,10 +147,12 @@ int HistoryKeeper::addChatEntry(const QString& chat, const QString& message, con
int chat_id = getChatID(chat, ctSingle).first;
int sender_id = getAliasID(sender);
db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message)") +
db->exec("BEGIN TRANSACTION;");
db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message) ") +
QString("VALUES (%1, %2, %3, '%4');")
.arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message)));
db->exec(QString("INSERT INTO sent_status (status) VALUES (%1)").arg(isSent));
db->exec(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent));
db->exec("COMMIT TRANSACTION;");
messageID++;
return messageID;
@ -287,12 +291,13 @@ HistoryKeeper::ChatType HistoryKeeper::convertToChatType(int ct)
return static_cast<ChatType>(ct);
}
QString HistoryKeeper::getHistoryPath()
QString HistoryKeeper::getHistoryPath(QString currentProfile, int encrypted)
{
QDir baseDir(Settings::getInstance().getSettingsDirPath());
QString currentProfile = Settings::getInstance().getCurrentProfile();
QDir baseDir(Settings::getSettingsDirPath());
if (currentProfile.isEmpty())
currentProfile = Settings::getInstance().getCurrentProfile();
if (Settings::getInstance().getEncryptLogs())
if (encrypted == 1 || (encrypted == -1 && Settings::getInstance().getEncryptLogs()))
return baseDir.filePath(currentProfile + ".qtox_history.encrypted");
else
return baseDir.filePath(currentProfile + ".qtox_history");
@ -315,3 +320,25 @@ void HistoryKeeper::markAsSent(int m_id)
{
db->exec(QString("UPDATE sent_status SET status = 1 WHERE id = %1;").arg(m_id));
}
void HistoryKeeper::setSyncType(Db::syncType sType)
{
QString syncCmd;
switch (sType) {
case Db::syncType::stFull:
syncCmd = "FULL";
break;
case Db::syncType::stNormal:
syncCmd = "NORMAL";
break;
case Db::syncType::stOff:
syncCmd = "OFF";
break;
default:
syncCmd = "FULL";
break;
}
db->exec(QString("PRAGMA synchronous=%1;").arg(syncCmd));
}

View File

@ -22,6 +22,7 @@
#include <QDateTime>
class GenericDdInterface;
namespace Db { enum class syncType; }
class HistoryKeeper
{
@ -42,7 +43,7 @@ public:
static HistoryKeeper* getInstance();
static void resetInstance();
static QString getHistoryPath();
static QString getHistoryPath(QString currentProfile = QString(), int encrypted = -1); // -1 defaults to checking settings, 0 or 1 to specify
static bool checkPassword();
static void renameHistory(QString from, QString to);
@ -51,6 +52,8 @@ public:
QList<HistMessage> getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to);
void markAsSent(int m_id);
void setSyncType(Db::syncType sType);
private:
HistoryKeeper(GenericDdInterface *db_);
HistoryKeeper(HistoryKeeper &hk) = delete;
@ -68,7 +71,6 @@ private:
GenericDdInterface *db;
QMap<QString, int> aliases;
QMap<QString, QPair<int, ChatType>> chats;
bool isEncrypted;
int messageID;
};

View File

@ -71,7 +71,9 @@ IPC::~IPC()
{
if (globalMemory.lock())
{
*(time_t*)((char*)globalMemory.data()+sizeof(globalId)) = 0;
char* data = (char*)globalMemory.data();
if (data)
*(time_t*)(data+sizeof(globalId)) = 0;
globalMemory.unlock();
}
}
@ -132,7 +134,7 @@ void IPC::processEvents()
}
else
{
qWarning() << "IPC:processEvents failed to lock";
//qWarning() << "IPC:processEvents failed to lock";
goto restartTimer;
}

View File

@ -21,11 +21,12 @@
#include "src/widget/toxsave.h"
#include "src/autoupdate.h"
#include <QApplication>
#include <QFontDatabase>
#include <QDebug>
#include <QFile>
#include <QDir>
#include <QCommandLineParser>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFontDatabase>
#include <QMutexLocker>
#include <sodium.h>
@ -57,8 +58,20 @@ int main(int argc, char *argv[])
QApplication a(argc, argv);
a.setApplicationName("qTox");
a.setOrganizationName("Tox");
a.setApplicationVersion("\nGit commit: " + QString(GIT_VERSION));
// Process arguments
QCommandLineParser parser;
parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION) + "\nBuilt: " + __TIME__ + " " + __DATE__);
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse"));
parser.addOption(QCommandLineOption("P", QObject::tr("Starts new instance and loads specified profile."), QObject::tr("profile")));
parser.process(a);
Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before
if (parser.isSet("P"))
Settings::getInstance().setCurrentProfile(parser.value("P"));
sodium_init(); // For the auto-updater
@ -83,8 +96,8 @@ int main(int argc, char *argv[])
// Windows platform plugins DLL hell fix
QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
a.addLibraryPath("platforms");
qDebug() << "built on: " << __TIME__ << __DATE__;
qDebug() << "built on: " << __TIME__ << __DATE__ << "(" << TIMESTAMP << ")";
qDebug() << "commit: " << GIT_VERSION << "\n";
// Install Unicode 6.1 supporting font
@ -100,11 +113,11 @@ int main(int argc, char *argv[])
IPC ipc;
ipc.registerEventHandler(&toxURIEventHandler);
ipc.registerEventHandler(&toxSaveEventHandler);
ipc.registerEventHandler(&toxActivateEventHandler);
// Process arguments
if (argc >= 2)
if (parser.positionalArguments().size() > 0)
{
QString firstParam(argv[1]);
QString firstParam(parser.positionalArguments()[0]);
// Tox URIs. If there's already another qTox instance running, we ask it to handle the URI and we exit
// Otherwise we start a new qTox instance and process it ourselves
if (firstParam.startsWith("tox:"))
@ -137,10 +150,22 @@ int main(int argc, char *argv[])
return EXIT_SUCCESS;
}
}
else
{
fprintf(stderr, "Invalid argument\n");
return EXIT_FAILURE;
}
}
else if (!ipc.isCurrentOwner() && !parser.isSet("P"))
{
time_t event = ipc.postEvent("$activate");
ipc.waitUntilProcessed(event);
if (!ipc.isCurrentOwner())
return EXIT_SUCCESS;
}
// Run
Widget* w = Widget::getInstance();
Widget* w = Widget::getInstance();
int errorcode = a.exec();
delete w;

View File

@ -1034,7 +1034,7 @@ QSplitter:handle{
<x>0</x>
<y>0</y>
<width>284</width>
<height>401</height>
<height>399</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5"/>
@ -1053,16 +1053,7 @@ QSplitter:handle{
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">QPushButton{
background-color:#1c1c1c;
border:none;
}
QPushButton:hover{
background-color:#292929;
border:none;
}
</string>
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
@ -1146,7 +1137,7 @@ QPushButton:hover{
</property>
<property name="icon">
<iconset resource="../res.qrc">
<normaloff>:/img/group_2x.png</normaloff>:/img/group_2x.png</iconset>
<normaloff>:/img/group_button.png</normaloff>:/img/group_button.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
@ -1674,7 +1665,7 @@ QPushButton:hover{
<property name="minimumSize">
<size>
<width>0</width>
<height>57</height>
<height>0</height>
</size>
</property>
<property name="maximumSize">
@ -1784,7 +1775,7 @@ QPushButton:hover{
<x>0</x>
<y>0</y>
<width>775</width>
<height>19</height>
<height>21</height>
</rect>
</property>
</widget>

View File

@ -21,6 +21,10 @@
#include <QSqlDatabase>
namespace Db {
enum class syncType : int {stOff = 0, stNormal = 1, stFull = 2};
}
class PlainDb : public GenericDdInterface
{
public:

View File

@ -17,6 +17,7 @@
#include "settings.h"
#include "smileypack.h"
#include "src/corestructs.h"
#include "src/misc/db/plaindb.h"
#include <QFont>
#include <QApplication>
@ -28,7 +29,16 @@
#include <QList>
#include <QStyleFactory>
const QString Settings::FILENAME = "settings.ini";
#ifdef Q_OS_LINUX
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) false
#else // OS is not linux
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
#endif
const QString Settings::OLDFILENAME = "settings.ini";
const QString Settings::FILENAME = "qtox.ini";
Settings* Settings::settings{nullptr};
bool Settings::makeToxPortable{false};
Settings::Settings() :
@ -39,36 +49,53 @@ Settings::Settings() :
Settings& Settings::getInstance()
{
static Settings* settings{nullptr};
if (!settings)
settings = new Settings();
return *settings;
}
void Settings::resetInstance()
{
if (settings)
{
delete settings;
settings = nullptr;
}
}
void Settings::load()
{
if (loaded) {
if (loaded)
return;
}
QFile portableSettings(FILENAME);
if (portableSettings.exists())
if (QFile(FILENAME).exists())
{
QSettings ps(FILENAME, QSettings::IniFormat);
ps.beginGroup("General");
makeToxPortable = ps.value("makeToxPortable", false).toBool();
ps.endGroup();
}
else if (QFile(OLDFILENAME).exists())
{
QSettings ps(OLDFILENAME, QSettings::IniFormat);
ps.beginGroup("General");
makeToxPortable = ps.value("makeToxPortable", false).toBool();
ps.endGroup();
}
else
makeToxPortable = false;
QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME);
QDir dir(getSettingsDirPath());
QString filePath = dir.filePath(FILENAME);
//if no settings file exist -- use the default one
QFile file(filePath);
if (!file.exists()) {
qDebug() << "No settings file found, using defaults";
filePath = ":/conf/" + FILENAME;
if (!QFile(filePath).exists())
{
if (!QFile(filePath = dir.filePath(OLDFILENAME)).exists())
{
qDebug() << "No settings file found, using defaults";
filePath = ":/conf/" + FILENAME;
}
}
qDebug() << "Settings: Loading from "<<filePath;
@ -95,35 +122,31 @@ void Settings::load()
useCustomDhtList=false;
s.endGroup();
friendLst.clear();
s.beginGroup("Friends");
int size = s.beginReadArray("fullAddresses");
for (int i = 0; i < size; i ++)
{
s.setArrayIndex(i);
friendProp fp;
fp.addr = s.value("addr").toString();
fp.alias = s.value("alias").toString();
friendLst[ToxID::fromString(fp.addr).publicKey] = fp;
}
s.endArray();
s.endGroup();
s.beginGroup("General");
enableIPv6 = s.value("enableIPv6", true).toBool();
translation = s.value("translation", "en").toString();
showSystemTray = s.value("showSystemTray", SHOW_SYSTEM_TRAY_DEFAULT).toBool();
makeToxPortable = s.value("makeToxPortable", false).toBool();
autostartInTray = s.value("autostartInTray", false).toBool();
closeToTray = s.value("closeToTray", false).toBool();
forceTCP = s.value("forceTCP", false).toBool();
useProxy = s.value("useProxy", false).toBool();
setProxyType(s.value("proxyType", static_cast<int>(ProxyType::ptNone)).toInt());
proxyAddr = s.value("proxyAddr", "").toString();
proxyPort = s.value("proxyPort", 0).toInt();
currentProfile = s.value("currentProfile", "").toString();
autoAwayTime = s.value("autoAwayTime", 10).toInt();
autoAwayTime = s.value("autoAwayTime", 10).toInt();
checkUpdates = s.value("checkUpdates", false).toBool();
showInFront = s.value("showInFront", false).toBool();
fauxOfflineMessaging = s.value("fauxOfflineMessaging", true).toBool();
autoSaveEnabled = s.value("autoSaveEnabled", false).toBool();
globalAutoAcceptDir = s.value("globalAutoAcceptDir",
QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory)
).toString();
s.endGroup();
s.beginGroup("Advanced");
int sType = s.value("dbSyncType", static_cast<int>(Db::syncType::stFull)).toInt();
setDbSyncType(sType);
s.endGroup();
s.beginGroup("Widgets");
@ -138,15 +161,17 @@ void Settings::load()
smileyPack = s.value("smileyPack", ":/smileys/cylgom/emoticons.xml").toString();
customEmojiFont = s.value("customEmojiFont", true).toBool();
emojiFontFamily = s.value("emojiFontFamily", "DejaVu Sans").toString();
emojiFontPointSize = s.value("emojiFontPointSize", QApplication::font().pointSize()).toInt();
emojiFontPointSize = s.value("emojiFontPointSize", 12).toInt();
firstColumnHandlePos = s.value("firstColumnHandlePos", 50).toInt();
secondColumnHandlePosFromRight = s.value("secondColumnHandlePosFromRight", 50).toInt();
timestampFormat = s.value("timestampFormat", "hh:mm").toString();
minimizeOnClose = s.value("minimizeOnClose", false).toBool();
minimizeToTray = s.value("minimizeToTray", false).toBool();
lightTrayIcon = s.value("lightTrayIcon", false).toBool();
useNativeStyle = s.value("nativeStyle", false).toBool();
useEmoticons = s.value("useEmoticons", true).toBool();
statusChangeNotificationEnabled = s.value("statusChangeNotificationEnabled", false).toBool();
themeColor = s.value("themeColor", 0).toInt();
style = s.value("style", "").toString();
if (style == "") // Default to Fusion if available, otherwise no style
{
@ -170,19 +195,10 @@ void Settings::load()
encryptTox = s.value("encryptTox", false).toBool();
s.endGroup();
s.beginGroup("AutoAccept");
autoSaveEnabled = s.value("autoSaveEnabled", false).toBool();
globalAutoAcceptDir = s.value("globalAutoAcceptDir",
QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory)
).toString();
for (auto& key : s.childKeys())
autoAccept[key] = s.value(key).toString();
s.endGroup();
s.beginGroup("Audio");
inDev = s.value("inDev", "").toString();
outDev = s.value("outDev", "").toString();
filterAudio = s.value("filterAudio", false).toBool();
s.endGroup();
// Read the embedded DHT bootsrap nodes list if needed
@ -206,15 +222,39 @@ void Settings::load()
}
loaded = true;
if (currentProfile.isEmpty()) // new profile in Core::switchConfiguration
return;
// load from a profile specific friend data list if possible
QString tmp = dir.filePath(currentProfile + ".ini");
if (QFile(tmp).exists())
filePath = tmp;
QSettings fs(filePath, QSettings::IniFormat);
friendLst.clear();
fs.beginGroup("Friends");
int size = fs.beginReadArray("Friend");
for (int i = 0; i < size; i ++)
{
fs.setArrayIndex(i);
friendProp fp;
fp.addr = fs.value("addr").toString();
fp.alias = fs.value("alias").toString();
fp.autoAcceptDir = fs.value("autoAcceptDir").toString();
friendLst[ToxID::fromString(fp.addr).publicKey] = fp;
}
fs.endArray();
fs.endGroup();
}
void Settings::save()
void Settings::save(bool writeFriends)
{
QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME);
save(filePath);
save(filePath, writeFriends);
}
void Settings::save(QString path)
void Settings::save(QString path, bool writeFriends)
{
qDebug() << "Settings: Saving in "<<path;
@ -235,26 +275,14 @@ void Settings::save(QString path)
s.endArray();
s.endGroup();
s.beginGroup("Friends");
s.beginWriteArray("fullAddresses", friendLst.size());
int index = 0;
for (auto &frnd : friendLst)
{
s.setArrayIndex(index);
s.setValue("addr", frnd.addr);
s.setValue("alias", frnd.alias);
index++;
}
s.endArray();
s.endGroup();
s.beginGroup("General");
s.setValue("enableIPv6", enableIPv6);
s.setValue("translation",translation);
s.setValue("makeToxPortable",makeToxPortable);
s.setValue("showSystemTray", showSystemTray);
s.setValue("autostartInTray",autostartInTray);
s.setValue("closeToTray", closeToTray);
s.setValue("useProxy", useProxy);
s.setValue("proxyType", static_cast<int>(proxyType));
s.setValue("forceTCP", forceTCP);
s.setValue("proxyAddr", proxyAddr);
s.setValue("proxyPort", proxyPort);
@ -263,6 +291,12 @@ void Settings::save(QString path)
s.setValue("checkUpdates", checkUpdates);
s.setValue("showInFront", showInFront);
s.setValue("fauxOfflineMessaging", fauxOfflineMessaging);
s.setValue("autoSaveEnabled", autoSaveEnabled);
s.setValue("globalAutoAcceptDir", globalAutoAcceptDir);
s.endGroup();
s.beginGroup("Advanced");
s.setValue("dbSyncType", static_cast<int>(dbSyncType));
s.endGroup();
s.beginGroup("Widgets");
@ -283,8 +317,10 @@ void Settings::save(QString path)
s.setValue("timestampFormat", timestampFormat);
s.setValue("minimizeOnClose", minimizeOnClose);
s.setValue("minimizeToTray", minimizeToTray);
s.setValue("lightTrayIcon", lightTrayIcon);
s.setValue("nativeStyle", useNativeStyle);
s.setValue("useEmoticons", useEmoticons);
s.setValue("themeColor", themeColor);
s.setValue("style", style);
s.setValue("statusChangeNotificationEnabled", statusChangeNotificationEnabled);
s.endGroup();
@ -302,17 +338,29 @@ void Settings::save(QString path)
s.setValue("encryptTox", encryptTox);
s.endGroup();
s.beginGroup("AutoAccept");
s.setValue("autoSaveEnabled", autoSaveEnabled);
s.setValue("globalAutoAcceptDir", globalAutoAcceptDir);
for (auto& id : autoAccept.keys())
s.setValue(id, autoAccept.value(id));
s.endGroup();
s.beginGroup("Audio");
s.setValue("inDev", inDev);
s.setValue("outDev", outDev);
s.setValue("filterAudio", filterAudio);
s.endGroup();
if (!writeFriends || currentProfile.isEmpty()) // Core::switchConfiguration
return;
QSettings fs(QFileInfo(path).dir().filePath(currentProfile + ".ini"), QSettings::IniFormat);
fs.beginGroup("Friends");
fs.beginWriteArray("Friend", friendLst.size());
int index = 0;
for (auto& frnd : friendLst)
{
fs.setArrayIndex(index);
fs.setValue("addr", frnd.addr);
fs.setValue("alias", frnd.alias);
fs.setValue("autoAcceptDir", frnd.autoAcceptDir);
index++;
}
fs.endArray();
fs.endGroup();
}
QString Settings::getSettingsDirPath()
@ -430,6 +478,16 @@ void Settings::setStyle(const QString& newStyle)
style = newStyle;
}
bool Settings::getShowSystemTray() const
{
return showSystemTray;
}
void Settings::setShowSystemTray(const bool& newValue)
{
showSystemTray = newValue;
}
void Settings::setUseEmoticons(bool newValue)
{
useEmoticons = newValue;
@ -476,6 +534,16 @@ void Settings::setMinimizeToTray(bool newValue)
minimizeToTray = newValue;
}
bool Settings::getLightTrayIcon() const
{
return lightTrayIcon;
}
void Settings::setLightTrayIcon(bool newValue)
{
lightTrayIcon = newValue;
}
bool Settings::getStatusChangeNotificationEnabled() const
{
return statusChangeNotificationEnabled;
@ -516,13 +584,17 @@ void Settings::setForceTCP(bool newValue)
forceTCP = newValue;
}
bool Settings::getUseProxy() const
ProxyType Settings::getProxyType() const
{
return useProxy;
return proxyType;
}
void Settings::setUseProxy(bool newValue)
void Settings::setProxyType(int newValue)
{
useProxy = newValue;
if (newValue >= 0 && newValue <= 2)
proxyType = static_cast<ProxyType>(newValue);
else
proxyType = ProxyType::ptNone;
}
QString Settings::getProxyAddr() const
@ -585,6 +657,19 @@ void Settings::setEncryptTox(bool newValue)
encryptTox = newValue;
}
Db::syncType Settings::getDbSyncType() const
{
return dbSyncType;
}
void Settings::setDbSyncType(int newValue)
{
if (newValue >= 0 && newValue <= 2)
dbSyncType = static_cast<Db::syncType>(newValue);
else
dbSyncType = Db::syncType::stFull;
}
int Settings::getAutoAwayTime() const
{
return autoAwayTime;
@ -597,17 +682,31 @@ void Settings::setAutoAwayTime(int newValue)
autoAwayTime = newValue;
}
QString Settings::getAutoAcceptDir(const QString& id) const
QString Settings::getAutoAcceptDir(const ToxID& id) const
{
return autoAccept.value(id.left(TOX_ID_PUBLIC_KEY_LENGTH));
QString key = id.publicKey;
auto it = friendLst.find(key);
if (it != friendLst.end())
{
return it->autoAcceptDir;
}
return QString();
}
void Settings::setAutoAcceptDir(const QString& id, const QString& dir)
void Settings::setAutoAcceptDir(const ToxID &id, const QString& dir)
{
if (dir.isEmpty())
autoAccept.remove(id.left(TOX_ID_PUBLIC_KEY_LENGTH));
else
autoAccept[id.left(TOX_ID_PUBLIC_KEY_LENGTH)] = dir;
QString key = id.publicKey;
auto it = friendLst.find(key);
if (it != friendLst.end())
{
it->autoAcceptDir = dir;
} else {
updateFriendAdress(id.toString());
setAutoAcceptDir(id, dir);
}
}
QString Settings::getGlobalAutoAcceptDir() const
@ -805,6 +904,16 @@ void Settings::setOutDev(const QString& deviceSpecifier)
outDev = deviceSpecifier;
}
bool Settings::getFilterAudio() const
{
return filterAudio;
}
void Settings::setFilterAudio(bool newValue)
{
filterAudio = newValue;
}
QString Settings::getFriendAdress(const QString &publicKey) const
{
QString key = ToxID::fromString(publicKey).publicKey;
@ -828,6 +937,7 @@ void Settings::updateFriendAdress(const QString &newAddr)
friendProp fp;
fp.addr = newAddr;
fp.alias = "";
fp.autoAcceptDir = "";
friendLst[newAddr] = fp;
}
}
@ -855,6 +965,7 @@ void Settings::setFriendAlias(const ToxID &id, const QString &alias)
friendProp fp;
fp.addr = key;
fp.alias = alias;
fp.autoAcceptDir = "";
friendLst[key] = fp;
}
}
@ -874,3 +985,13 @@ void Settings::setFauxOfflineMessaging(bool value)
{
fauxOfflineMessaging = value;
}
int Settings::getThemeColor() const
{
return themeColor;
}
void Settings::setThemeColor(const int &value)
{
themeColor = value;
}

View File

@ -21,13 +21,17 @@
#include <QObject>
#include <QPixmap>
class ToxID;
struct ToxID;
namespace Db { enum class syncType; }
enum ProxyType {ptNone, ptSOCKS5, ptHTTP};
class Settings : public QObject
{
Q_OBJECT
public:
static Settings& getInstance();
static void resetInstance();
~Settings() = default;
void executeSettingsDialog(QWidget* parent);
@ -59,9 +63,15 @@ public:
bool getMinimizeToTray() const;
void setMinimizeToTray(bool newValue);
bool getLightTrayIcon() const;
void setLightTrayIcon(bool newValue);
QString getStyle() const;
void setStyle(const QString& newValue);
bool getShowSystemTray() const;
void setShowSystemTray(const bool& newValue);
bool getUseEmoticons() const;
void setUseEmoticons(bool newValue);
@ -81,8 +91,8 @@ public:
QString getProxyAddr() const;
void setProxyAddr(const QString& newValue);
bool getUseProxy() const;
void setUseProxy(bool newValue);
ProxyType getProxyType() const;
void setProxyType(int newValue);
int getProxyPort() const;
void setProxyPort(int newValue);
@ -96,6 +106,9 @@ public:
bool getEncryptTox() const;
void setEncryptTox(bool newValue);
Db::syncType getDbSyncType() const;
void setDbSyncType(int newValue);
int getAutoAwayTime() const;
void setAutoAwayTime(int newValue);
@ -117,6 +130,9 @@ public:
QString getOutDev() const;
void setOutDev(const QString& deviceSpecifier);
bool getFilterAudio() const;
void setFilterAudio(bool newValue);
// Assume all widgets have unique names
// Don't use it to save every single thing you want to save, use it
// for some general purpose widgets, such as MainWindows or Splitters,
@ -145,6 +161,9 @@ public:
QString getSmileyPack() const;
void setSmileyPack(const QString &value);
int getThemeColor() const;
void setThemeColor(const int& value);
bool isCurstomEmojiFont() const;
void setCurstomEmojiFont(bool value);
@ -154,8 +173,8 @@ public:
int getEmojiFontPointSize() const;
void setEmojiFontPointSize(int value);
QString getAutoAcceptDir(const QString& id) const;
void setAutoAcceptDir(const QString&id, const QString& dir);
QString getAutoAcceptDir(const ToxID& id) const;
void setAutoAcceptDir(const ToxID&id, const QString& dir);
QString getGlobalAutoAcceptDir() const;
void setGlobalAutoAcceptDir(const QString& dir);
@ -205,16 +224,19 @@ public:
void setFauxOfflineMessaging(bool value);
public:
void save();
void save(QString path);
void save(bool writeFriends = true);
void save(QString path, bool writeFriends = true);
void load();
private:
static Settings* settings;
Settings();
Settings(Settings &settings) = delete;
Settings& operator=(const Settings&) = delete;
static const QString FILENAME;
static const QString OLDFILENAME;
bool loaded;
@ -230,13 +252,14 @@ private:
bool autostartInTray;
bool closeToTray;
bool minimizeToTray;
bool lightTrayIcon;
bool useEmoticons;
bool checkUpdates;
bool showInFront;
bool forceTCP;
bool useProxy;
ProxyType proxyType;
QString proxyAddr;
int proxyPort;
@ -265,7 +288,8 @@ private:
QByteArray windowState;
QByteArray splitterState;
QString style;
bool showSystemTray;
// ChatView
int firstColumnHandlePos;
int secondColumnHandlePosFromRight;
@ -274,19 +298,23 @@ private:
// Privacy
bool typingNotification;
Db::syncType dbSyncType;
// Audio
QString inDev;
QString outDev;
bool filterAudio;
struct friendProp
{
QString alias;
QString addr;
QString autoAcceptDir;
};
QHash<QString, friendProp> friendLst;
int themeColor;
signals:
//void dataChanged();

View File

@ -69,9 +69,9 @@ QList<QPair<QString, QString> > SmileyPack::listSmileyPacks(const QStringList &p
if (relPath.leftRef(2) == "..")
{
if(!smileyPacks.contains(QPair<QString, QString>(packageName, absPath)))
if (!smileyPacks.contains(QPair<QString, QString>(packageName, absPath)))
smileyPacks << QPair<QString, QString>(packageName, absPath);
else if(!smileyPacks.contains(QPair<QString, QString>(packageName, relPath)))
else if (!smileyPacks.contains(QPair<QString, QString>(packageName, relPath)))
smileyPacks << QPair<QString, QString>(packageName, relPath); // use relative path for subdirectories
}
}
@ -97,7 +97,7 @@ bool SmileyPack::load(const QString& filename)
// open emoticons.xml
QFile xmlFile(filename);
if(!xmlFile.open(QIODevice::ReadOnly))
if (!xmlFile.open(QIODevice::ReadOnly))
return false; // cannot open file
/* parse the cfg file
@ -134,7 +134,7 @@ bool SmileyPack::load(const QString& filename)
filenameTable.insert(emoticon, file);
cacheSmiley(file); // preload all smileys
if(!getCachedSmiley(emoticon).size().isEmpty())
emoticonSet.push_back(emoticon);
@ -142,7 +142,7 @@ bool SmileyPack::load(const QString& filename)
}
if(emoticonSet.size() > 0)
if (emoticonSet.size() > 0)
emoticons.push_back(emoticonSet);
}

View File

@ -17,6 +17,10 @@
#include "style.h"
#include "settings.h"
#include "src/widget/widget.h"
#include "ui_mainwindow.h"
#include "src/widget/genericchatroomwidget.h"
#include <QFile>
#include <QDebug>
#include <QMap>
@ -42,6 +46,33 @@ QString qssifyFont(QFont font)
.arg(font.family());
}
// colors as defined in
// https://github.com/ItsDuke/Tox-UI/blob/master/UI%20GUIDELINES.md
static QColor palette[] = {
QColor("#6bc260"),
QColor("#cebf44"),
QColor("#c84e4e"),
QColor("#000000"),
QColor("#1c1c1c"),
QColor("#414141"),
QColor("#414141").lighter(120),
QColor("#d1d1d1"),
QColor("#ffffff"),
QColor("#ff7700"),
// Theme colors
QColor("#1c1c1c"),
QColor("#2a2a2a"),
QColor("#414141"),
QColor("#4e4e4e"),
};
static QMap<QString, QString> dict;
QStringList Style::themeColorNames = {QObject::tr("Default"), QObject::tr("Blue"), QObject::tr("Olive"), QObject::tr("Red"), QObject::tr("Violet")};
QList<QColor> Style::themeColorColors = {QColor(), QColor("#004aa4"), QColor("#97ba00"), QColor("#c23716"), QColor("#4617b5")};
QString Style::getStylesheet(const QString &filename)
{
if (!Settings::getInstance().getUseNativeStyle())
@ -58,21 +89,6 @@ QString Style::getStylesheet(const QString &filename)
QColor Style::getColor(Style::ColorPalette entry)
{
// colors as defined in
// https://github.com/ItsDuke/Tox-UI/blob/master/UI%20GUIDELINES.md
static QColor palette[] = {
QColor("#6bc260"),
QColor("#cebf44"),
QColor("#c84e4e"),
QColor("#000000"),
QColor("#1c1c1c"),
QColor("#414141"),
QColor("#414141").lighter(120),
QColor("#d1d1d1"),
QColor("#ffffff"),
QColor("#ff7700"),
};
return palette[entry];
}
@ -98,28 +114,35 @@ QFont Style::getFont(Style::Font font)
QString Style::resolve(QString qss)
{
static QMap<QString, QString> dict = {
// colors
{"@green", getColor(Green).name()},
{"@yellow", getColor(Yellow).name()},
{"@red", getColor(Red).name()},
{"@black", getColor(Black).name()},
{"@darkGrey", getColor(DarkGrey).name()},
{"@mediumGrey", getColor(MediumGrey).name()},
{"@mediumGreyLight", getColor(MediumGreyLight).name()},
{"@lightGrey", getColor(LightGrey).name()},
{"@white", getColor(White).name()},
{"@orange", getColor(Orange).name()},
if (dict.isEmpty())
{
dict = {
// colors
{"@green", Style::getColor(Style::Green).name()},
{"@yellow", Style::getColor(Style::Yellow).name()},
{"@red", Style::getColor(Style::Red).name()},
{"@black", Style::getColor(Style::Black).name()},
{"@darkGrey", Style::getColor(Style::DarkGrey).name()},
{"@mediumGrey", Style::getColor(Style::MediumGrey).name()},
{"@mediumGreyLight", Style::getColor(Style::MediumGreyLight).name()},
{"@lightGrey", Style::getColor(Style::LightGrey).name()},
{"@white", Style::getColor(Style::White).name()},
{"@orange", Style::getColor(Style::Orange).name()},
{"@themeDark", Style::getColor(Style::ThemeDark).name()},
{"@themeMediumDark", Style::getColor(Style::ThemeMediumDark).name()},
{"@themeMedium", Style::getColor(Style::ThemeMedium).name()},
{"@themeLight", Style::getColor(Style::ThemeLight).name()},
// fonts
{"@extraBig", qssifyFont(getFont(ExtraBig))},
{"@big", qssifyFont(getFont(Big))},
{"@bigBold", qssifyFont(getFont(BigBold))},
{"@medium", qssifyFont(getFont(Medium))},
{"@mediumBold", qssifyFont(getFont(MediumBold))},
{"@small", qssifyFont(getFont(Small))},
{"@smallLight", qssifyFont(getFont(SmallLight))},
};
// fonts
{"@extraBig", qssifyFont(Style::getFont(Style::ExtraBig))},
{"@big", qssifyFont(Style::getFont(Style::Big))},
{"@bigBold", qssifyFont(Style::getFont(Style::BigBold))},
{"@medium", qssifyFont(Style::getFont(Style::Medium))},
{"@mediumBold", qssifyFont(Style::getFont(Style::MediumBold))},
{"@small", qssifyFont(Style::getFont(Style::Small))},
{"@smallLight", qssifyFont(Style::getFont(Style::SmallLight))},
};
}
for (const QString& key : dict.keys())
{
@ -144,3 +167,42 @@ void Style::repolish(QWidget *w)
}
}
}
void Style::setThemeColor(int color)
{
if (color < 0 || color >= themeColorColors.size())
setThemeColor(QColor());
else
setThemeColor(themeColorColors[color]);
}
void Style::setThemeColor(QColor color)
{
if (!color.isValid())
{
// Reset to default
palette[ThemeDark] = QColor("#1c1c1c");
palette[ThemeMediumDark] = QColor("#2a2a2a");
palette[ThemeMedium] = QColor("#414141");
palette[ThemeLight] = QColor("#4e4e4e");
}
else
{
palette[ThemeDark] = color.darker(155);
palette[ThemeMediumDark] = color.darker(135);
palette[ThemeMedium] = color.darker(120);
palette[ThemeLight] = color.lighter(110);
}
dict["@themeDark"] = getColor(ThemeDark).name();
dict["@themeMediumDark"] = getColor(ThemeMediumDark).name();
dict["@themeMedium"] = getColor(ThemeMedium).name();
dict["@themeLight"] = getColor(ThemeLight).name();
applyTheme();
}
void Style::applyTheme()
{
Widget::getInstance()->reloadTheme();
}

View File

@ -38,6 +38,10 @@ public:
LightGrey,
White,
Orange,
ThemeDark,
ThemeMediumDark,
ThemeMedium,
ThemeLight,
};
enum Font
@ -56,6 +60,15 @@ public:
static QFont getFont(Font font);
static QString resolve(QString qss);
static void repolish(QWidget* w);
static void setThemeColor(int color);
static void setThemeColor(QColor color); ///< Pass an invalid QColor to reset to defaults
static void applyTheme(); ///< Reloads some CCS
static QStringList themeColorNames;
static QList<QColor> themeColorColors;
signals:
void themeChanged();
private:
Style();

32
src/platform/timer.h Normal file
View File

@ -0,0 +1,32 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifdef QTOX_PLATFORM_EXT
#ifndef PLATFORM_TIMER_H
#define PLATFORM_TIMER_H
#include <cstdint>
namespace Platform
{
uint32_t getIdleTime();
}
#endif // PLATFORM_TIMER_H
#endif // QTOX_PLATFORM_EXT

View File

@ -0,0 +1,50 @@
/*
Pidgin is the legal property of its developers, whose names are too numerous
to list here. Please refer to the COPYRIGHT file distributed with this
source distribution (which can be found at
<https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/COPYRIGHT> ).
Additional Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#if defined(__APPLE__) && defined(__MACH__)
#include "src/platform/timer.h"
#include <QDebug>
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
uint32_t Platform::getIdleTime()
{
// https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/pidgin/gtkidle.c
static io_service_t service = NULL;
CFTypeRef property;
uint64_t idleTime_ns = 0;
if (!service)
{
mach_port_t master;
IOMasterPort(MACH_PORT_NULL, &master);
service = IOServiceGetMatchingService(master, IOServiceMatching("IOHIDSystem"));
}
property = IORegistryEntryCreateCFProperty(service, CFSTR("HIDIdleTime"), kCFAllocatorDefault, 0);
CFNumberGetValue((CFNumberRef)property, kCFNumberSInt64Type, &idleTime_ns);
CFRelease(property);
return idleTime_ns / 1000000;
}
#endif // defined(__APPLE__) && defined(__MACH__)

View File

@ -0,0 +1,32 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include <QDebug>
#ifdef Q_OS_WIN32
#include "src/platform/timer.h"
#include <windows.h>
uint32_t Platform::getIdleTime()
{
LASTINPUTINFO info = { 0 };
info.cbSize = sizeof(info);
if (GetLastInputInfo(&info))
return GetTickCount() - info.dwTime;
return 0;
}
#endif // Q_OS_WIN32

View File

@ -0,0 +1,52 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include <QDebug>
#if defined(Q_OS_UNIX) && !defined(__APPLE__) && !defined(__MACH__)
#include "src/platform/timer.h"
#include <X11/extensions/scrnsaver.h>
uint32_t Platform::getIdleTime()
{
uint32_t idleTime = 0;
Display *display = XOpenDisplay(NULL);
if (!display)
{
qDebug() << "XOpenDisplay(NULL) failed";
return 0;
}
int32_t x11event = 0, x11error = 0;
static int32_t hasExtension = XScreenSaverQueryExtension(display, &x11event, &x11error);
if (hasExtension)
{
XScreenSaverInfo *info = XScreenSaverAllocInfo();
if (info)
{
XScreenSaverQueryInfo(display, DefaultRootWindow(display), info);
idleTime = info->idle;
XFree(info);
}
else
qDebug() << "XScreenSaverAllocInfo() failed";
}
XCloseDisplay(display);
return idleTime;
}
#endif // Q_OS_UNIX

View File

@ -129,7 +129,7 @@ void CameraWorker::applyProps()
if (!cam.isOpened())
return;
for(int prop : props.keys())
for (int prop : props.keys())
cam.set(prop, props.value(prop));
}
@ -148,7 +148,7 @@ void CameraWorker::subscribe()
void CameraWorker::unsubscribe()
{
if(--refCount <= 0)
if (--refCount <= 0)
{
cam.release();
refCount = 0;

View File

@ -28,7 +28,7 @@ void NetVideoSource::pushFrame(VideoFrame frame)
emit frameAvailable(frame);
}
void NetVideoSource::pushVPXFrame(vpx_image *image)
void NetVideoSource::pushVPXFrame(const vpx_image *image)
{
const int dw = image->d_w;
const int dh = image->d_h;

View File

@ -27,7 +27,7 @@ public:
NetVideoSource();
void pushFrame(VideoFrame frame);
void pushVPXFrame(vpx_image* image);
void pushVPXFrame(const vpx_image *image);
virtual void subscribe() {}
virtual void unsubscribe() {}

View File

@ -94,6 +94,9 @@ bool CroppingLabel::eventFilter(QObject *obj, QEvent *e)
// events fired by the QLineEdit
if (obj == textEdit)
{
if (!textEdit->isVisible())
return false;
if (e->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
@ -126,8 +129,9 @@ void CroppingLabel::hideTextEdit(bool acceptText)
{
if (acceptText)
{
emit textChanged(textEdit->text(), origText);
setText(textEdit->text());
QString oldOrigText = origText;
setText(textEdit->text()); // set before emitting so we don't override external reactions to signal
emit textChanged(textEdit->text(), oldOrigText);
}
textEdit->hide();
@ -141,3 +145,8 @@ void CroppingLabel::showTextEdit()
textEdit->setFocus();
textEdit->setText(origText);
}
QString CroppingLabel::fullText()
{
return origText;
}

View File

@ -37,6 +37,8 @@ public:
virtual void mouseReleaseEvent(QMouseEvent *e);
virtual bool eventFilter(QObject *obj, QEvent *e);
QString fullText(); ///< Returns the un-cropped text
signals:
void textChanged(QString newText, QString oldText);
void clicked();

View File

@ -95,10 +95,10 @@ void AddFriendForm::onSendTriggered()
this->toxId.clear();
this->message.clear();
} else {
if (Settings::getInstance().getUseProxy())
if (Settings::getInstance().getProxyType() != ProxyType::ptNone)
{
QMessageBox::StandardButton btn = QMessageBox::warning(main, "qTox", tr("qTox needs to use the Tox DNS, but can't do it through a proxy.\n\
Ignore the proxy and connect to the Internet directly ?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No);
Ignore the proxy and connect to the Internet directly?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No);
if (btn != QMessageBox::Ok)
return;
}

View File

@ -43,8 +43,6 @@
ChatForm::ChatForm(Friend* chatFriend)
: f(chatFriend)
, audioInputFlag(false)
, audioOutputFlag(false)
, callId(0)
{
nameLabel->setText(f->getDisplayedName());
@ -77,11 +75,10 @@ ChatForm::ChatForm(Friend* chatFriend)
connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle()));
connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
connect(this, SIGNAL(chatAreaCleared()), this, SLOT(clearReciepts()));
connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig)
{if (text != orig) emit aliasChanged(text);} );
setAcceptDrops(true);
if (Settings::getInstance().getEnableLogging())
loadHistory(QDateTime::currentDateTime().addDays(-7), true);
}
ChatForm::~ChatForm()
@ -105,7 +102,7 @@ void ChatForm::onSendTriggered()
if (isAction)
msg = msg = msg.right(msg.length() - 4);
QList<CString> splittedMsg = Core::splitMessage(msg);
QList<CString> splittedMsg = Core::splitMessage(msg, TOX_MAX_MESSAGE_LENGTH);
QDateTime timestamp = QDateTime::currentDateTime();
for (CString& c_msg : splittedMsg)
@ -120,14 +117,13 @@ void ChatForm::onSendTriggered()
int id = HistoryKeeper::getInstance()->addChatEntry(f->getToxID().publicKey, qt_msg_hist,
Core::getInstance()->getSelfId().publicKey, timestamp, status);
ChatMessage* ma = addSelfMessage(msg, isAction, timestamp, false);
int rec;
if (isAction)
rec = Core::getInstance()->sendAction(f->getFriendID(), msg);
rec = Core::getInstance()->sendAction(f->getFriendID(), qt_msg);
else
rec = Core::getInstance()->sendMessage(f->getFriendID(), msg);
rec = Core::getInstance()->sendMessage(f->getFriendID(), qt_msg);
registerReceipt(rec, id, ma);
}
@ -144,7 +140,10 @@ void ChatForm::onAttachClicked()
{
QFile file(path);
if (!file.exists() || !file.open(QIODevice::ReadOnly))
{
QMessageBox::warning(this, tr("File not read"), tr("qTox wasn't able to open %1").arg(QFileInfo(path).fileName()));
continue;
}
if (file.isSequential())
{
QMessageBox::critical(0, tr("Bad Idea"), tr("You're trying to send a special (sequential) file, that's not going to work!"));
@ -198,12 +197,12 @@ void ChatForm::onFileRecvRequest(ToxFile file)
}
ChatMessage* msg = chatWidget->addFileTransferMessage(name, file, QDateTime::currentDateTime(), false);
if (!Settings::getInstance().getAutoAcceptDir(Core::getInstance()->getFriendAddress(f->getFriendID())).isEmpty()
if (!Settings::getInstance().getAutoAcceptDir(f->getToxID()).isEmpty()
|| Settings::getInstance().getAutoSaveEnabled())
{
FileTransferWidget* tfWidget = dynamic_cast<FileTransferWidget*>(msg->getContent(1));
if(tfWidget)
tfWidget->acceptTransfer(Settings::getInstance().getAutoAcceptDir(Core::getInstance()->getFriendAddress(f->getFriendID())));
tfWidget->acceptTransfer(Settings::getInstance().getAutoAcceptDir(f->getToxID()));
}
}
@ -284,6 +283,8 @@ void ChatForm::onAvCancel(int FriendId, int)
if (FriendId != f->getFriendID())
return;
stopCounter();
audioInputFlag = false;
audioOutputFlag = false;
@ -638,7 +639,7 @@ void ChatForm::onFileSendFailed(int FriendId, const QString &fname)
if (FriendId != f->getFriendID())
return;
addSystemInfoMessage("File: \"" + fname + "\" failed to send.", "red", QDateTime::currentDateTime());
addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fname), "red", QDateTime::currentDateTime());
}
void ChatForm::onAvatarChange(int FriendId, const QPixmap &pic)
@ -717,7 +718,7 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered)
// Show each messages
ToxID id = ToxID::fromString(it.sender);
ChatMessage* msg = chatWidget->addChatMessage(Core::getInstance()->getPeerName(id), it.message, id.isMine(), false);
if (it.isSent)
if (it.isSent || !id.isMine())
{
msg->markAsSent(msgDateTime);
}
@ -757,7 +758,7 @@ void ChatForm::onLoadHistory()
void ChatForm::startCounter()
{
if(!timer)
if (!timer)
{
timer = new QTimer();
connect(timer, SIGNAL(timeout()), this, SLOT(updateTime()));
@ -769,7 +770,7 @@ void ChatForm::startCounter()
void ChatForm::stopCounter()
{
if(timer)
if (timer)
{
addSystemInfoMessage(tr("Call with %1 ended. %2").arg(f->getDisplayedName(),secondsToDHMS(timeElapsed.elapsed()/1000)),
"white", QDateTime::currentDateTime());
@ -797,10 +798,10 @@ QString ChatForm::secondsToDHMS(quint32 duration)
int hours = (int) (duration % 24);
int days = (int) (duration / 24);
if(minutes == 0)
if (minutes == 0)
return cD + res.sprintf("%02ds", seconds);
if(hours == 0 && days == 0)
if (hours == 0 && days == 0)
return cD + res.sprintf("%02dm %02ds", minutes, seconds);
if (days == 0)

View File

@ -36,6 +36,7 @@ public:
ChatForm(Friend* chatFriend);
~ChatForm();
void setStatusMessage(QString newMessage);
void loadHistory(QDateTime since, bool processUndelivered = false);
void dischargeReceipt(int receipt);
@ -48,6 +49,7 @@ signals:
void cancelCall(int callId, int friendId);
void micMuteToggle(int callId);
void volMuteToggle(int callId);
void aliasChanged(const QString& alias);
public slots:
void deliverOfflineMsgs();
@ -84,7 +86,6 @@ private slots:
void updateTime();
protected:
void loadHistory(QDateTime since, bool processUndelivered = false);
// drag & drop
void dragEnterEvent(QDragEnterEvent* ev);
void dropEvent(QDropEvent* ev);
@ -94,8 +95,6 @@ private:
Friend* f;
CroppingLabel *statusMessageLabel;
NetCamView* netcam;
bool audioInputFlag;
bool audioOutputFlag;
int callId;
QLabel *callDuration;
QTimer *timer;

View File

@ -26,6 +26,8 @@
#include "src/widget/tool/chattextedit.h"
#include "src/widget/maskablepixmapwidget.h"
#include "src/core.h"
#include "src/grouplist.h"
#include "src/group.h"
#include "src/friendlist.h"
#include "src/friend.h"
#include "src/chatlog/content/text.h"
@ -34,6 +36,8 @@
GenericChatForm::GenericChatForm(QWidget *parent) :
QWidget(parent),
earliestMessage(nullptr)
, audioInputFlag(false)
, audioOutputFlag(false)
{
curRow = 0;
@ -42,12 +46,16 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
nameLabel = new CroppingLabel();
nameLabel->setObjectName("nameLabel");
nameLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize());
nameLabel->setEditable(true);
avatar = new MaskablePixmapWidget(this, QSize(40,40), ":/img/avatar_mask.png");
QHBoxLayout *headLayout = new QHBoxLayout(), *mainFootLayout = new QHBoxLayout();
headTextLayout = new QVBoxLayout();
QVBoxLayout *mainLayout = new QVBoxLayout();
QVBoxLayout *footButtonsSmall = new QVBoxLayout(), *volMicLayout = new QVBoxLayout();
QHBoxLayout *headLayout = new QHBoxLayout(),
*mainFootLayout = new QHBoxLayout();
QVBoxLayout *mainLayout = new QVBoxLayout(),
*footButtonsSmall = new QVBoxLayout(),
*volMicLayout = new QVBoxLayout();
headTextLayout = new QVBoxLayout();
chatWidget = new ChatLog(this);
@ -63,16 +71,16 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
fileButton->setToolTip(tr("Send file(s)"));
callButton = new QPushButton();
callButton->setFixedSize(50,40);
callButton->setToolTip(tr("Audio call"));
callButton->setToolTip(tr("Audio call: RED means you're on a call"));
videoButton = new QPushButton();
videoButton->setFixedSize(50,40);
videoButton->setToolTip(tr("Video call"));
videoButton->setToolTip(tr("Video call: RED means you're on a call"));
volButton = new QPushButton();
volButton->setFixedSize(25,20);
volButton->setToolTip(tr("Toggle speakers volume"));
//volButton->setFixedSize(25,20);
volButton->setToolTip(tr("Toggle speakers volume: RED is OFF"));
micButton = new QPushButton();
micButton->setFixedSize(25,20);
micButton->setToolTip(tr("Toggle microphone"));
// micButton->setFixedSize(25,20);
micButton->setToolTip(tr("Toggle microphone: RED is OFF"));
footButtonsSmall->setSpacing(2);
@ -111,6 +119,13 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
mainFootLayout->addSpacing(5);
mainFootLayout->addWidget(sendButton);
mainFootLayout->setSpacing(0);
headTextLayout->addStretch();
headTextLayout->addWidget(nameLabel);
volMicLayout->addWidget(micButton, Qt::AlignTop);
volMicLayout->addSpacing(2);
volMicLayout->addWidget(volButton, Qt::AlignBottom);
headWidget->setLayout(headLayout);
headLayout->addWidget(avatar);
@ -122,14 +137,6 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
headLayout->addWidget(videoButton);
headLayout->setSpacing(0);
volMicLayout->addSpacing(1);
volMicLayout->addWidget(micButton);
volMicLayout->addSpacing(2);
volMicLayout->addWidget(volButton);
headTextLayout->addStretch();
headTextLayout->addWidget(nameLabel);
//Fix for incorrect layouts on OS X as per
//https://bugreports.qt-project.org/browse/QTBUG-14591
sendButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
@ -142,6 +149,7 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
connect(emoteButton, SIGNAL(clicked()), this, SLOT(onEmoteButtonClicked()));
connect(chatWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
connect(chatWidget, SIGNAL(onClick()), this, SLOT(onChatWidgetClicked()));
//chatWidget->document()->setDefaultStyleSheet(Style::getStylesheet(":ui/chatArea/innerStyle.css"));
//chatWidget->setStyleSheet(Style::getStylesheet(":/ui/chatArea/chatArea.css"));
@ -174,18 +182,6 @@ void GenericChatForm::show(Ui::MainWindow &ui)
QWidget::show();
}
void GenericChatForm::addMessage(const QString &author, const QString &message, bool isAction, const QDateTime &datetime)
{
if(!isAction)
{
chatWidget->addChatMessage(author, message, datetime, false, false);
}
else
{
chatWidget->addChatAction(author, message, datetime);
}
}
void GenericChatForm::onChatContextMenuRequested(QPoint pos)
{
QWidget* sender = (QWidget*)QObject::sender();
@ -239,6 +235,7 @@ void GenericChatForm::addAlertMessage(const ToxID &author, QString message, QDat
{
QString authorStr = Core::getInstance()->getPeerName(author);
chatWidget->addChatMessage(author != previousId ? authorStr : QString(), message, author.isMine(), true);
previousId = author;
}
@ -259,6 +256,11 @@ void GenericChatForm::onEmoteButtonClicked()
}
}
void GenericChatForm::onChatWidgetClicked()
{
msgEdit->setFocus();
}
void GenericChatForm::onEmoteInsertRequested(QString str)
{
// insert the emoticon
@ -280,20 +282,6 @@ void GenericChatForm::addSystemInfoMessage(const QString &message, const QString
chatWidget->addSystemMessage(message, datetime);
}
void GenericChatForm::addAlertMessage(const QString &author, QString message, QDateTime datetime)
{
ChatMessage* msg = chatWidget->addChatMessage(author, message, false, true);
msg->markAsSent(datetime);
}
//QString GenericChatForm::getElidedName(const QString& name)
//{
// // update this whenever you change the font in innerStyle.css
// QFontMetrics fm(Style::getFont(Style::BigBold));
// return fm.elidedText(name, Qt::ElideRight, chatWidget->nameColWidth());
//}
void GenericChatForm::clearChatArea(bool notinform)
{
chatWidget->clear();
@ -311,127 +299,20 @@ void GenericChatForm::clearChatArea(bool notinform)
emit chatAreaCleared();
}
/**
* @deprecated The only reason it's still alive is because the groupchat API is a bit limited
*/
//MessageActionPtr GenericChatForm::genMessageActionAction(const QString &author, QString message, bool isAction,
// const QDateTime &datetime)
//{
// if (earliestMessage == nullptr)
// {
// earliestMessage = new QDateTime(datetime);
// }
QString GenericChatForm::resolveToxID(const ToxID &id)
{
Friend *f = FriendList::findFriend(id);
if (f)
{
return f->getDisplayedName();
} else {
for (auto it : GroupList::getAllGroups())
{
QString res = it->resolveToxID(id);
if (res.size())
return res;
}
}
// QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
// bool isMe = (author == Widget::getInstance()->getUsername());
// if (!isAction && message.startsWith("/me "))
// { // always render actions regardless of what core thinks
// isAction = true;
// message = message.right(message.length()-4);
// }
// if (isAction)
// {
// previousId = ToxID(); // next msg has a name regardless
// return MessageActionPtr(new ActionAction (getElidedName(author), message, date, isMe));
// }
// MessageActionPtr res;
// if (previousId.publicKey == author)
// res = MessageActionPtr(new MessageAction(QString(), message, date, isMe));
// else
// res = MessageActionPtr(new MessageAction(getElidedName(author), message, date, isMe));
// previousId.publicKey = author;
// return res;
//}
//MessageActionPtr GenericChatForm::genMessageActionAction(const ToxID& author, QString message, bool isAction, const QDateTime &datetime)
//{
// if (earliestMessage == nullptr)
// {
// earliestMessage = new QDateTime(datetime);
// }
// const Core* core = Core::getInstance();
// QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
// bool isMe = (author == core->getSelfId());
// QString authorStr;
// if (isMe)
// authorStr = core->getUsername();
// else {
// Friend *f = FriendList::findFriend(author.publicKey);
// if (f)
// authorStr = f->getDisplayedName();
// else
// authorStr = core->getPeerName(author);
// }
// if (authorStr.isEmpty()) // Fallback if we can't find a username
// authorStr = author.toString();
// if (!isAction && message.startsWith("/me "))
// { // always render actions regardless of what core thinks
// isAction = true;
// message = message.right(message.length()-4);
// }
// if (isAction)
// {
// previousId = ToxID(); // next msg has a name regardless
// return MessageActionPtr(new ActionAction (getElidedName(authorStr), message, date, isMe));
// }
// MessageActionPtr res;
// if (previousId == author)
// res = MessageActionPtr(new MessageAction(QString(), message, date, isMe));
// else
// res = MessageActionPtr(new MessageAction(getElidedName(authorStr), message, date, isMe));
// previousId = author;
// return res;
//}
//MessageActionPtr GenericChatForm::genSelfActionAction(QString message, bool isAction, const QDateTime &datetime)
//{
// if (earliestMessage == nullptr)
// {
// earliestMessage = new QDateTime(datetime);
// }
// const Core* core = Core::getInstance();
// QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
// QString author = core->getUsername();;
// if (!isAction && message.startsWith("/me "))
// { // always render actions regardless of what core thinks
// isAction = true;
// message = message.right(message.length()-4);
// }
// if (isAction)
// {
// previousId = ToxID(); // next msg has a name regardless
// return MessageActionPtr(new ActionAction (getElidedName(author), message, date, true));
// }
// MessageActionPtr res;
// if (previousId.isMine())
// res = MessageActionPtr(new MessageAction(QString(), message, date, true));
// else
// res = MessageActionPtr(new MessageAction(getElidedName(author), message, date, true));
// previousId = Core::getInstance()->getSelfId();
// return res;
//}
//ChatMessage* GenericChatForm::genSystemInfoAction(const QString &message, const QString &type, const QDateTime &datetime)
//{
// previousId = ToxID();
// QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
// return ChatActionPtr(new SystemMessageAction(message, type, date));
//}
return QString();
}

View File

@ -50,11 +50,10 @@ public:
virtual void setName(const QString &newName);
virtual void show(Ui::MainWindow &ui);
void addMessage(const QString& author, const QString &message, bool isAction, const QDateTime &datetime); ///< Deprecated
ChatMessage* addMessage(const ToxID& author, const QString &message, bool isAction, const QDateTime &datetime, bool isSent);
ChatMessage* addSelfMessage(const QString &message, bool isAction, const QDateTime &datetime, bool isSent);
void addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime);
void addAlertMessage(const QString& author, QString message, QDateTime datetime); ///< Deprecated
void addAlertMessage(const ToxID& author, QString message, QDateTime datetime);
bool isEmpty();
@ -74,9 +73,10 @@ protected slots:
void onEmoteButtonClicked();
void onEmoteInsertRequested(QString str);
void clearChatArea(bool);
void onChatWidgetClicked();
protected:
QString getElidedName(const QString& name);
QString resolveToxID(const ToxID &id);
ToxID previousId;
QMenu menu;
@ -90,6 +90,8 @@ protected:
QPushButton *sendButton;
ChatLog *chatWidget;
QDateTime *earliestMessage;
bool audioInputFlag;
bool audioOutputFlag;
};
#endif // GENERICCHATFORM_H

View File

@ -28,24 +28,33 @@
#include <QDragEnterEvent>
#include "src/historykeeper.h"
#include "src/misc/flowlayout.h"
#include <QDebug>
GroupChatForm::GroupChatForm(Group* chatGroup)
: group(chatGroup)
: group(chatGroup), inCall{false}
{
nusersLabel = new QLabel();
tabber = new TabCompleter(msgEdit, group);
fileButton->setEnabled(false);
callButton->setVisible(false);
videoButton->setVisible(false);
volButton->setVisible(false);
micButton->setVisible(false);
if (group->isAvGroupchat())
{
videoButton->setEnabled(false);
videoButton->setObjectName("grey");
}
else
{
videoButton->setVisible(false);
callButton->setVisible(false);
volButton->setVisible(false);
micButton->setVisible(false);
}
nameLabel->setText(group->widget->getName());
nameLabel->setText(group->getGroupWidget()->getName());
nusersLabel->setFont(Style::getFont(Style::Medium));
nusersLabel->setText(GroupChatForm::tr("%1 users in chat","Number of users in chat").arg(group->peers.size()));
nusersLabel->setText(GroupChatForm::tr("%1 users in chat","Number of users in chat").arg(group->getPeersCount()));
nusersLabel->setObjectName("statusLabel");
avatar->setPixmap(QPixmap(":/img/group_dark.png"), Qt::transparent);
@ -53,7 +62,7 @@ GroupChatForm::GroupChatForm(Group* chatGroup)
msgEdit->setObjectName("group");
namesListLayout = new FlowLayout(0,5,0);
QStringList names(group->peers.values());
QStringList names(group->getPeerList());
for (const QString& name : names)
namesListLayout->addWidget(new QLabel(name));
@ -68,6 +77,11 @@ GroupChatForm::GroupChatForm(Group* chatGroup)
connect(msgEdit, SIGNAL(enterPressed()), this, SLOT(onSendTriggered()));
connect(msgEdit, &ChatTextEdit::tabPressed, tabber, &TabCompleter::complete);
connect(msgEdit, &ChatTextEdit::keyPressed, tabber, &TabCompleter::reset);
connect(callButton, &QPushButton::clicked, this, &GroupChatForm::onCallClicked);
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle()));
connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig)
{if (text != orig) emit groupTitleChanged(group->getGroupId(), text.left(128));} );
setAcceptDrops(true);
}
@ -83,15 +97,15 @@ void GroupChatForm::onSendTriggered()
if (msg.startsWith("/me "))
{
msg = msg.right(msg.length() - 4);
emit sendAction(group->groupId, msg);
emit sendAction(group->getGroupId(), msg);
} else {
emit sendMessage(group->groupId, msg);
emit sendMessage(group->getGroupId(), msg);
}
}
void GroupChatForm::onUserListChanged()
{
nusersLabel->setText(tr("%1 users in chat").arg(group->nPeers));
nusersLabel->setText(tr("%1 users in chat").arg(group->getPeersCount()));
QLayoutItem *child;
while ((child = namesListLayout->takeAt(0)))
@ -101,7 +115,7 @@ void GroupChatForm::onUserListChanged()
delete child;
}
QStringList names(group->peers.values());
QStringList names(group->getPeerList());
unsigned nNames = names.size();
for (unsigned i=0; i<nNames; ++i)
{
@ -125,7 +139,108 @@ void GroupChatForm::dropEvent(QDropEvent *ev)
if (ev->mimeData()->hasFormat("friend"))
{
int friendId = ev->mimeData()->data("friend").toInt();
Core::getInstance()->groupInviteFriend(friendId, group->groupId);
Core::getInstance()->groupInviteFriend(friendId, group->getGroupId());
}
}
void GroupChatForm::onMicMuteToggle()
{
if (audioInputFlag == true)
{
if (micButton->objectName() == "red")
{
Core::getInstance()->enableGroupCallMic(group->getGroupId());
micButton->setObjectName("green");
}
else
{
Core::getInstance()->disableGroupCallMic(group->getGroupId());
micButton->setObjectName("red");
}
Style::repolish(micButton);
}
}
void GroupChatForm::onVolMuteToggle()
{
if (audioOutputFlag == true)
{
if (volButton->objectName() == "red")
{
Core::getInstance()->enableGroupCallVol(group->getGroupId());
volButton->setObjectName("green");
}
else
{
Core::getInstance()->disableGroupCallVol(group->getGroupId());
volButton->setObjectName("red");
}
Style::repolish(volButton);
}
}
void GroupChatForm::onCallClicked()
{
if (!inCall)
{
Core::getInstance()->joinGroupCall(group->getGroupId());
audioInputFlag = true;
audioOutputFlag = true;
callButton->setObjectName("red");
callButton->style()->polish(callButton);
inCall = true;
}
else
{
Core::getInstance()->leaveGroupCall(group->getGroupId());
audioInputFlag = false;
audioOutputFlag = false;
micButton->setObjectName("green");
micButton->style()->polish(micButton);
volButton->setObjectName("green");
volButton->style()->polish(volButton);
callButton->setObjectName("green");
callButton->style()->polish(callButton);
inCall = false;
}
}
void GroupChatForm::keyPressEvent(QKeyEvent* ev)
{
// Push to talk (CTRL+P)
if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall)
{
Core* core = Core::getInstance();
if (!core->isGroupCallMicEnabled(group->getGroupId()))
{
core->enableGroupCallMic(group->getGroupId());
micButton->setObjectName("green");
micButton->style()->polish(micButton);
Style::repolish(micButton);
}
}
if (msgEdit->hasFocus())
return;
}
void GroupChatForm::keyReleaseEvent(QKeyEvent* ev)
{
// Push to talk (CTRL+P)
if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall)
{
Core* core = Core::getInstance();
if (core->isGroupCallMicEnabled(group->getGroupId()))
{
core->disableGroupCallMic(group->getGroupId());
micButton->setObjectName("red");
micButton->style()->polish(micButton);
Style::repolish(micButton);
}
}
if (msgEdit->hasFocus())
return;
}

View File

@ -32,8 +32,17 @@ public:
void onUserListChanged();
void keyPressEvent(QKeyEvent* ev);
void keyReleaseEvent(QKeyEvent* ev);
signals:
void groupTitleChanged(int groupnum, const QString& name);
private slots:
void onSendTriggered();
void onMicMuteToggle();
void onVolMuteToggle();
void onCallClicked();
protected:
// drag & drop
@ -45,6 +54,7 @@ private:
FlowLayout* namesListLayout;
QLabel *nusersLabel;
TabCompleter* tabber;
bool inCall;
};
#endif // GROUPCHATFORM_H

View File

@ -0,0 +1,62 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include "ui_advancedsettings.h"
#include "advancedform.h"
#include "src/historykeeper.h"
#include "src/misc/settings.h"
#include "src/misc/db/plaindb.h"
AdvancedForm::AdvancedForm() :
GenericForm(tr("Advanced"), QPixmap(":/img/settings/general.png"))
{
bodyUI = new Ui::AdvancedSettings;
bodyUI->setupUi(this);
bodyUI->dbLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
bodyUI->dbLabel->setOpenExternalLinks(true);
bodyUI->syncTypeComboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
bodyUI->syncTypeComboBox->addItems({tr("FULL - very safe, slowest (recommended)"),
tr("NORMAL - almost as safe as FULL, about 20% faster than FULL"),
tr("OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended)")
});
int index = 2 - static_cast<int>(Settings::getInstance().getDbSyncType());
bodyUI->syncTypeComboBox->setCurrentIndex(index);
connect(bodyUI->syncTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onDbSyncTypeUpdated()));
connect(bodyUI->resetButton, SIGNAL(clicked()), this, SLOT(resetToDefault()));
}
AdvancedForm::~AdvancedForm()
{
delete bodyUI;
}
void AdvancedForm::onDbSyncTypeUpdated()
{
int index = 2 - bodyUI->syncTypeComboBox->currentIndex();
Settings::getInstance().setDbSyncType(index);
HistoryKeeper::getInstance()->setSyncType(Settings::getInstance().getDbSyncType());
}
void AdvancedForm::resetToDefault()
{
int index = 2 - static_cast<int>(Db::syncType::stFull);
bodyUI->syncTypeComboBox->setCurrentIndex(index);
onDbSyncTypeUpdated();
}

View File

@ -0,0 +1,43 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifndef ADVANCEDFORM_H
#define ADVANCEDFORM_H
#include "genericsettings.h"
class Core;
namespace Ui {
class AdvancedSettings;
}
class AdvancedForm : public GenericForm
{
Q_OBJECT
public:
AdvancedForm();
virtual ~AdvancedForm();
private slots:
void onDbSyncTypeUpdated();
void resetToDefault();
private:
Ui::AdvancedSettings* bodyUI;
};
#endif // ADVANCEDFORM_H

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AdvancedSettings</class>
<widget class="QWidget" name="AdvancedSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>380</width>
<height>280</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="warningLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; color:#ff0000;&quot;&gt;IMPORTANT NOTE&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;Unless you &lt;/span&gt;&lt;span style=&quot; font-weight:600; color:#ff0000;&quot;&gt;really&lt;/span&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt; know what you are doing, please do &lt;/span&gt;&lt;span style=&quot; font-weight:600; color:#ff0000;&quot;&gt;not&lt;/span&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt; change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QPushButton" name="resetButton">
<property name="text">
<string>Reset to default settings</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="historyGroup">
<property name="title">
<string>History</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="dbLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.sqlite.org/pragma.html#pragma_synchronous&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Synchronous writing to DB&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="syncTypeComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -17,6 +17,7 @@
#include "avform.h"
#include "ui_avsettings.h"
#include "src/misc/settings.h"
#include "src/audio.h"
#if defined(__APPLE__) && defined(__MACH__)
#include <OpenAL/al.h>
@ -32,8 +33,11 @@ AVForm::AVForm() :
bodyUI = new Ui::AVSettings;
bodyUI->setupUi(this);
getAudioOutDevices();
getAudioInDevices();
#ifdef QTOX_FILTER_AUDIO
bodyUI->filterAudio->setChecked(Settings::getInstance().getFilterAudio());
#else
bodyUI->filterAudio->setDisabled(true);
#endif
connect(Camera::getInstance(), &Camera::propProbingFinished, this, &AVForm::onPropProbingFinished);
connect(Camera::getInstance(), &Camera::resolutionProbingFinished, this, &AVForm::onResProbingFinished);
@ -41,6 +45,8 @@ AVForm::AVForm() :
auto qcomboboxIndexChanged = (void(QComboBox::*)(const QString&)) &QComboBox::currentIndexChanged;
connect(bodyUI->inDevCombobox, qcomboboxIndexChanged, this, &AVForm::onInDevChanged);
connect(bodyUI->outDevCombobox, qcomboboxIndexChanged, this, &AVForm::onOutDevChanged);
connect(bodyUI->filterAudio, SIGNAL(toggled(bool)), this, SLOT(onFilterAudioToggled(bool)));
connect(bodyUI->rescanButton, &QPushButton::clicked, this, [=](){getAudioInDevices(); getAudioOutDevices();});
}
AVForm::~AVForm()
@ -50,6 +56,9 @@ AVForm::~AVForm()
void AVForm::present()
{
getAudioOutDevices();
getAudioInDevices();
bodyUI->CamVideoSurface->setSource(Camera::getInstance());
Camera::getInstance()->probeProp(Camera::SATURATION);
@ -179,9 +188,16 @@ void AVForm::getAudioOutDevices()
void AVForm::onInDevChanged(const QString &deviceDescriptor)
{
Settings::getInstance().setInDev(deviceDescriptor);
Audio::openInput(deviceDescriptor);
}
void AVForm::onOutDevChanged(const QString& deviceDescriptor)
{
Settings::getInstance().setOutDev(deviceDescriptor);
Audio::openOutput(deviceDescriptor);
}
void AVForm::onFilterAudioToggled(bool filterAudio)
{
Settings::getInstance().setFilterAudio(filterAudio);
}

View File

@ -52,6 +52,7 @@ private slots:
// audio
void onInDevChanged(const QString& deviceDescriptor);
void onOutDevChanged(const QString& deviceDescriptor);
void onFilterAudioToggled(bool filterAudio);
// camera
void onPropProbingFinished(Camera::Prop prop, double val);

View File

@ -30,8 +30,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>824</width>
<height>489</height>
<width>808</width>
<height>618</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -41,54 +41,68 @@
<string>Audio Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="microphoneLabel">
<property name="text">
<string>Microphone</string>
</property>
</widget>
</item>
<item row="1" column="0">
<item row="2" column="0">
<widget class="QLabel" name="playbackLabel">
<property name="text">
<string>Playback</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QSlider" name="playbackSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QSlider" name="microphoneSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<item row="1" column="0">
<widget class="QLabel" name="outDevLabel">
<property name="text">
<string>Playback device</string>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QLabel" name="inDevLabel">
<property name="text">
<string>Capture device</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="6" column="1">
<widget class="QComboBox" name="inDevCombobox"/>
</item>
<item row="0" column="1">
<item row="1" column="1">
<widget class="QComboBox" name="outDevCombobox"/>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="rescanButton">
<property name="text">
<string>Rescan audio devices</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="filterAudio">
<property name="text">
<string>Filter audio</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -21,6 +21,7 @@
#include "src/misc/settings.h"
#include "src/misc/smileypack.h"
#include "src/core.h"
#include "src/misc/style.h"
#include <QMessageBox>
#include <QStyleFactory>
#include <QTime>
@ -29,38 +30,47 @@
#include "src/autoupdate.h"
static QStringList locales = {"bg", "de", "en", "fr", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"};
static QStringList langs = {"Български", "Deustch", "English", "Français", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"};
static QStringList locales = {"bg", "de", "en", "es", "fr", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"};
static QStringList langs = {"Български", "Deutsch", "English", "Español", "Français", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"};
static QStringList timeFormats = {"hh:mm AP", "hh:mm", "hh:mm:ss AP", "hh:mm:ss"};
GeneralForm::GeneralForm(SettingsWidget *myParent) :
GenericForm(tr("General"), QPixmap(":/img/settings/general.png"))
{
parent = myParent;
parent = myParent;
bodyUI = new Ui::GeneralSettings;
bodyUI->setupUi(this);
bodyUI->checkUpdates->setVisible(AUTOUPDATE_ENABLED);
bodyUI->checkUpdates->setChecked(Settings::getInstance().getCheckUpdates());
bodyUI->trayLayout->addStretch();
bodyUI->trayBehavior->addStretch();
bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6());
for (int i = 0; i < langs.size(); i++)
bodyUI->transComboBox->insertItem(i, langs[i]);
bodyUI->transComboBox->setCurrentIndex(locales.indexOf(Settings::getInstance().getTranslation()));
bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable());
bool showSystemTray = Settings::getInstance().getShowSystemTray();
bodyUI->showSystemTray->setChecked(showSystemTray);
bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray());
bodyUI->startInTray->setEnabled(showSystemTray);
bodyUI->closeToTray->setChecked(Settings::getInstance().getCloseToTray());
bodyUI->closeToTray->setEnabled(showSystemTray);
bodyUI->minimizeToTray->setChecked(Settings::getInstance().getMinimizeToTray());
bodyUI->minimizeToTray->setEnabled(showSystemTray);
bodyUI->lightTrayIcon->setChecked(Settings::getInstance().getLightTrayIcon());
bodyUI->lightTrayIcon->setEnabled(showSystemTray);
bodyUI->statusChanges->setChecked(Settings::getInstance().getStatusChangeNotificationEnabled());
bodyUI->useEmoticons->setChecked(Settings::getInstance().getUseEmoticons());
bodyUI->autoacceptFiles->setChecked(Settings::getInstance().getAutoSaveEnabled());
bodyUI->autoSaveFilesDir->setText(Settings::getInstance().getGlobalAutoAcceptDir());
bodyUI->showInFront->setChecked(Settings::getInstance().getShowInFront());
bodyUI->cbFauxOfflineMessaging->setChecked(Settings::getInstance().getFauxOfflineMessaging());
for (auto entry : SmileyPack::listSmileyPacks())
{
bodyUI->smileyPackBrowser->addItem(entry.first, entry.second);
@ -70,63 +80,75 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) :
bodyUI->styleBrowser->addItem(tr("None"));
bodyUI->styleBrowser->addItems(QStyleFactory::keys());
if(QStyleFactory::keys().contains(Settings::getInstance().getStyle()))
if (QStyleFactory::keys().contains(Settings::getInstance().getStyle()))
bodyUI->styleBrowser->setCurrentText(Settings::getInstance().getStyle());
else
bodyUI->styleBrowser->setCurrentText(tr("None"));
for (QString color : Style::themeColorNames)
bodyUI->themeColorCBox->addItem(color);
bodyUI->themeColorCBox->setCurrentIndex(Settings::getInstance().getThemeColor());
bodyUI->emoticonSize->setValue(Settings::getInstance().getEmojiFontPointSize());
QStringList timestamps;
timestamps << QString("%1 - %2").arg(timeFormats[0],QTime::currentTime().toString(timeFormats[0]))
<< QString("%1 - %2").arg(timeFormats[1],QTime::currentTime().toString(timeFormats[1]))
<< QString("%1 - %2").arg(timeFormats[2],QTime::currentTime().toString(timeFormats[2]))
<< QString("%1 - %2").arg(timeFormats[3],QTime::currentTime().toString(timeFormats[3]));
bodyUI->timestamp->addItems(timestamps);
bodyUI->timestamp->setCurrentText(QString("%1 - %2").arg(Settings::getInstance().getTimestampFormat(),
QTime::currentTime().toString(Settings::getInstance().getTimestampFormat()))
); //idiot proof enough?
bodyUI->autoAwaySpinBox->setValue(Settings::getInstance().getAutoAwayTime());
bodyUI->cbEnableUDP->setChecked(!Settings::getInstance().getForceTCP());
bodyUI->proxyAddr->setText(Settings::getInstance().getProxyAddr());
int port = Settings::getInstance().getProxyPort();
if (port != -1)
bodyUI->proxyPort->setValue(port);
bodyUI->cbUseProxy->setChecked(Settings::getInstance().getUseProxy());
bodyUI->proxyType->setCurrentIndex(static_cast<int>(Settings::getInstance().getProxyType()));
onUseProxyUpdated();
//general
connect(bodyUI->checkUpdates, &QCheckBox::stateChanged, this, &GeneralForm::onCheckUpdateChanged);
connect(bodyUI->transComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onTranslationUpdated()));
connect(bodyUI->cbMakeToxPortable, &QCheckBox::stateChanged, this, &GeneralForm::onMakeToxPortableUpdated);
connect(bodyUI->showSystemTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowSystemTray);
connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray);
connect(bodyUI->closeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetCloseToTray);
connect(bodyUI->minimizeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetMinimizeToTray);
connect(bodyUI->lightTrayIcon, &QCheckBox::stateChanged, this, &GeneralForm::onSetLightTrayIcon);
connect(bodyUI->statusChanges, &QCheckBox::stateChanged, this, &GeneralForm::onSetStatusChange);
connect(bodyUI->autoAwaySpinBox, SIGNAL(editingFinished()), this, SLOT(onAutoAwayChanged()));
connect(bodyUI->showInFront, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowInFront);
connect(bodyUI->autoacceptFiles, &QCheckBox::stateChanged, this, &GeneralForm::onAutoAcceptFileChange);
if(bodyUI->autoacceptFiles->isChecked())
if (bodyUI->autoacceptFiles->isChecked())
connect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()), this, SLOT(onAutoSaveDirChange()));
//theme
connect(bodyUI->useEmoticons, &QCheckBox::stateChanged, this, &GeneralForm::onUseEmoticonsChange);
connect(bodyUI->smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int)));
connect(bodyUI->styleBrowser, SIGNAL(currentTextChanged(QString)), this, SLOT(onStyleSelected(QString)));
connect(bodyUI->emoticonSize, SIGNAL(editingFinished()), this, SLOT(onEmoticonSizeChanged()));
connect(bodyUI->themeColorCBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onThemeColorChanged(int)));
connect(bodyUI->emoticonSize, SIGNAL(editingFinished()), this, SLOT(onEmoticonSizeChanged()));
connect(bodyUI->timestamp, SIGNAL(currentIndexChanged(int)), this, SLOT(onTimestampSelected(int)));
//connection
connect(bodyUI->cbEnableIPv6, &QCheckBox::stateChanged, this, &GeneralForm::onEnableIPv6Updated);
connect(bodyUI->cbEnableUDP, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated);
connect(bodyUI->cbUseProxy, &QCheckBox::stateChanged, this, &GeneralForm::onUseProxyUpdated);
connect(bodyUI->proxyType, SIGNAL(currentIndexChanged(int)), this, SLOT(onUseProxyUpdated()));
connect(bodyUI->proxyAddr, &QLineEdit::editingFinished, this, &GeneralForm::onProxyAddrEdited);
connect(bodyUI->proxyPort, SIGNAL(valueChanged(int)), this, SLOT(onProxyPortEdited(int)));
connect(bodyUI->reconnectButton, &QPushButton::clicked, this, &GeneralForm::onReconnectClicked);
connect(bodyUI->cbFauxOfflineMessaging, &QCheckBox::stateChanged, this, &GeneralForm::onFauxOfflineMessaging);
#ifndef QTOX_PLATFORM_EXT
bodyUI->autoAwayLabel->setEnabled(false); // these don't seem to change the appearance of the widgets,
bodyUI->autoAwaySpinBox->setEnabled(false); // though they are unusable
#endif
}
GeneralForm::~GeneralForm()
@ -150,6 +172,12 @@ void GeneralForm::onMakeToxPortableUpdated()
Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked());
}
void GeneralForm::onSetShowSystemTray()
{
Settings::getInstance().setShowSystemTray(bodyUI->showSystemTray->isChecked());
emit parent->setShowSystemTray(bodyUI->showSystemTray->isChecked());
}
void GeneralForm::onSetAutostartInTray()
{
Settings::getInstance().setAutostartInTray(bodyUI->startInTray->isChecked());
@ -160,6 +188,12 @@ void GeneralForm::onSetCloseToTray()
Settings::getInstance().setCloseToTray(bodyUI->closeToTray->isChecked());
}
void GeneralForm::onSetLightTrayIcon()
{
Settings::getInstance().setLightTrayIcon(bodyUI->lightTrayIcon->isChecked());
Widget::getInstance()->updateTrayIcon();
}
void GeneralForm::onSetMinimizeToTray()
{
Settings::getInstance().setMinimizeToTray(bodyUI->minimizeToTray->isChecked());
@ -167,11 +201,11 @@ void GeneralForm::onSetMinimizeToTray()
void GeneralForm::onStyleSelected(QString style)
{
if(bodyUI->styleBrowser->currentIndex() == 0)
if (bodyUI->styleBrowser->currentIndex() == 0)
Settings::getInstance().setStyle("None");
else
Settings::getInstance().setStyle(style);
this->setStyle(QStyleFactory::create(style));
parent->setBodyHeadStyle(style);
}
@ -190,14 +224,13 @@ void GeneralForm::onAutoAwayChanged()
{
int minutes = bodyUI->autoAwaySpinBox->value();
Settings::getInstance().setAutoAwayTime(minutes);
Widget::getInstance()->setIdleTimer(minutes);
}
void GeneralForm::onAutoAcceptFileChange()
{
Settings::getInstance().setAutoSaveEnabled(bodyUI->autoacceptFiles->isChecked());
if(bodyUI->autoacceptFiles->isChecked() == true)
if (bodyUI->autoacceptFiles->isChecked() == true)
connect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()), this, SLOT(onAutoSaveDirChange()));
else
disconnect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()),this, SLOT(onAutoSaveDirChange()));
@ -207,9 +240,9 @@ void GeneralForm::onAutoSaveDirChange()
{
QString previousDir = Settings::getInstance().getGlobalAutoAcceptDir();
QString directory = QFileDialog::getExistingDirectory(0, tr("Choose an auto accept directory","popup title"));
if(directory.isEmpty())
if (directory.isEmpty())
directory = previousDir;
Settings::getInstance().setGlobalAutoAcceptDir(directory);
bodyUI->autoSaveFilesDir->setText(directory);
}
@ -253,11 +286,11 @@ void GeneralForm::onProxyPortEdited(int port)
void GeneralForm::onUseProxyUpdated()
{
bool state = bodyUI->cbUseProxy->isChecked();
int proxytype = bodyUI->proxyType->currentIndex();
bodyUI->proxyAddr->setEnabled(state);
bodyUI->proxyPort->setEnabled(state);
Settings::getInstance().setUseProxy(state);
bodyUI->proxyAddr->setEnabled(proxytype);
bodyUI->proxyPort->setEnabled(proxytype);
Settings::getInstance().setProxyType(proxytype);
}
void GeneralForm::onReconnectClicked()
@ -275,10 +308,11 @@ void GeneralForm::reloadSmiles()
QStringList smiles;
smiles << ":)" << ";)" << ":p" << ":O" << ":["; //just in case...
for(int i = 0; i < emoticons.size(); i++)
for (int i = 0; i < emoticons.size(); i++)
smiles.push_front(emoticons.at(i).first());
int pixSize = 30;
bodyUI->smile1->setPixmap(SmileyPack::getInstance().getAsPixmap(smiles[0]));
bodyUI->smile2->setPixmap(SmileyPack::getInstance().getAsPixmap(smiles[1]));
bodyUI->smile3->setPixmap(SmileyPack::getInstance().getAsPixmap(smiles[2]));
@ -306,3 +340,10 @@ void GeneralForm::onFauxOfflineMessaging()
{
Settings::getInstance().setFauxOfflineMessaging(bodyUI->cbFauxOfflineMessaging->isChecked());
}
void GeneralForm::onThemeColorChanged(int)
{
int index = bodyUI->themeColorCBox->currentIndex();
Settings::getInstance().setThemeColor(index);
Style::setThemeColor(index);
}

View File

@ -34,8 +34,10 @@ private slots:
void onEnableIPv6Updated();
void onTranslationUpdated();
void onMakeToxPortableUpdated();
void onSetShowSystemTray();
void onSetAutostartInTray();
void onSetCloseToTray();
void onSetLightTrayIcon();
void onSmileyBrowserIndexChanged(int index);
void onUDPUpdated();
void onProxyAddrEdited();
@ -54,6 +56,7 @@ private slots:
void onCheckUpdateChanged();
void onSetShowInFront();
void onFauxOfflineMessaging();
void onThemeColorChanged(int);
private:
Ui::GeneralSettings *bodyUI;

View File

@ -38,12 +38,12 @@
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<y>-173</y>
<width>511</width>
<height>698</height>
<height>797</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0,1">
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0,0">
<property name="spacing">
<number>9</number>
</property>
@ -94,47 +94,73 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="trayLayout">
<item>
<widget class="QCheckBox" name="startInTray">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Start in tray</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="closeToTray">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Close to tray</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="minimizeToTray">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Minimize to tray</string>
</property>
</widget>
</item>
</layout>
<widget class="QGroupBox" name="trayGroup">
<property name="title">
<string>System tray integration</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="showSystemTray">
<property name="text">
<string>Show system tray icon</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="trayBehavior">
<item>
<widget class="QCheckBox" name="startInTray">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Start in tray</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="closeToTray">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Close to tray</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="minimizeToTray">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Minimize to tray</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="lightTrayIcon">
<property name="text">
<string>Light icon</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="statusChanges">
@ -210,6 +236,9 @@
<layout class="QHBoxLayout" name="fileLayout">
<item>
<widget class="QCheckBox" name="autoacceptFiles">
<property name="toolTip">
<string comment="autoaccept cb tooltip">You can set this on a per-friend basis by right clicking them.</string>
</property>
<property name="text">
<string>Autoaccept files</string>
</property>
@ -244,8 +273,14 @@
</layout>
</widget>
</item>
<item alignment="Qt::AlignTop">
<item>
<widget class="QGroupBox" name="themeGroup">
<property name="minimumSize">
<size>
<width>0</width>
<height>232</height>
</size>
</property>
<property name="title">
<string>Theme</string>
</property>
@ -258,15 +293,24 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="smileyHLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="smileyPackLabel">
<property name="text">
<string extracomment="Text on smiley pack label">Smiley Pack</string>
</property>
</widget>
</item>
<item>
<item row="0" column="1">
<widget class="QComboBox" name="smileyPackBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -276,75 +320,71 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile1">
<property name="toolTip">
<string notr="true">:)</string>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile1">
<property name="toolTip">
<string notr="true">:)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile2">
<property name="toolTip">
<string notr="true">;)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile3">
<property name="toolTip">
<string notr="true">:p</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile4">
<property name="toolTip">
<string notr="true">:O</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile5">
<property name="toolTip">
<string notr="true">:'(</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile2">
<property name="toolTip">
<string notr="true">;)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile3">
<property name="toolTip">
<string notr="true">:p</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile4">
<property name="toolTip">
<string notr="true">:O</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="smile5">
<property name="toolTip">
<string notr="true">:'(</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="styleHLayout">
<item>
<item row="2" column="0">
<widget class="QLabel" name="styleLabel">
<property name="text">
<string>Style</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<item row="2" column="1">
<widget class="QComboBox" name="styleBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -354,18 +394,31 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="emoiticonHLayout">
<item>
<item row="3" column="0">
<widget class="QLabel" name="themeColorLabel">
<property name="text">
<string>Theme color</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="themeColorCBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="emoticonSizeLabel">
<property name="text">
<string>Emoticon size</string>
</property>
</widget>
</item>
<item>
<item row="4" column="1">
<widget class="QSpinBox" name="emoticonSize">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -390,18 +443,14 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="timestampHLayout">
<item>
<item row="5" column="0">
<widget class="QLabel" name="timestampLabel">
<property name="text">
<string>Timestamp format</string>
</property>
</widget>
</item>
<item>
<item row="5" column="1">
<widget class="QComboBox" name="timestamp">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -416,7 +465,7 @@
</layout>
</widget>
</item>
<item alignment="Qt::AlignTop">
<item>
<widget class="QGroupBox" name="connectionGroup">
<property name="title">
<string>Connection Settings</string>
@ -446,11 +495,40 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbUseProxy">
<property name="text">
<string>Use proxy (SOCKS5)</string>
</property>
</widget>
<layout class="QHBoxLayout" name="proxyLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Proxy type</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="proxyType">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>SOCKS5</string>
</property>
</item>
<item>
<property name="text">
<string>HTTP</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="proxyLayout">
@ -503,5 +581,54 @@
</layout>
</widget>
<resources/>
<connections/>
<connections>
<connection>
<sender>showSystemTray</sender>
<signal>toggled(bool)</signal>
<receiver>startInTray</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>105</x>
<y>144</y>
</hint>
<hint type="destinationlabel">
<x>119</x>
<y>177</y>
</hint>
</hints>
</connection>
<connection>
<sender>showSystemTray</sender>
<signal>toggled(bool)</signal>
<receiver>closeToTray</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>205</x>
<y>146</y>
</hint>
<hint type="destinationlabel">
<x>224</x>
<y>178</y>
</hint>
</hints>
</connection>
<connection>
<sender>showSystemTray</sender>
<signal>toggled(bool)</signal>
<receiver>minimizeToTray</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>180</x>
<y>144</y>
</hint>
<hint type="destinationlabel">
<x>359</x>
<y>178</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -196,9 +196,16 @@ void IdentityForm::onDeleteClicked()
else
{
if (checkContinue(tr("Deletion imminent!","deletion confirmation title"),
tr("Are you sure you want to delete this profile?","deletion confirmation text")))
tr("Are you sure you want to delete this profile?\nAssociated friend information and chat logs will be deleted as well.","deletion confirmation text")))
{
QFile::remove(QDir(Settings::getSettingsDirPath()).filePath(bodyUI->profiles->currentText()+Core::TOX_EXT));
QString profile = bodyUI->profiles->currentText();
QDir dir(Settings::getSettingsDirPath());
QFile::remove(dir.filePath(profile + Core::TOX_EXT));
QFile::remove(dir.filePath(profile + ".ini"));
QFile::remove(HistoryKeeper::getHistoryPath(profile, 0));
QFile::remove(HistoryKeeper::getHistoryPath(profile, 1));
bodyUI->profiles->removeItem(bodyUI->profiles->currentIndex());
bodyUI->profiles->setCurrentText(Settings::getInstance().getCurrentProfile());
}

View File

@ -30,16 +30,13 @@ PrivacyForm::PrivacyForm() :
bodyUI = new Ui::PrivacySettings;
bodyUI->setupUi(this);
bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled());
bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs());
bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox());
connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated()));
connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated()));
connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated()));
connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated()));
connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam()));
connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam()));
connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit()));
}
PrivacyForm::~PrivacyForm()
@ -134,3 +131,48 @@ void PrivacyForm::onEncryptToxUpdated()
if (!Settings::getInstance().getEncryptTox())
Core::getInstance()->clearPassword(Core::ptMain);
}
void PrivacyForm::setNospam()
{
QString newNospam = bodyUI->nospamLineEdit->text();
bool ok;
uint32_t nospam = newNospam.toLongLong(&ok, 16);
if (ok)
Core::getInstance()->setNospam(nospam);
}
void PrivacyForm::present()
{
bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam);
bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled());
bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs());
bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox());
}
void PrivacyForm::generateRandomNospam()
{
QTime time = QTime::currentTime();
qsrand((uint)time.msec());
uint32_t newNospam{0};
for (int i = 0; i < 4; i++)
newNospam = (newNospam<<8) + (qrand() % 256); // Generate byte by byte. For some reason.
Core::getInstance()->setNospam(newNospam);
bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam);
}
void PrivacyForm::onNospamEdit()
{
QString str = bodyUI->nospamLineEdit->text();
int curs = bodyUI->nospamLineEdit->cursorPosition();
if (str.length() != 8)
{
str = QString("00000000").replace(0, str.length(), str);
bodyUI->nospamLineEdit->setText(str);
bodyUI->nospamLineEdit->setCursorPosition(curs);
};
}

View File

@ -30,10 +30,14 @@ public:
PrivacyForm();
~PrivacyForm();
virtual void present();
private slots:
void onEnableLoggingUpdated();
void onTypingNotificationEnabledUpdated();
void setNospam();
void generateRandomNospam();
void onNospamEdit();
void onEncryptLogsUpdated();
void onEncryptToxUpdated();

View File

@ -96,6 +96,46 @@
</layout>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="nospamGroup">
<property name="title">
<string>Nospam</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="nospamLineEdit">
<property name="inputMask">
<string>HHHHHHHH</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="randomNosapamButton">
<property name="text">
<string>Generate random nospam</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>

View File

@ -22,6 +22,7 @@
#include "src/widget/form/settings/identityform.h"
#include "src/widget/form/settings/privacyform.h"
#include "src/widget/form/settings/avform.h"
#include "src/widget/form/settings/advancedform.h"
#include <QTabWidget>
SettingsWidget::SettingsWidget(QWidget* parent)
@ -54,8 +55,9 @@ SettingsWidget::SettingsWidget(QWidget* parent)
IdentityForm* ifrm = new IdentityForm;
PrivacyForm* pfrm = new PrivacyForm;
AVForm* avfrm = new AVForm;
AdvancedForm *expfrm = new AdvancedForm;
GenericForm* cfgForms[] = { gfrm, ifrm, pfrm, avfrm };
GenericForm* cfgForms[] = { gfrm, ifrm, pfrm, avfrm, expfrm };
for (GenericForm* cfgForm : cfgForms)
settingsWidgets->addTab(cfgForm, cfgForm->getFormIcon(), cfgForm->getFormName());

Some files were not shown because too many files have changed in this diff Show More