diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 83%
rename from CONTRIBUTING.md
rename to .github/CONTRIBUTING.md
index f906731af4..ff3c0ad1e2 100644
--- a/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -11,6 +11,7 @@ This document describes how you can contribute to Telegram Desktop. Please read
* [Pull upstream changes into your fork regularly](#pull-upstream-changes-into-your-fork-regularly)
* [How to get your pull request accepted](#how-to-get-your-pull-request-accepted)
* [Keep your pull requests limited to a single issue](#keep-your-pull-requests-limited-to-a-single-issue)
+ * [Squash your commits to a single commit](#squash-your-commits-to-a-single-commit)
* [Don't mix code changes with whitespace cleanup](#dont-mix-code-changes-with-whitespace-cleanup)
* [Keep your code simple!](#keep-your-code-simple)
* [Test your changes!](#test-your-changes)
@@ -72,10 +73,8 @@ For more info, see [GitHub Help][help_change_commit_message].
## Build instructions
-* [Visual Studio 2013][msvc]
-* [XCode 6.4][xcode]
-* [XCode 6.4 for OS X 10.6 and 10.7][xcode_old]
-* [Qt Creator 3.2.0 Ubuntu][qtcreator]
+See the [README.md](README.md#build-instructions) for details on the various build
+environments.
## Pull upstream changes into your fork regularly
@@ -90,9 +89,13 @@ Check the log to be sure that you actually want the changes, before merging:
git log upstream/master
-Then merge the changes that you fetched:
+Then rebase your changes on the latest commits in the `master` branch:
- git merge upstream/master
+ git rebase upstream/master
+
+After that, you have to force push your commits:
+
+ git push --force
For more info, see [GitHub Help][help_fork_repo].
@@ -107,6 +110,21 @@ Pull requests should be as small/atomic as possible. Large, wide-sweeping change
* If you are making spelling corrections in the docs, don't modify other files.
* If you are adding new functions don't '*cleanup*' unrelated functions. That cleanup belongs in another pull request.
+#### Squash your commits to a single commit
+
+To keep the history of the project clean, you should make one commit per pull request.
+If you already have multiple commits, you can add the commits together (squash them) with the following commands in Git Bash:
+
+1. Open `Git Bash` (or `Git Shell`)
+2. Enter following command to squash the recent {N} commits: `git reset --soft HEAD~{N} && git commit` (replace `{N}` with the number of commits you want to squash)
+3. Press i to get into Insert-mode
+4. Enter the commit message of the new commit (and add the [signature](#sign-your-work) at the and)
+5. After adding the message, press ESC to get out of the Insert-mode
+6. Write `:wq` and press Enter to save the new message or write `:q!` to discard your changes
+7. Enter `git push --force` to push the new commit to the remote repository
+
+For example, if you want to squash the last 5 commits, use `git reset --soft HEAD~5 && git commit`
+
### Don't mix code changes with whitespace cleanup
If you change two lines of code and correct 200 lines of whitespace issues in a file the diff on that pull request is functionally unreadable and will be **rejected**. Whitespace cleanups need to be in their own pull request.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000000..42a39d90b9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,26 @@
+
+### Steps to reproduce
+1.
+2.
+3.
+
+### Expected behaviour
+Tell us what should happen
+
+### Actual behaviour
+Tell us what happens instead
+
+### Configuration
+**Operating system:**
+
+**Version of Telegram Desktop:**
+
+### Logs
+Insert logs here (if necessary)
+
+You can type "debugmode" in settings and then see ~/.TelegramDesktop/DebugLogs/log_...txt for log files.
+Type "debugmode" in settings again to disable logs.
diff --git a/.gitignore b/.gitignore
index 19c18e0cfb..a155d81c70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,9 +7,11 @@
/Telegram/SourceFiles/art/sprite_125x.png
/Telegram/SourceFiles/art/sprite_150x.png
/Telegram/*.user
+*.vcxproj.user
*.suo
*.sdf
*.opensdf
+*.opendb
/Telegram/*.aps
/Win32/
ipch/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000..11f305e48e
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,47 @@
+sudo: required
+
+language: cpp
+
+env:
+ - BUILD_VERSION=""
+ - BUILD_VERSION="disable_autoupdate"
+ - BUILD_VERSION="disable_register_custom_scheme"
+ - BUILD_VERSION="disable_crash_reports"
+ - BUILD_VERSION="disable_network_proxy"
+
+arch:
+ packages:
+ - bzr
+ - wget
+ - qt5-base
+
+ - git
+ - patch
+ - libunity
+ - libappindicator-gtk2
+
+ - ffmpeg
+ - icu
+ - jasper
+ - libexif
+ - libmng
+ - libwebp
+ - libxkbcommon-x11
+ - libinput
+ - libproxy
+ - mtdev
+ - openal
+ - libva
+ - desktop-file-utils
+ - gtk-update-icon-cache
+
+ script:
+ - libtool --finish /usr/lib
+ - .travis/build.sh
+
+before_install:
+ - "export TRAVIS_COMMIT_MSG=\"$(git log --format=%B --no-merges -n 1)\""
+ - .travis/check.sh
+
+script:
+ - .travis/arch.sh
\ No newline at end of file
diff --git a/.travis/arch.sh b/.travis/arch.sh
new file mode 100755
index 0000000000..9c917be1d1
--- /dev/null
+++ b/.travis/arch.sh
@@ -0,0 +1,297 @@
+#!/bin/bash
+# Copyright (C) 2016 Mikkel Oscar Lyderik Larsen
+#
+# 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 .
+
+# Source: https://raw.githubusercontent.com/mikkeloscar/arch-travis/master/arch-travis.sh
+
+# Script for setting up and running a travis-ci build in an up to date
+# Arch Linux chroot
+
+ARCH_TRAVIS_MIRROR=${ARCH_TRAVIS_MIRROR:-"https://lug.mtu.edu/archlinux"}
+ARCH_TRAVIS_ARCH_ISO=${ARCH_TRAVIS_ARCH_ISO:-"$(date +%Y.%m).01"}
+mirror_entry='Server = '$ARCH_TRAVIS_MIRROR'/\$repo/os/\$arch'
+archive="archlinux-bootstrap-$ARCH_TRAVIS_ARCH_ISO-x86_64.tar.gz"
+default_root="root.x86_64"
+ARCH_TRAVIS_CHROOT=${ARCH_TRAVIS_CHROOT:-"$default_root"}
+user="travis"
+user_home="/home/$user"
+user_build_dir="/build"
+user_uid=$UID
+
+if [ -n "$CC" ]; then
+ # store travis CC
+ TRAVIS_CC=$CC
+ # reset to gcc for building arch packages
+ CC=gcc
+fi
+
+
+# default packages
+default_packages=("base-devel" "git")
+
+# pacman.conf repository line
+repo_line=70
+
+# setup working Arch Linux chroot
+setup_chroot() {
+ arch_msg "Setting up Arch chroot"
+
+ if [ ! -f $archive ]; then
+ # get root fs
+ curl --fail -O "$ARCH_TRAVIS_MIRROR/iso/$ARCH_TRAVIS_ARCH_ISO/$archive" 2>&1
+ local ret=$?
+
+ # if it fails, try arch iso form the previous month
+ if [ $ret -gt 0 ]; then
+ ARCH_TRAVIS_ARCH_ISO="$(date +%Y.%m -d "-1 month").01"
+ archive="archlinux-bootstrap-$ARCH_TRAVIS_ARCH_ISO-x86_64.tar.gz"
+ as_normal "curl -O $ARCH_TRAVIS_MIRROR/iso/$ARCH_TRAVIS_ARCH_ISO/$archive"
+ fi
+ fi
+
+ # extract root fs
+ as_root "tar xf $archive"
+
+ # remove archive if ARCH_TRAVIS_CLEAN_CHROOT is set
+ if [ -n "$ARCH_TRAVIS_CLEAN_CHROOT" ]; then
+ as_root "rm $archive"
+ fi
+
+ if [ "$ARCH_TRAVIS_CHROOT" != "$default_root" ]; then
+ as_root "mv $default_root $ARCH_TRAVIS_CHROOT"
+ fi
+
+ # don't care for signed packages
+ as_root "sed -i 's|SigLevel = Required DatabaseOptional|SigLevel = Never|' $ARCH_TRAVIS_CHROOT/etc/pacman.conf"
+
+ # enable multilib
+ as_root "sed -i 's|#\[multilib\]|\[multilib\]\nInclude = /etc/pacman.d/mirrorlist|' $ARCH_TRAVIS_CHROOT/etc/pacman.conf"
+
+ # add mirror
+ as_root "echo $mirror_entry >> $ARCH_TRAVIS_CHROOT/etc/pacman.d/mirrorlist"
+
+ # add nameserver to resolv.conf
+ as_root "echo nameserver 8.8.8.8 >> $ARCH_TRAVIS_CHROOT/etc/resolv.conf"
+
+ sudo mount $ARCH_TRAVIS_CHROOT $ARCH_TRAVIS_CHROOT --bind
+ sudo mount --bind /proc $ARCH_TRAVIS_CHROOT/proc
+ sudo mount --bind /sys $ARCH_TRAVIS_CHROOT/sys
+ sudo mount --bind /dev $ARCH_TRAVIS_CHROOT/dev
+ sudo mount --bind /dev/pts $ARCH_TRAVIS_CHROOT/dev/pts
+ sudo mount --bind /dev/shm $ARCH_TRAVIS_CHROOT/dev/shm
+ sudo mount --bind /run $ARCH_TRAVIS_CHROOT/run
+
+ # update packages
+ chroot_as_root "pacman -Syy"
+ chroot_as_root "pacman -Syu ${default_packages[*]} --noconfirm"
+
+ # use LANG=en_US.UTF-8 as expected in travis environments
+ as_root "sed -i 's|#en_US.UTF-8|en_US.UTF-8|' $ARCH_TRAVIS_CHROOT/etc/locale.gen"
+ chroot_as_root "locale-gen"
+
+ # setup non-root user
+ chroot_as_root "useradd -u $user_uid -m -s /bin/bash $user"
+
+ # disable password for sudo users
+ as_root "echo \"$user ALL=(ALL) NOPASSWD: ALL\" >> $ARCH_TRAVIS_CHROOT/etc/sudoers.d/$user"
+
+ # Add build dir
+ chroot_as_root "mkdir $user_build_dir && chown $user $user_build_dir"
+
+ # bind $TRAVIS_BUILD_DIR to chroot build dir
+ sudo mount --bind $TRAVIS_BUILD_DIR $ARCH_TRAVIS_CHROOT$user_build_dir
+
+ # add custom repos
+ add_repositories
+
+ # setup pacaur for AUR packages
+ setup_pacaur
+}
+
+# add custom repositories to pacman.conf
+add_repositories() {
+ if [ ${#CONFIG_REPOS[@]} -gt 0 ]; then
+ for r in "${CONFIG_REPOS[@]}"; do
+ local splitarr=(${r//=/ })
+ ((repo_line+=1))
+ as_root "sed -i '${repo_line}i[${splitarr[0]}]' $ARCH_TRAVIS_CHROOT/etc/pacman.conf"
+ ((repo_line+=1))
+ as_root "sed -i '${repo_line}iServer = ${splitarr[1]}\n' $ARCH_TRAVIS_CHROOT/etc/pacman.conf"
+ ((repo_line+=1))
+ done
+
+ # update repos
+ chroot_as_root "pacman -Syy"
+ fi
+}
+
+# a wrapper which can be used to eventually add fakeroot support.
+sudo_wrapper() {
+ sudo "$@"
+}
+
+# run command as normal user
+as_normal() {
+ local str="$@"
+ run /bin/bash -c "$str"
+}
+
+# run command as root
+as_root() {
+ local str="$@"
+ run sudo_wrapper /bin/bash -c "$str"
+}
+
+# run command in chroot as root
+chroot_as_root() {
+ local str="$@"
+ run sudo_wrapper chroot $ARCH_TRAVIS_CHROOT /bin/bash -c "$str"
+}
+
+# run command in chroot as normal user
+chroot_as_normal() {
+ local str="$@"
+ run sudo_wrapper chroot --userspec=$user:$user $ARCH_TRAVIS_CHROOT /bin/bash \
+ -c "export HOME=$user_home USER=$user TRAVIS_BUILD_DIR=$user_build_dir && cd $user_build_dir && $str"
+}
+
+# run command
+run() {
+ "$@"
+ local ret=$?
+
+ if [ $ret -gt 0 ]; then
+ takedown_chroot
+ exit $ret
+ fi
+}
+
+# run build script
+run_build_script() {
+ local cmd="$@"
+ echo "$ $cmd"
+ sudo_wrapper chroot --userspec=$user:$user $ARCH_TRAVIS_CHROOT /bin/bash -c "export HOME=$user_home USER=$user TRAVIS_BUILD_DIR=$user_build_dir && cd $user_build_dir && $cmd"
+ local ret=$?
+
+ if [ $ret -gt 0 ]; then
+ takedown_chroot
+ exit $ret
+ fi
+}
+
+# setup pacaur
+setup_pacaur() {
+ local cowerarchive="cower.tar.gz"
+ local aururl="https://aur.archlinux.org/cgit/aur.git/snapshot/"
+ # install cower
+ as_normal "curl -O $aururl/$cowerarchive"
+ as_normal "tar xf $cowerarchive"
+ chroot_as_normal "cd cower && makepkg -is --skippgpcheck --noconfirm"
+ as_root "rm -r cower"
+ as_normal "rm $cowerarchive"
+ # install pacaur
+ chroot_as_normal "cower -dd pacaur"
+ chroot_as_normal "cd pacaur && makepkg -is --noconfirm"
+ chroot_as_normal "rm -rf pacaur"
+}
+
+# install package through pacaur
+_pacaur() {
+ local pacaur="pacaur -S $@ --noconfirm --noedit"
+ chroot_as_normal "$pacaur"
+}
+
+# takedown chroot
+# unmounts anything mounted in the chroot setup
+takedown_chroot() {
+ sudo umount $ARCH_TRAVIS_CHROOT/{run,dev/shm,dev/pts,dev,sys,proc}
+ sudo umount $ARCH_TRAVIS_CHROOT$user_build_dir
+ sudo umount $ARCH_TRAVIS_CHROOT
+
+ if [ -n "$ARCH_TRAVIS_CLEAN_CHROOT" ]; then
+ as_root "rm -rf $ARCH_TRAVIS_CHROOT"
+ fi
+}
+
+# read value from .travis.yml
+travis_yml() {
+ ruby -ryaml -e 'puts ARGV[1..-1].inject(YAML.load(File.read(ARGV[0]))) {|acc, key| acc[key] }' .travis.yml $@
+}
+
+read_config() {
+ old_ifs=$IFS
+ IFS=$'\n'
+ CONFIG_BUILD_SCRIPTS=($(travis_yml arch script))
+ CONFIG_PACKAGES=($(travis_yml arch packages))
+ CONFIG_REPOS=($(travis_yml arch repos))
+ IFS=$old_ifs
+}
+
+# run build scripts defined in .travis.yml
+build_scripts() {
+ if [ ${#CONFIG_BUILD_SCRIPTS[@]} -gt 0 ]; then
+ for script in "${CONFIG_BUILD_SCRIPTS[@]}"; do
+ run_build_script $script
+ done
+ else
+ echo "No build scripts defined"
+ takedown_chroot
+ exit 1
+ fi
+}
+
+# install packages defined in .travis.yml
+install_packages() {
+ for package in "${CONFIG_PACKAGES[@]}"; do
+ _pacaur $package
+ done
+}
+
+# install custom compiler if CC != gcc
+install_c_compiler() {
+ if [ "$TRAVIS_CC" != "gcc" ]; then
+ _pacaur "$TRAVIS_CC"
+ fi
+}
+
+arch_msg() {
+ lightblue='\033[1;34m'
+ reset='\e[0m'
+ echo -e "${lightblue}$@${reset}"
+}
+
+# read .travis.yml
+read_config
+
+echo "travis_fold:start:arch_travis"
+setup_chroot
+
+install_packages
+
+if [ -n "$CC" ]; then
+ install_c_compiler
+
+ # restore CC
+ CC=$TRAVIS_CC
+fi
+echo "travis_fold:end:arch_travis"
+echo ""
+
+arch_msg "Running travis build"
+build_scripts
+
+takedown_chroot
+
+# vim:set ts=2 sw=2 et:
diff --git a/.travis/build.sh b/.travis/build.sh
new file mode 100755
index 0000000000..7deae1f953
--- /dev/null
+++ b/.travis/build.sh
@@ -0,0 +1,160 @@
+#!/bin/bash
+# Installs libs and compiles tdesktop
+
+run() {
+ info_msg "Build version: ${BUILD_VERSION}"
+
+ downloadLibs
+ prepare
+ build
+ check
+}
+
+downloadLibs() {
+ travis_fold_start "download_libs"
+ # Move telegram project to subfolder
+ mkdir tdesktop
+ mv -f Telegram tdesktop
+
+ # Download libraries
+ info_msg "QT-Version: ${_qtver}, SRC-Dir: ${srcdir}"
+
+ echo -e "\nDownload and extract qt"
+ qt_file=qt-everywhere-opensource-src-$_qtver.tar.xz
+ echo -e "QT-File: ${qt_file}"
+
+ wget "http://download.qt.io/official_releases/qt/${_qtver%.*}/$_qtver/single/$qt_file"
+ tar xf $qt_file
+ rm $qt_file
+
+ echo -e "Clone Breakpad"
+ git clone https://chromium.googlesource.com/breakpad/breakpad breakpad
+
+ echo -e "\nClone Linux Syscall Support"
+ git clone https://chromium.googlesource.com/linux-syscall-support breakpad-lss
+
+ echo -e "\nLets view the folder content"
+ ls
+ travis_fold_end "download_libs"
+}
+
+prepare() {
+ travis_fold_start "prepare"
+ start_msg "Preparing the libraries..."
+
+ cd "$srcdir/tdesktop"
+
+ mkdir -p "$srcdir/Libraries"
+
+ local qt_patch_file="$srcdir/tdesktop/Telegram/_qtbase_${_qtver//./_}_patch.diff"
+ if [ "$qt_patch_file" -nt "$srcdir/Libraries/QtStatic" ]; then
+ rm -rf "$srcdir/Libraries/QtStatic"
+ mv "$srcdir/qt-everywhere-opensource-src-$_qtver" "$srcdir/Libraries/QtStatic"
+ cd "$srcdir/Libraries/QtStatic/qtbase"
+ patch -p1 -i "$qt_patch_file"
+ fi
+
+ if [ ! -h "$srcdir/Libraries/breakpad" ]; then
+ ln -s "$srcdir/breakpad" "$srcdir/Libraries/breakpad"
+ ln -s "$srcdir/breakpad-lss" "$srcdir/Libraries/breakpad/src/third_party/lss"
+ fi
+
+ sed -i 's/CUSTOM_API_ID//g' "$srcdir/tdesktop/Telegram/Telegram.pro"
+ sed -i 's,LIBS += /usr/local/lib/libxkbcommon.a,,g' "$srcdir/tdesktop/Telegram/Telegram.pro"
+ sed -i 's,LIBS += /usr/local/lib/libz.a,LIBS += -lz,g' "$srcdir/tdesktop/Telegram/Telegram.pro"
+
+ local options=""
+
+ if [[ $BUILD_VERSION == *"disable_autoupdate"* ]]; then
+ options+="\nDEFINES += TDESKTOP_DISABLE_AUTOUPDATE"
+ fi
+
+ if [[ $BUILD_VERSION == *"disable_register_custom_scheme"* ]]; then
+ options+="\nDEFINES += TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME"
+ fi
+
+ if [[ $BUILD_VERSION == *"disable_crash_reports"* ]]; then
+ options+="\nDEFINES += TDESKTOP_DISABLE_CRASH_REPORTS"
+ fi
+
+ if [[ $BUILD_VERSION == *"disable_network_proxy"* ]]; then
+ options+="\nDEFINES += TDESKTOP_DISABLE_NETWORK_PROXY"
+ fi
+
+ options+='\nINCLUDEPATH += "/usr/lib/glib-2.0/include"'
+ options+='\nINCLUDEPATH += "/usr/lib/gtk-2.0/include"'
+ options+='\nINCLUDEPATH += "/usr/include/opus"'
+ options+='\nLIBS += -lcrypto -lssl'
+
+ info_msg "Build options: ${options}"
+
+ echo -e "${options}" >> "$srcdir/tdesktop/Telegram/Telegram.pro"
+
+ success_msg "Prepare done! :)"
+ travis_fold_end "prepare"
+}
+
+build() {
+ start_msg "Building the projects..."
+
+ info_msg "Build patched Qt"
+ # Build patched Qt
+ cd "$srcdir/Libraries/QtStatic"
+ ./configure -prefix "$srcdir/qt" -release -opensource -confirm-license -qt-zlib \
+ -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \
+ -qt-xkbcommon-x11 -no-opengl -static -nomake examples -nomake tests
+ make --silent module-qtbase module-qtimageformats
+ make --silent module-qtbase-install_subtargets module-qtimageformats-install_subtargets
+
+ export PATH="$srcdir/qt/bin:$PATH"
+
+ info_msg "Build breakpad"
+ # Build breakpad
+ cd "$srcdir/Libraries/breakpad"
+ ./configure
+ make --silent
+
+ info_msg "Build MetaStyle"
+ # Build MetaStyle
+ mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateStyle"
+ cd "$srcdir/tdesktop/Linux/DebugIntermediateStyle"
+ qmake CONFIG+=debug "../../Telegram/MetaStyle.pro"
+ make --silent
+
+ info_msg "Build MetaLang"
+ # Build MetaLang
+ mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateLang"
+ cd "$srcdir/tdesktop/Linux/DebugIntermediateLang"
+ qmake CONFIG+=debug "../../Telegram/MetaLang.pro"
+ make --silent
+
+ info_msg "Build Telegram Desktop"
+ # Build Telegram Desktop
+ mkdir -p "$srcdir/tdesktop/Linux/ReleaseIntermediate"
+ cd "$srcdir/tdesktop/Linux/ReleaseIntermediate"
+
+ qmake CONFIG+=release "../../Telegram/Telegram.pro"
+ local pattern="^PRE_TARGETDEPS +="
+ grep "$pattern" "$srcdir/tdesktop/Telegram/Telegram.pro" | sed "s/$pattern//g" | xargs make
+
+ qmake CONFIG+=release "../../Telegram/Telegram.pro"
+ make
+}
+
+check() {
+ local filePath="$srcdir/tdesktop/Linux/Release/Telegram"
+ if test -f "$filePath"; then
+ success_msg "Build successful done! :)"
+
+ local size;
+ size=$(stat -c %s "$filePath")
+ success_msg "File size of ${filePath}: ${size} Bytes"
+ else
+ error_msg "Build error, output file does not exist"
+ exit 1
+ fi
+}
+
+source ./.travis/common.sh
+
+run
diff --git a/.travis/check.sh b/.travis/check.sh
new file mode 100755
index 0000000000..3fe7c7fd3c
--- /dev/null
+++ b/.travis/check.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Checks commit message, ...
+
+run() {
+ checkCommitMessage
+}
+
+checkCommitMessage() {
+ info_msg "Commit message: ${TRAVIS_COMMIT_MSG}";
+ info_msg "Is pull request: ${TRAVIS_PULL_REQUEST}";
+
+ if [[ $TRAVIS_PULL_REQUEST != "false" ]];then
+ if [[ $TRAVIS_COMMIT_MSG != *"Signed-off-by: "* ]];then
+ error_msg "The commit message does not contain the signature!"
+ error_msg "More information: https://github.com/telegramdesktop/tdesktop/blob/master/.github/CONTRIBUTING.md#sign-your-work"
+ exit 1
+ else
+ success_msg "Commit message contains signature"
+ fi
+ fi
+}
+
+source ./.travis/common.sh
+
+run
diff --git a/.travis/common.sh b/.travis/common.sh
new file mode 100755
index 0000000000..dd3bb19673
--- /dev/null
+++ b/.travis/common.sh
@@ -0,0 +1,40 @@
+# set colors
+RCol='\e[0m' # Text Reset
+
+# Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity Backgrounds
+Bla='\e[0;30m'; BBla='\e[1;30m'; UBla='\e[4;30m'; IBla='\e[0;90m'; BIBla='\e[1;90m'; On_Bla='\e[40m'; On_IBla='\e[0;100m';
+Red='\e[0;31m'; BRed='\e[1;31m'; URed='\e[4;31m'; IRed='\e[0;91m'; BIRed='\e[1;91m'; On_Red='\e[41m'; On_IRed='\e[0;101m';
+Gre='\e[0;32m'; BGre='\e[1;32m'; UGre='\e[4;32m'; IGre='\e[0;92m'; BIGre='\e[1;92m'; On_Gre='\e[42m'; On_IGre='\e[0;102m';
+Yel='\e[0;33m'; BYel='\e[1;33m'; UYel='\e[4;33m'; IYel='\e[0;93m'; BIYel='\e[1;93m'; On_Yel='\e[43m'; On_IYel='\e[0;103m';
+Blu='\e[0;34m'; BBlu='\e[1;34m'; UBlu='\e[4;34m'; IBlu='\e[0;94m'; BIBlu='\e[1;94m'; On_Blu='\e[44m'; On_IBlu='\e[0;104m';
+Pur='\e[0;35m'; BPur='\e[1;35m'; UPur='\e[4;35m'; IPur='\e[0;95m'; BIPur='\e[1;95m'; On_Pur='\e[45m'; On_IPur='\e[0;105m';
+Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m'; BICya='\e[1;96m'; On_Cya='\e[46m'; On_ICya='\e[0;106m';
+Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m';
+
+# Set variables
+_qtver=5.5.1
+srcdir=${PWD}
+
+start_msg() {
+ echo -e "\n${Gre}$*${RCol}"
+}
+
+info_msg() {
+ echo -e "\n${Cya}$*${RCol}"
+}
+
+error_msg() {
+ echo -e "\n${BRed}$*${RCol}"
+}
+
+success_msg() {
+ echo -e "\n${BGre}$*${RCol}"
+}
+
+travis_fold_start() {
+ echo "travis_fold:start:$*"
+}
+
+travis_fold_end() {
+ echo "travis_fold:end:$*"
+}
diff --git a/README.md b/README.md
index b2dc768a2c..32398eb243 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,16 @@
This is the complete source code and the build instructions for the alpha version of the official desktop client for the [Telegram][telegram] messenger, based on the [Telegram API][telegram_api] and the [MTProto][telegram_proto] secure protocol.
+[](https://travis-ci.org/telegramdesktop/tdesktop)
+
The source code is published under GPLv3 with OpenSSL exception, the license is available [here][license].
## Supported systems
* Windows XP - Windows 10 (**not** RT)
-* Mac OS X 10.8 - Mac OS X 10.10
+* Mac OS X 10.8 - Mac OS X 10.11
* Mac OS X 10.6 - Mac OS X 10.7 (separate build)
-* Ubuntu 12.04 - Ubuntu 14.04
+* Ubuntu 12.04 - Ubuntu 15.04
* Fedora 22
## Third-party libraries
@@ -20,6 +22,8 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
* libexif 0.6.20 ([LGPL](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html))
* LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html))
* liblzma ([public domain](http://tukaani.org/xz/))
+* Google Breakpad ([License](https://chromium.googlesource.com/breakpad/breakpad/+/master/LICENSE))
+* Google Crashpad ([Apache License 2.0](https://chromium.googlesource.com/crashpad/crashpad/+/master/LICENSE))
* OpenAL Soft ([LGPL](http://kcat.strangesoft.net/openal.html))
* Opus codec ([BSD license](http://www.opus-codec.org/license/))
* FFmpeg ([LGPL](https://www.ffmpeg.org/legal.html))
@@ -31,6 +35,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
* [XCode 7][xcode]
* [XCode 7 for OS X 10.6 and 10.7][xcode_old]
* [Qt Creator 3.5.1 Ubuntu][qtcreator]
+* [Using qmake on GNU/Linux][qmake]
## Projects in Telegram solution
@@ -46,21 +51,6 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
Compiles given files to single update file, compresses it with lzma and signs with a private key. It is not built in **Debug** and **Release** configurations of Telegram solution, because private key is inaccessible.
-* ### Prepare
-
- Prepares a release for deployment, puts all current files to deploy/{version} folder.
-
- **Windows**:
- * tsetup{version}.exe installer
- * Telegram.exe
- * Telegram.pdb (debug info for crash minidumps view)
- * tupdate{updversion} binary lzma update archive
-
- **Mac**:
- * tsetup{version}.dmg
- * Telegram.app
- * tmacupd{updversion} binary lzma update archive
-
* ### MetaEmoji
Creates four sprites and text2emoji replace code
@@ -92,7 +82,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
* ### MetaLang
- Creates from languagepack file `Resources/lang.txt` language constants code and language file parse code:
+ Creates from languagepack file `Resources/lang.strings` language constants code and language file parse code:
* GeneratedFiles/lang.h
* GeneratedFiles/lang.cpp
@@ -102,7 +92,8 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
[telegram_api]: https://core.telegram.org
[telegram_proto]: https://core.telegram.org/mtproto
[license]: LICENSE
-[msvc]: MSVC.md
-[xcode]: XCODE.md
-[xcode_old]: XCODEold.md
-[qtcreator]: QTCREATOR.md
+[msvc]: doc/building-msvc.md
+[xcode]: doc/building-xcode.md
+[xcode_old]: doc/building-xcode-old.md
+[qtcreator]: doc/building-qtcreator.md
+[qmake]: doc/building-qmake.md
diff --git a/Telegram.sln b/Telegram.sln
index 836d4a043e..6487c468ac 100644
--- a/Telegram.sln
+++ b/Telegram.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.30501.0
+# Visual Studio 14
+VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Telegram", "Telegram\Telegram.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}"
ProjectSection(ProjectDependencies) = postProject
@@ -18,10 +18,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Updater", "Telegram\Updater
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MetaLang", "Telegram\MetaLang.vcxproj", "{E417CAA4-259B-4C99-88E3-805F1300E8EB}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F863EAD-33C9-4014-A573-93F085BA9CB1}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "codegen", "codegen", "{2F863EAD-33C9-4014-A573-93F085BA9CB1}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Packer", "Telegram\Packer.vcxproj", "{56A9A4B2-21E5-4360-AFA8-85B43AC43B08}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "codegen_style", "Telegram\build\vc\codegen_style\codegen_style.vcxproj", "{E4DF8176-4DEF-4859-962F-B497E3E7A323}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -82,8 +84,21 @@ Global
{56A9A4B2-21E5-4360-AFA8-85B43AC43B08}.Deploy|x64.ActiveCfg = Release|Win32
{56A9A4B2-21E5-4360-AFA8-85B43AC43B08}.Release|Win32.ActiveCfg = Release|Win32
{56A9A4B2-21E5-4360-AFA8-85B43AC43B08}.Release|x64.ActiveCfg = Release|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Debug|Win32.Build.0 = Debug|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Debug|x64.ActiveCfg = Debug|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|Win32.ActiveCfg = Debug|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|Win32.Build.0 = Debug|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|x64.ActiveCfg = Release|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|x64.Build.0 = Release|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Release|Win32.ActiveCfg = Release|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Release|Win32.Build.0 = Release|Win32
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Release|x64.ActiveCfg = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {E4DF8176-4DEF-4859-962F-B497E3E7A323} = {2F863EAD-33C9-4014-A573-93F085BA9CB1}
+ EndGlobalSection
EndGlobal
diff --git a/Telegram/Build.bat b/Telegram/Build.bat
index 650a937ee5..788f9c7668 100644
--- a/Telegram/Build.bat
+++ b/Telegram/Build.bat
@@ -1,4 +1,5 @@
@echo OFF
+setlocal
FOR /F "tokens=1,2* delims= " %%i in (Version) do set "%%i=%%j"
@@ -29,6 +30,8 @@ set "HomePath=..\..\Telegram"
set "ReleasePath=..\Win32\Deploy"
set "DeployPath=%ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStrFull%"
set "SignPath=..\..\TelegramPrivate\Sign.bat"
+set "BinaryName=Telegram"
+set "DropboxSymbolsPath=Z:\Dropbox\Telegram\symbols"
if %BetaVersion% neq 0 (
if exist %DeployPath%\ (
@@ -71,9 +74,13 @@ echo .
echo Version %AppVersionStrFull% build successfull. Preparing..
echo .
+echo Dumping debug symbols..
+call ..\..\Libraries\breakpad\src\tools\windows\binaries\dump_syms.exe %ReleasePath%\%BinaryName%.pdb > %ReleasePath%\%BinaryName%.sym
+echo Done!
+
set "PATH=%PATH%;C:\Program Files\7-Zip;C:\Program Files (x86)\Inno Setup 5"
-call %SignPath% %ReleasePath%\Telegram.exe
+call %SignPath% %ReleasePath%\%BinaryName%.exe
if %errorlevel% neq 0 goto error
call %SignPath% %ReleasePath%\Updater.exe
@@ -90,7 +97,7 @@ if %BetaVersion% equ 0 (
)
cd %ReleasePath%
-call Packer.exe -version %VersionForPacker% -path Telegram.exe -path Updater.exe %DevParam%
+call Packer.exe -version %VersionForPacker% -path %BinaryName%.exe -path Updater.exe %DevParam%
cd %HomePath%
if %errorlevel% neq 0 goto error
@@ -109,6 +116,21 @@ if %BetaVersion% neq 0 (
set "PortableFile=tbeta%BetaVersion%_%BetaSignature%.zip"
)
+for /f ^"usebackq^ eol^=^
+
+^ delims^=^" %%a in (%ReleasePath%\%BinaryName%.sym) do (
+ set "SymbolsHashLine=%%a"
+ goto symbolslinedone
+)
+:symbolslinedone
+FOR /F "tokens=1,2,3,4* delims= " %%i in ("%SymbolsHashLine%") do set "SymbolsHash=%%l"
+
+echo Copying %BinaryName%.sym to %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash%
+if not exist %DropboxSymbolsPath%\%BinaryName%.pdb mkdir %DropboxSymbolsPath%\%BinaryName%.pdb
+if not exist %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash% mkdir %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash%
+xcopy %ReleasePath%\%BinaryName%.sym %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash%\
+echo Done!
+
if not exist %ReleasePath%\deploy mkdir %ReleasePath%\deploy
if not exist %ReleasePath%\deploy\%AppVersionStrMajor% mkdir %ReleasePath%\deploy\%AppVersionStrMajor%
mkdir %DeployPath%
@@ -144,7 +166,7 @@ if not exist %DeployPath%\%PortableFile% goto error
if %BetaVersion% equ 0 (
if not exist %DeployPath%\%SetupFile% goto error
)
-if not exist %DeployPath%\Telegram.pdb goto error
+if not exist %DeployPath%\%BinaryName%.pdb goto error
if not exist %DeployPath%\Updater.exe goto error
if not exist %DeployPath%\Updater.pdb goto error
if not exist %FinalReleasePath%\%AppVersionStrMajor% mkdir %FinalReleasePath%\%AppVersionStrMajor%
@@ -157,7 +179,7 @@ if %BetaVersion% equ 0 (
) else (
xcopy %DeployPath%\%BetaKeyFile% %FinalDeployPath%\ /Y
)
-xcopy %DeployPath%\Telegram.pdb %FinalDeployPath%\
+xcopy %DeployPath%\%BinaryName%.pdb %FinalDeployPath%\
xcopy %DeployPath%\Updater.exe %FinalDeployPath%\
xcopy %DeployPath%\Updater.pdb %FinalDeployPath%\
diff --git a/Telegram/Build.sh b/Telegram/Build.sh
index 3aa9b88f66..650e3692ba 100755
--- a/Telegram/Build.sh
+++ b/Telegram/Build.sh
@@ -1,5 +1,7 @@
set -e
+FastParam="$1"
+
while IFS='' read -r line || [[ -n "$line" ]]; do
set $line
eval $1="$2"
@@ -37,6 +39,7 @@ if [ "$BuildTarget" == "linux" ]; then
WorkPath="./../Linux"
FixScript="$HomePath/FixMake.sh"
ReleasePath="./../Linux/Release"
+ BinaryName="Telegram"
elif [ "$BuildTarget" == "linux32" ]; then
echo "Building version $AppVersionStrFull for Linux 32bit.."
UpdateFile="tlinux32upd$AppVersion"
@@ -44,6 +47,7 @@ elif [ "$BuildTarget" == "linux32" ]; then
WorkPath="./../Linux"
FixScript="$HomePath/FixMake32.sh"
ReleasePath="./../Linux/Release"
+ BinaryName="Telegram"
elif [ "$BuildTarget" == "mac" ]; then
echo "Building version $AppVersionStrFull for OS X 10.8+.."
UpdateFile="tmacupd$AppVersion"
@@ -104,22 +108,25 @@ fi
#fi
if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then
+
+ DropboxSymbolsPath="/media/psf/Home/Dropbox/Telegram/symbols"
+
mkdir -p "$WorkPath/ReleaseIntermediateUpdater"
cd "$WorkPath/ReleaseIntermediateUpdater"
- /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Updater.pro"
+ /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Updater.pro" -r -spec linux-g++
make
echo "Updater build complete!"
cd "$HomePath"
mkdir -p "$WorkPath/ReleaseIntermediate"
cd "$WorkPath/ReleaseIntermediate"
- /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Telegram.pro"
+ /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Telegram.pro" -r -spec linux-g++
eval "$FixScript"
make
- echo "Telegram build complete!"
+ echo "$BinaryName build complete!"
cd "$HomePath"
- if [ ! -f "$ReleasePath/Telegram" ]; then
- echo "Telegram not found!"
+ if [ ! -f "$ReleasePath/$BinaryName" ]; then
+ echo "$BinaryName not found!"
exit 1
fi
@@ -128,8 +135,16 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then
exit 1
fi
+ echo "Dumping debug symbols.."
+ "./../../Libraries/breakpad/src/tools/linux/dump_syms/dump_syms" "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym"
+ echo "Done!"
+
+ echo "Stripping the executable.."
+ strip -s "$ReleasePath/$BinaryName"
+ echo "Done!"
+
echo "Preparing version $AppVersionStrFull, executing Packer.."
- cd "$ReleasePath" && "./Packer" -path Telegram -path Updater -version $VersionForPacker $DevParam && cd "$HomePath"
+ cd "$ReleasePath" && "./Packer" -path "$BinaryName" -path Updater -version $VersionForPacker $DevParam && cd "$HomePath"
echo "Packer done!"
if [ "$BetaVersion" != "0" ]; then
@@ -146,6 +161,12 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then
SetupFile="tbeta${BetaVersion}_${BetaSignature}.tar.xz"
fi
+ SymbolsHash=`head -n 1 "$ReleasePath/$BinaryName.sym" | awk -F " " 'END {print $4}'`
+ echo "Copying $BinaryName.sym to $DropboxSymbolsPath/$BinaryName/$SymbolsHash"
+ mkdir -p "$DropboxSymbolsPath/$BinaryName/$SymbolsHash"
+ cp "$ReleasePath/$BinaryName.sym" "$DropboxSymbolsPath/$BinaryName/$SymbolsHash/"
+ echo "Done!"
+
if [ ! -d "$ReleasePath/deploy" ]; then
mkdir "$ReleasePath/deploy"
fi
@@ -154,21 +175,25 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then
mkdir "$ReleasePath/deploy/$AppVersionStrMajor"
fi
- echo "Copying Telegram, Updater and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStrFull..";
+ echo "Copying $BinaryName, Updater and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStrFull..";
mkdir "$DeployPath"
- mkdir "$DeployPath/Telegram"
- mv "$ReleasePath/Telegram" "$DeployPath/Telegram/"
- mv "$ReleasePath/Updater" "$DeployPath/Telegram/"
+ mkdir "$DeployPath/$BinaryName"
+ mv "$ReleasePath/$BinaryName" "$DeployPath/$BinaryName/"
+ mv "$ReleasePath/Updater" "$DeployPath/$BinaryName/"
mv "$ReleasePath/$UpdateFile" "$DeployPath/"
if [ "$BetaVersion" != "0" ]; then
mv "$ReleasePath/$BetaKeyFile" "$DeployPath/"
fi
- cd "$DeployPath" && tar -cJvf "$SetupFile" "Telegram/" && cd "./../../../$HomePath"
+ cd "$DeployPath" && tar -cJvf "$SetupFile" "$BinaryName/" && cd "./../../../$HomePath"
fi
if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarget" == "macstore" ]; then
- touch "./SourceFiles/telegram.qrc"
+ DropboxSymbolsPath="./../../../Dropbox/Telegram/symbols"
+
+ if [ "$FastParam" != "fast" ]; then
+ touch "./SourceFiles/telegram.qrc"
+ fi
xcodebuild -project Telegram.xcodeproj -alltargets -configuration Release build
if [ ! -d "$ReleasePath/$BinaryName.app" ]; then
@@ -181,6 +206,28 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg
exit 1
fi
+ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then
+ echo "Removing Updater debug symbols.."
+ rm -rf "$ReleasePath/$BinaryName.app/Contents/Frameworks/Updater.dSYM"
+ echo "Done!"
+ fi
+
+ echo "Dumping debug symbols.."
+ "./../../Libraries/breakpad/src/tools/mac/dump_syms/build/Release/dump_syms" "$ReleasePath/$BinaryName.app.dSYM" > "$ReleasePath/$BinaryName.sym" 2>/dev/null
+ echo "Done!"
+
+ echo "Stripping the executable.."
+ strip "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName"
+ echo "Done!"
+
+ echo "Signing the application.."
+ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then
+ codesign --force --deep --sign "Developer ID Application: John Preston" "$ReleasePath/$BinaryName.app"
+ elif [ "$BuildTarget" == "macstore" ]; then
+ codesign --force --deep --sign "3rd Party Mac Developer Application: TELEGRAM MESSENGER LLP (6N38VWS5BX)" "$ReleasePath/$BinaryName.app" --entitlements "Telegram/Telegram Desktop.entitlements"
+ fi
+ echo "Done!"
+
AppUUID=`dwarfdump -u "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" | awk -F " " '{print $2}'`
DsymUUID=`dwarfdump -u "$ReleasePath/$BinaryName.app.dSYM" | awk -F " " '{print $2}'`
if [ "$AppUUID" != "$DsymUUID" ]; then
@@ -215,6 +262,12 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg
fi
fi
+ SymbolsHash=`head -n 1 "$ReleasePath/$BinaryName.sym" | awk -F " " 'END {print $4}'`
+ echo "Copying $BinaryName.sym to $DropboxSymbolsPath/$BinaryName/$SymbolsHash"
+ mkdir -p "$DropboxSymbolsPath/$BinaryName/$SymbolsHash"
+ cp "$ReleasePath/$BinaryName.sym" "$DropboxSymbolsPath/$BinaryName/$SymbolsHash/"
+ echo "Done!"
+
if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then
if [ "$BetaVersion" == "0" ]; then
cd "$ReleasePath"
@@ -254,10 +307,10 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg
if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then
echo "Copying $BinaryName.app and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStr..";
mkdir "$DeployPath"
- mkdir "$DeployPath/Telegram"
- cp -r "$ReleasePath/$BinaryName.app" "$DeployPath/Telegram/"
+ mkdir "$DeployPath/$BinaryName"
+ cp -r "$ReleasePath/$BinaryName.app" "$DeployPath/$BinaryName/"
if [ "$BetaVersion" != "0" ]; then
- cd "$DeployPath" && zip -r "$SetupFile" "Telegram" && mv "$SetupFile" "./../../../" && cd "./../../../$HomePath"
+ cd "$DeployPath" && zip -r "$SetupFile" "$BinaryName" && mv "$SetupFile" "./../../../" && cd "./../../../$HomePath"
mv "$ReleasePath/$BetaKeyFile" "$DeployPath/"
fi
mv "$ReleasePath/$BinaryName.app.dSYM" "$DeployPath/"
@@ -294,6 +347,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg
rm "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName"
rm -rf "$ReleasePath/$BinaryName.app/Contents/_CodeSignature"
+ mkdir -p "$DropboxDeployPath"
cp -v "$DeployPath/$BinaryName.app" "$DropboxDeployPath/"
cp -rv "$DeployPath/$BinaryName.app.dSYM" "$DropboxDeployPath/"
fi
diff --git a/Telegram/Deploy.sh b/Telegram/Deploy.sh
index 058cbf09d2..3f40b0fc75 100755
--- a/Telegram/Deploy.sh
+++ b/Telegram/Deploy.sh
@@ -57,7 +57,11 @@ elif [ "$BuildTarget" == "mac" ]; then
echo "Deploying version $AppVersionStrFull for Windows.."
else
DeployMac="1"
- DeployMac32="1"
+ if [ "$BetaVersion" != "0" ]; then
+ DeployMac32="0"
+ else
+ DeployMac32="1"
+ fi
DeployWin="1"
echo "Deploying three versions of $AppVersionStrFull: for Windows, OS X 10.6 and 10.7 and OS X 10.8+.."
fi
@@ -165,13 +169,7 @@ fi
fi
fi
- if [ ! -d "$DropboxPath" ]; then
- mkdir "$DropboxPath"
- fi
-
- if [ ! -d "$DropboxDeployPath" ]; then
- mkdir "$DropboxDeployPath"
- fi
+ mkdir -p "$DropboxDeployPath"
fi
#fi
@@ -194,7 +192,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build
fi
if [ "$DeployMac" == "1" ]; then
- cp -v "$DeployPath/$UpdateFile" "$DropboxDeployPath/"
cp -v "$DeployPath/$SetupFile" "$DropboxDeployPath/$DropboxSetupFile"
if [ -d "$DropboxDeployPath/Telegram.app.dSYM" ]; then
rm -rf "$DropboxDeployPath/Telegram.app.dSYM"
@@ -202,7 +199,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build
cp -rv "$DeployPath/Telegram.app.dSYM" "$DropboxDeployPath/"
fi
if [ "$DeployMac32" == "1" ]; then
- mv -v "$Mac32DeployPath/$Mac32UpdateFile" "$DropboxDeployPath/"
mv -v "$Mac32DeployPath/$Mac32SetupFile" "$DropboxDeployPath/$DropboxMac32SetupFile"
if [ -d "$DropboxDeployPath/Telegram32.app.dSYM" ]; then
rm -rf "$DropboxDeployPath/Telegram32.app.dSYM"
@@ -213,7 +209,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build
mv -v "$WinDeployPath/Telegram.pdb" "$DropboxDeployPath/"
mv -v "$WinDeployPath/Updater.exe" "$DropboxDeployPath/"
mv -v "$WinDeployPath/Updater.pdb" "$DropboxDeployPath/"
- mv -v "$WinDeployPath/$WinUpdateFile" "$DropboxDeployPath/"
if [ "$BetaVersion" == "0" ]; then
mv -v "$WinDeployPath/$WinSetupFile" "$DropboxDeployPath/"
fi
diff --git a/Telegram/FixMake.sh b/Telegram/FixMake.sh
index 21abada2b2..74b2c6b036 100755
--- a/Telegram/FixMake.sh
+++ b/Telegram/FixMake.sh
@@ -11,12 +11,7 @@ Replace () {
}
Replace '\-llzma' '\/usr\/lib\/x86_64\-linux\-gnu\/liblzma\.a'
-Replace '\-lz' '\/usr\/lib\/x86_64\-linux\-gnu\/libz\.a'
-Replace '\-lssl' '\/usr\/lib\/x86_64\-linux\-gnu\/libssl\.a'
-Replace '\-lcrypto' '\/usr\/lib\/x86_64\-linux\-gnu\/libcrypto\.a'
-Replace '\-lexif' '\/usr\/lib\/x86_64\-linux\-gnu\/libexif\.a'
-Replace '\-lgobject\-2\.0' '\/usr\/lib\/x86_64\-linux\-gnu\/libgobject\-2\.0\.a \/usr\/lib\/x86_64\-linux\-gnu\/libffi\.a'
-Replace '\-lXi' '\/usr\/lib\/x86_64\-linux\-gnu\/libXi\.a'
+Replace '\-lXi' '\/usr\/lib\/x86_64\-linux\-gnu\/libXi\.a \/usr\/lib\/x86_64\-linux\-gnu\/libXext\.a'
Replace '\-lSM' '\/usr\/lib\/x86_64\-linux\-gnu\/libSM\.a'
Replace '\-lICE' '\/usr\/lib\/x86_64\-linux\-gnu\/libICE\.a'
Replace '\-lfontconfig' '\/usr\/lib\/x86_64\-linux\-gnu\/libfontconfig\.a \/usr\/lib\/x86_64\-linux\-gnu\/libexpat\.a'
@@ -30,3 +25,4 @@ Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a'
Replace '\-lswscale' '\/usr\/local\/lib\/libswscale\.a'
Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a'
Replace '\-lva' '\/usr\/local\/lib\/libva\.a'
+Replace '\-lQt5Network' '\/usr\/local\/Qt-5.5.1\/lib\/libQt5Network.a \/usr\/local\/ssl\/lib\/libssl.a \/usr\/local\/ssl\/lib\/libcrypto.a'
diff --git a/Telegram/FixMake32.sh b/Telegram/FixMake32.sh
index 833bca6c6d..24d5e677f9 100755
--- a/Telegram/FixMake32.sh
+++ b/Telegram/FixMake32.sh
@@ -11,12 +11,7 @@ Replace () {
}
Replace '\-llzma' '\/usr\/lib\/i386\-linux\-gnu\/liblzma\.a'
-Replace '\-lz' '\/usr\/lib\/i386\-linux\-gnu\/libz\.a'
-Replace '\-lssl' '\/usr\/lib\/i386\-linux\-gnu\/libssl\.a'
-Replace '\-lcrypto' '\/usr\/lib\/i386\-linux\-gnu\/libcrypto\.a'
-Replace '\-lexif' '\/usr\/lib\/i386\-linux\-gnu\/libexif\.a'
-Replace '\-lgobject\-2\.0' '\/usr\/lib\/i386\-linux\-gnu\/libgobject\-2\.0\.a \/usr\/lib\/i386\-linux\-gnu\/libffi\.a'
-Replace '\-lXi' '\/usr\/lib\/i386\-linux\-gnu\/libXi\.a'
+Replace '\-lXi' '\/usr\/lib\/i386\-linux\-gnu\/libXi\.a \/usr\/lib\/i386\-linux\-gnu\/libXext\.a'
Replace '\-lSM' '\/usr\/lib\/i386\-linux\-gnu\/libSM\.a'
Replace '\-lICE' '\/usr\/lib\/i386\-linux\-gnu\/libICE\.a'
Replace '\-lfontconfig' '\/usr\/lib\/i386\-linux\-gnu\/libfontconfig\.a \/usr\/lib\/i386\-linux\-gnu\/libexpat\.a'
@@ -30,3 +25,4 @@ Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a'
Replace '\-lswscale' '\/usr\/local\/lib\/libswscale\.a'
Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a'
Replace '\-lva' '\/usr\/local\/lib\/libva\.a'
+Replace '\-lQt5Network' '\/usr\/local\/Qt-5.5.1\/lib\/libQt5Network.a \/usr\/local\/ssl\/lib\/libssl.a \/usr\/local\/ssl\/lib\/libcrypto.a'
diff --git a/Telegram/MetaLang.pro b/Telegram/MetaLang.pro
index dbef70ae94..0dfa29a6fd 100644
--- a/Telegram/MetaLang.pro
+++ b/Telegram/MetaLang.pro
@@ -12,7 +12,7 @@ CONFIG(release, debug|release) {
DESTDIR = ./../ReleaseLang
}
-CONFIG += plugin static
+CONFIG += plugin static c++11
macx {
QMAKE_INFO_PLIST = ./SourceFiles/_other/Lang.plist
diff --git a/Telegram/MetaStyle.pro b/Telegram/MetaStyle.pro
index 2ef67ea2e0..a68a443856 100644
--- a/Telegram/MetaStyle.pro
+++ b/Telegram/MetaStyle.pro
@@ -12,7 +12,7 @@ CONFIG(release, debug|release) {
DESTDIR = ./../ReleaseStyle
}
-CONFIG += plugin static
+CONFIG += plugin static c++11
macx {
QMAKE_INFO_PLIST = ./SourceFiles/_other/Style.plist
diff --git a/Telegram/Resources/LangList b/Telegram/Resources/LangList
new file mode 100644
index 0000000000..6581227605
--- /dev/null
+++ b/Telegram/Resources/LangList
@@ -0,0 +1 @@
+de,es,it,ko,nl,pt_BR
diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings
index 7d8eddad43..3e5a90d59b 100644
--- a/Telegram/Resources/lang.strings
+++ b/Telegram/Resources/lang.strings
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
"lng_language_name" = "English";
"lng_switch_to_this" = "Switch to English";
@@ -86,8 +86,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_cancel" = "Cancel";
"lng_continue" = "Continue";
"lng_close" = "Close";
-"lng_connecting" = "Connecting..";
-"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}..";
+"lng_connecting" = "Connecting...";
+"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}...";
"lng_reconnecting_try_now" = "Try now";
"lng_status_service_notifications" = "service notifications";
@@ -108,7 +108,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_status_lastseen_date" = "last seen {date}";
"lng_status_lastseen_date_time" = "last seen {date} at {time}";
"lng_status_online" = "online";
-"lng_status_connecting" = "connecting..";
+"lng_status_connecting" = "connecting...";
"lng_chat_status_unaccessible" = "group is unaccessible";
"lng_chat_status_members" = "{count:no members|# member|# members}";
@@ -123,8 +123,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_server_error" = "Internal server error.";
"lng_flood_error" = "Too many tries. Please try again later.";
"lng_gif_error" = "An error has occured while reading GIF animation :(";
+"lng_edit_error" = "You cannot edit this message";
+"lng_edit_deleted" = "This message was deleted";
+"lng_edit_too_long" = "Your message text is too long";
+"lng_edit_message" = "Edit message";
+"lng_edit_message_text" = "New message text...";
"lng_deleted" = "Unknown";
"lng_deleted_message" = "Deleted message";
+"lng_pinned_message" = "Pinned message";
+"lng_pinned_unpin_sure" = "Would you like to unpin this message?";
+"lng_pinned_pin_sure" = "Would you like to pin this message?";
+"lng_pinned_pin" = "Pin";
+"lng_pinned_unpin" = "Unpin";
+"lng_pinned_notify" = "Notify all members";
"lng_intro" = "Welcome to the official [a href=\"https://telegram.org/\"]Telegram[/a] desktop app.\nIt's [b]fast[/b] and [b]secure[/b].";
"lng_start_msgs" = "START MESSAGING";
@@ -151,7 +162,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_code_telegram" = "Please enter the code you've just\nreceived in your previous [b]Telegram[/b] app.";
"lng_code_no_telegram" = "Send code via SMS";
"lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}";
-"lng_code_calling" = "Requesting a call from Telegram..";
+"lng_code_calling" = "Requesting a call from Telegram...";
"lng_code_called" = "Telegram dialed your number";
"lng_bad_phone" = "Invalid phone number. Please try again.";
@@ -190,7 +201,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_dlg_new_channel_name" = "Channel name";
"lng_no_contacts" = "You have no contacts";
"lng_no_chats" = "Your chats will be here";
-"lng_contacts_loading" = "Loading..";
+"lng_contacts_loading" = "Loading...";
"lng_contacts_not_found" = "No contacts found";
"lng_dlg_search_chat" = "Search in this chat";
"lng_dlg_search_channel" = "Search in this channel";
@@ -199,7 +210,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_settings_save" = "Save";
"lng_settings_upload" = "Set Profile Photo";
"lng_settings_crop_profile" = "Select a square area for your profile photo";
-"lng_settings_uploading_photo" = "Uploading photo..";
+"lng_settings_uploading_photo" = "Uploading photo...";
"lng_username_title" = "Username";
"lng_username_about" = "You can choose a username on Telegram.\nIf you do, other people will be able to find\nyou by this username and contact you\nwithout knowing your phone number.\n\nYou can use a-z, 0-9 and underscores.\nMinimum length is 5 characters.";
@@ -236,9 +247,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_settings_auto_update" = "Update automatically";
"lng_settings_current_version" = "Version {version}";
"lng_settings_check_now" = "Check for updates";
-"lng_settings_update_checking" = "Checking for updates..";
+"lng_settings_update_checking" = "Checking for updates...";
"lng_settings_latest_installed" = "Latest version is installed";
-"lng_settings_downloading" = "Downloading update {ready} / {total} MB..";
+"lng_settings_downloading" = "Downloading update {ready} / {total} MB...";
"lng_settings_update_ready" = "New version is ready";
"lng_settings_update_now" = "Restart Now";
"lng_settings_update_fail" = "Update check failed :(";
@@ -262,6 +273,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_settings_bg_from_gallery" = "Choose from gallery";
"lng_settings_bg_from_file" = "Choose from file";
"lng_settings_bg_tile" = "Tile background";
+"lng_settings_adaptive_wide" = "Adaptive layout for wide screens";
"lng_backgrounds_header" = "Choose your new chat background";
@@ -279,7 +291,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_download_path_failed" = "File download could not be started. It could happen because of a bad download location.\n\nYou can change download path in Settings.";
"lng_download_path_settings" = "Settings";
"lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?";
-"lng_download_path_clearing" = "Clearing..";
+"lng_download_path_clearing" = "Clearing...";
"lng_download_path_cleared" = "Cleared!";
"lng_download_path_clear_failed" = "Clear failed :(";
@@ -288,7 +300,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_settings_images_cached" = "{count:_not_used_|# image|# images}, {size}";
"lng_settings_audios_cached" = "{count:_not_used_|# voice message|# voice messages}, {size}";
"lng_local_storage_clear" = "Clear all";
-"lng_local_storage_clearing" = "Clearing..";
+"lng_local_storage_clearing" = "Clearing...";
"lng_local_storage_cleared" = "Cleared!";
"lng_local_storage_clear_failed" = "Clear failed :(";
@@ -319,7 +331,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_passcode_logout" = "Log out";
"lng_passcode_need_unblock" = "You need to unlock me first.";
-"lng_cloud_password_waiting" = "Confirmation link sent to {email}..";
+"lng_cloud_password_waiting" = "Confirmation link sent to {email}...";
"lng_cloud_password_change" = "Change cloud password";
"lng_cloud_password_create" = "Cloud password";
"lng_cloud_password_remove" = "Remove cloud password";
@@ -346,9 +358,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_cloud_password_is_same" = "Password was not changed";
"lng_connection_type" = "Connection type:";
-"lng_connection_auto_connecting" = "Default (connecting..)";
+"lng_connection_auto_connecting" = "Default (connecting...)";
"lng_connection_auto" = "Default ({transport} used)";
-"lng_connection_proxy_connecting" = "Connecting through proxy..";
+"lng_connection_proxy_connecting" = "Connecting through proxy...";
"lng_connection_proxy" = "{transport} with proxy";
"lng_connection_header" = "Connection type";
"lng_connection_auto_rb" = "Auto (TCP if available or HTTP)";
@@ -384,7 +396,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized.";
"lng_sessions_terminate_all" = "Terminate all other sessions";
-"lng_preview_loading" = "Getting Link Info..";
+"lng_preview_loading" = "Getting Link Info...";
"lng_profile_chat_unaccessible" = "Group is unaccessible";
"lng_topbar_info" = "Info";
@@ -423,10 +435,11 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_profile_add_participant" = "Add Members";
"lng_profile_delete_and_exit" = "Leave";
"lng_profile_kick" = "Remove";
+"lng_profile_admin" = "admin";
"lng_profile_sure_kick" = "Remove {user} from the group?";
"lng_profile_sure_kick_channel" = "Remove {user} from the channel?";
"lng_profile_sure_kick_admin" = "Remove {user} from administrators?";
-"lng_profile_loading" = "Loading..";
+"lng_profile_loading" = "Loading...";
"lng_profile_shared_media" = "Shared media";
"lng_profile_no_media" = "No media in this conversation.";
"lng_profile_photos" = "{count:_not_used_|# photo|# photos} »";
@@ -442,6 +455,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links} »";
"lng_profile_shared_links_header" = "Shared links overview";
"lng_profile_copy_phone" = "Copy phone number";
+"lng_profile_copy_fullname" = "Copy name";
"lng_channel_add_admins" = "New administrator";
"lng_channel_add_members" = "Add members";
@@ -463,13 +477,17 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_create_group_next" = "Next";
"lng_create_group_create" = "Create";
"lng_create_group_title" = "New Group";
-"lng_create_group_about" = "Groups are ideal for smaller communities,\nthey can have up to {count:_not_used|# member|# members}";
+"lng_create_group_about" = "Groups are ideal for limited communities,\nthey can have up to {count:_not_used|# member|# members}";
"lng_create_channel_title" = "New Channel";
"lng_create_channel_about" = "Channels are a tool for broadcasting your messages to unlimited audiences";
"lng_create_public_channel_title" = "Public Channel";
"lng_create_public_channel_about" = "Anyone can find the channel in search and join";
"lng_create_private_channel_title" = "Private Channel";
"lng_create_private_channel_about" = "Only people with a special invite link may join";
+"lng_create_public_group_title" = "Public Group";
+"lng_create_public_group_about" = "Anyone can find the group in search and join, chat history is available to everybody";
+"lng_create_private_group_title" = "Private Group";
+"lng_create_private_group_about" = "People can only join if they were invited or have an invite link";
"lng_create_channel_comments" = "Enable Comments";
"lng_create_channel_comments_about" = "If you enable comments, members will be able to discuss your posts in the channel";
"lng_create_group_skip" = "Skip";
@@ -498,7 +516,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_sure_delete_group" = "Are you sure, you want to delete this group? All members will be removed and all messages will be lost.";
"lng_message_empty" = "Empty Message";
-"lng_media_unsupported" = "Media Unsupported";
+"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the last version in Settings or install it from {link}";
"lng_action_add_user" = "{from} added {user}";
"lng_action_add_users_many" = "{from} added {users}";
@@ -522,6 +540,17 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_action_created_chat" = "{from} created group «{title}»";
"lng_action_created_channel" = "Channel «{title}» created";
"lng_action_group_migrate" = "The group was upgraded to a supergroup";
+"lng_action_pinned_message" = "{from} pinned «{text}»";
+"lng_action_pinned_media" = "{from} pinned {media}";
+"lng_action_pinned_media_photo" = "a photo";
+"lng_action_pinned_media_video" = "a video file";
+"lng_action_pinned_media_audio" = "an audio file";
+"lng_action_pinned_media_voice" = "a voice message";
+"lng_action_pinned_media_file" = "a file";
+"lng_action_pinned_media_gif" = "a GIF animation";
+"lng_action_pinned_media_contact" = "a contact information";
+"lng_action_pinned_media_location" = "a location mark";
+"lng_action_pinned_media_sticker" = "a sticker";
"lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached";
"lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:";
@@ -531,6 +560,16 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_profile_migrate_feature4" = "— Notifications are muted by default";
"lng_profile_migrate_button" = "Upgrade to supergroup";
"lng_profile_migrate_sure" = "Are you sure you want to upgrade this group to supergroup? This action cannot be undone.";
+"lng_profile_convert_button" = "Convert to supergroup";
+"lng_profile_convert_title" = "Convert to supergroup";
+"lng_profile_convert_about" = "In supergroups:";
+"lng_profile_convert_feature1" = "— New members see the full message history";
+"lng_profile_convert_feature2" = "— Messages are deleted for all members";
+"lng_profile_convert_feature3" = "— Members can edit their own messages";
+"lng_profile_convert_feature4" = "— Creator can set a public link for the group";
+"lng_profile_convert_warning" = "{bold_start}Note:{bold_end} This action can not be undone";
+"lng_profile_convert_confirm" = "Convert";
+"lng_profile_add_more_after_upgrade" = "You will be able to add up to {count:_not_used_|# member|# members} after you upgrade your group to a supergroup.";
"lng_channel_comments_count" = "{count:_not_used_|# comment|# comments}";
"lng_channel_hide_comments" = "Hide comments";
@@ -555,9 +594,18 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_channel_public_link_copied" = "Link copied to clipboard.";
-"lng_forwarded_from" = "Forwarded from";
+"lng_forwarded" = "Forwarded from {user}";
+"lng_forwarded_channel" = "Forwarded from {channel}";
+"lng_forwarded_via" = "Forwarded from {user} via {inline_bot}";
+"lng_forwarded_channel_via" = "Forwarded from {channel} via {inline_bot}";
+"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "In reply to";
+"lng_bot_share_location_unavailable" = "Sorry, the location sharing is currently unavailable in Telegram Desktop.";
+"lng_bot_inline_geo_unavailable" = "Sorry, this bot requires location sharing.\nIt is not unavailable in Telegram Desktop.";
+"lng_bot_share_phone" = "Share Phone Number?";
+"lng_bot_share_phone_confirm" = "Share";
+
"lng_attach_failed" = "Failed";
"lng_attach_file" = "File";
"lng_attach_photo" = "Photo";
@@ -600,6 +648,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_saved_gifs" = "Saved GIFs";
"lng_inline_bot_results" = "Results from {inline_bot}";
"lng_inline_bot_no_results" = "No results";
+"lng_inline_bot_via" = "via {inline_bot}";
"lng_box_remove" = "Remove";
@@ -616,16 +665,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_stickers_remove" = "Delete";
"lng_stickers_return" = "Undo";
"lng_stickers_restore" = "Restore";
-"lng_stickers_count" = "{count:Loading..|# sticker|# stickers}";
+"lng_stickers_count" = "{count:Loading...|# sticker|# stickers}";
"lng_in_dlg_photo" = "Photo";
-"lng_in_dlg_video" = "Video";
+"lng_in_dlg_video" = "Video file";
+"lng_in_dlg_audio_file" = "Audio file";
"lng_in_dlg_contact" = "Contact";
-"lng_in_dlg_audio" = "Audio";
+"lng_in_dlg_audio" = "Voice message";
"lng_in_dlg_file" = "File";
"lng_in_dlg_sticker" = "Sticker";
"lng_in_dlg_sticker_emoji" = "{emoji} (sticker)";
+"lng_ban_user" = "Ban User";
+"lng_delete_all_from" = "Delete all from this user";
"lng_report_spam" = "Report Spam";
"lng_report_spam_hide" = "Hide";
"lng_report_spam_thanks" = "Thank you for your report!";
@@ -633,16 +685,23 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_report_spam_sure_group" = "Are you sure you want to report spam in this group?";
"lng_report_spam_sure_channel" = "Are you sure you want to report spam in this channel?";
"lng_report_spam_ok" = "Report";
-"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment. {more_info}";
-"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment. {more_info}";
-"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment. {more_info}";
+"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}";
+"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}";
+"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment.\n{more_info}";
"lng_cant_more_info" = "More info »";
+"lng_cant_invite_banned" = "Sorry, only admin can add this user.";
+"lng_cant_invite_privacy" = "Sorry, you cannot add this user to groups because of the privacy settings.";
+"lng_cant_invite_privacy_channel" = "Sorry, you cannot add this user to channels because of the privacy settings.";
+"lng_cant_do_this" = "Sorry, this action is unavailable.";
"lng_send_button" = "Send";
-"lng_message_ph" = "Write a message..";
-"lng_comment_ph" = "Write a comment..";
-"lng_broadcast_ph" = "Broadcast a message..";
+"lng_message_ph" = "Write a message...";
+"lng_comment_ph" = "Write a comment...";
+"lng_broadcast_ph" = "Broadcast a message...";
+"lng_broadcast_silent_ph" = "Silent broadcast...";
"lng_record_cancel" = "Release outside this field to cancel";
+"lng_will_be_notified" = "Members will be notified when you post";
+"lng_wont_be_notified" = "Members will not be notified when you post";
"lng_empty_history" = "";
"lng_willbe_history" = "Please select a chat to start messaging";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
@@ -667,28 +726,29 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_user_typing" = "{user} is typing";
"lng_users_typing" = "{user} and {second_user} are typing";
"lng_many_typing" = "{count:_not_used_|# is|# are} typing";
-"lng_send_action_record_video" = "recording video";
-"lng_user_action_record_video" = "{user} is recording video";
-"lng_send_action_upload_video" = "sending video";
-"lng_user_action_upload_video" = "{user} is sending video";
-"lng_send_action_record_audio" = "recording audio";
-"lng_user_action_record_audio" = "{user} is recording audio";
-"lng_send_action_upload_audio" = "sending audio";
-"lng_user_action_upload_audio" = "{user} is sending audio";
-"lng_send_action_upload_photo" = "sending photo";
-"lng_user_action_upload_photo" = "{user} is sending photo";
-"lng_send_action_upload_file" = "sending file";
-"lng_user_action_upload_file" = "{user} is sending file";
-"lng_send_action_geo_location" = "picking location";
-"lng_user_action_geo_location" = "{user} is picking location";
-"lng_send_action_choose_contact" = "choosing contact";
-"lng_user_action_choose_contact" = "{user} is choosing contact";
+"lng_send_action_record_video" = "recording a video";
+"lng_user_action_record_video" = "{user} is recording a video";
+"lng_send_action_upload_video" = "sending a video";
+"lng_user_action_upload_video" = "{user} is sending a video";
+"lng_send_action_record_audio" = "recording a voice message";
+"lng_user_action_record_audio" = "{user} is recording a voice message";
+"lng_send_action_upload_audio" = "sending a voice message";
+"lng_user_action_upload_audio" = "{user} is sending a voice message";
+"lng_send_action_upload_photo" = "sending a photo";
+"lng_user_action_upload_photo" = "{user} is sending a photo";
+"lng_send_action_upload_file" = "sending a file";
+"lng_user_action_upload_file" = "{user} is sending a file";
+"lng_send_action_geo_location" = "picking a location";
+"lng_user_action_geo_location" = "{user} is picking a location";
+"lng_send_action_choose_contact" = "choosing a contact";
+"lng_user_action_choose_contact" = "{user} is choosing a contact";
"lng_unread_bar" = "{count:_not_used_|# unread message|# unread messages}";
"lng_maps_point" = "Location";
"lng_save_photo" = "Save image";
-"lng_save_video" = "Save video";
-"lng_save_audio" = "Save audio";
+"lng_save_video" = "Save video file";
+"lng_save_audio_file" = "Save audio file";
+"lng_save_audio" = "Save voice message";
"lng_save_file" = "Save file";
"lng_save_downloaded" = "{ready} / {total} {mb}";
"lng_duration_and_size" = "{duration}, {size}";
@@ -700,16 +760,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_context_view_group" = "View group info";
"lng_context_view_channel" = "View channel info";
-"lng_context_open_link" = "Open Link";
"lng_context_copy_link" = "Copy Link";
-"lng_context_open_email" = "Write to this address";
+"lng_context_copy_post_link" = "Copy Post Link";
"lng_context_copy_email" = "Copy email address";
-"lng_context_open_hashtag" = "Search by hashtag";
"lng_context_copy_hashtag" = "Copy hashtag";
-"lng_context_open_mention" = "Open profile";
"lng_context_copy_mention" = "Copy username";
-"lng_context_open_image" = "Open Image";
-"lng_context_save_image" = "Save Image As..";
+"lng_context_save_image" = "Save Image As...";
"lng_context_forward_image" = "Forward Image";
"lng_context_delete_image" = "Delete Image";
"lng_context_copy_image" = "Copy Image";
@@ -717,14 +773,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_context_cancel_download" = "Cancel Download";
"lng_context_show_in_folder" = "Show in Folder";
"lng_context_show_in_finder" = "Show in Finder";
-"lng_context_open_video" = "Open Video";
-"lng_context_save_video" = "Save Video As..";
-"lng_context_open_audio" = "Open Audio";
-"lng_context_save_audio" = "Save Audio As..";
+"lng_context_save_video" = "Save Video File As...";
+"lng_context_save_audio_file" = "Save Audio File As...";
+"lng_context_save_audio" = "Save Voice Message As...";
"lng_context_pack_info" = "Pack Info";
"lng_context_pack_add" = "Add Stickers";
-"lng_context_open_file" = "Open File";
-"lng_context_save_file" = "Save File As..";
+"lng_context_save_file" = "Save File As...";
"lng_context_forward_file" = "Forward File";
"lng_context_delete_file" = "Delete File";
"lng_context_close_file" = "Close File";
@@ -732,9 +786,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_context_save_gif" = "Save GIF";
"lng_context_to_msg" = "Go To Message";
"lng_context_reply_msg" = "Reply";
+"lng_context_edit_msg" = "Edit";
"lng_context_forward_msg" = "Forward Message";
"lng_context_delete_msg" = "Delete Message";
"lng_context_select_msg" = "Select Message";
+"lng_context_pin_msg" = "Pin Message";
+"lng_context_unpin_msg" = "Unpin Message";
"lng_context_cancel_upload" = "Cancel Upload";
"lng_context_copy_selected" = "Copy Selected Text";
"lng_context_forward_selected" = "Forward Selected";
@@ -748,7 +805,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1.5 GB :(";
"lng_send_folder" = "Could not send «{name}» because it is a directory :(";
-"lng_forward_choose" = "Choose recipient..";
+"lng_forward_choose" = "Choose recipient...";
"lng_forward_cant" = "Sorry, no way to forward here :(";
"lng_forward_confirm" = "Forward to {recipient}?";
"lng_forward_share_contact" = "Share contact to {recipient}?";
@@ -768,6 +825,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_edit_group_title" = "Edit group name";
"lng_edit_contact_title" = "Edit contact name";
"lng_edit_channel_title" = "Edit channel";
+"lng_edit_sign_messages" = "Sign messages";
"lng_edit_group" = "Edit group";
"lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact";
@@ -793,7 +851,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_selected_delete" = "Delete";
"lng_selected_forward" = "Forward";
"lng_selected_count" = "{count:_not_used_|# message|# messages}";
-"lng_selected_cancel_sure_this" = "Do you want to cancel this upload?";
+"lng_selected_cancel_sure_this" = "Cancel uploading?";
+"lng_selected_upload_stop" = "Stop";
"lng_selected_delete_sure_this" = "Do you want to delete this message?";
"lng_selected_delete_sure" = "Do you want to delete {count:_not_used_|# message|# messages}?";
"lng_box_delete" = "Delete";
@@ -809,7 +868,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_search_global_results" = "Global search results";
"lng_media_save_progress" = "{ready} of {total} {mb}";
-"lng_mediaview_save_as" = "Save As..";
+"lng_mediaview_save_as" = "Save As...";
"lng_mediaview_copy" = "Copy";
"lng_mediaview_forward" = "Forward";
"lng_mediaview_delete" = "Delete";
@@ -832,7 +891,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}";
"lng_new_version_minor" = "— Bug fixes and other minor improvements";
-"lng_new_version_text" = "GIF revolution: 10x faster sending and downloading, autoplay, save your favorite GIFs to a dedicated tab on the sticker panel.\n\nMore about GIFs:\n{gifs_link}\n\nInline bots: A new way to add bot content to any chat. Type a bot's username and your query in the text field to get instant results and send them to your chat partner. Try typing “@gif dog” in your next chat. Sample bots: @gif, @wiki, @bing, @vid, @bold.\n\nMore about inline bots:\n{bots_link}\n\nAlso in this release: New cute design for media, automatic download settings for photos, voice messages and GIFs.";
+"lng_new_version_text" = "— Visual improvements";
"lng_menu_insert_unicode" = "Insert Unicode control character";
diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt
index a1f7a785cd..f1502116b2 100644
--- a/Telegram/Resources/style.txt
+++ b/Telegram/Resources/style.txt
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
defaultFontFamily: 'Open Sans';
semibold: 'Open Sans Semibold';
@@ -51,7 +51,10 @@ color7: #2996ad; // sea
color8: #ce671b; // orange
wndMinWidth: 380px;
-wideModeWidth: 640px;
+
+adaptiveNormalWidth: 640px;
+adaptiveWideWidth: 1366px;
+
wndMinHeight: 480px;
wndDefWidth: 800px;
wndDefHeight: 600px;
@@ -64,6 +67,13 @@ layerBg: black;
overBg: #edf2f5;
+labelDefFlat: flatLabel {
+ font: font(fsize);
+ minWidth: 100px;
+ width: 0px;
+ align: align(left);
+}
+
boxBg: white;
boxVerticalMargin: 10px;
boxWidth: 320px;
@@ -72,6 +82,8 @@ boxPadding: margins(26px, 30px, 34px, 8px);
boxMaxListHeight: 600px;
boxFontSize: 14px;
boxTextFont: font(boxFontSize);
+boxLittleSkip: 10px;
+boxMediumSkip: 20px;
boxTitleFg: #444444;
boxTitleFont: font(boxFontSize bold);
@@ -123,6 +135,10 @@ redBoxLinkButton: linkButton(defaultBoxLinkButton) {
overColor: #d15948;
downColor: #db6352;
}
+boxLabel: flatLabel(labelDefFlat) {
+ font: font(boxFontSize);
+ align: align(topleft);
+}
defaultInputArea: InputArea {
textFg: black;
@@ -269,6 +285,21 @@ defaultPopupMenu: PopupMenu {
widthMin: 180px;
widthMax: 300px;
}
+
+defaultTooltip: Tooltip {
+ textBg: #eef2f5;
+ textFg: #5d6c80;
+ textFont: normalFont;
+ textBorder: #c9d1db;
+ textPadding: margins(5px, 2px, 5px, 2px);
+
+ shift: point(-20px, 20px);
+ skip: 10px;
+
+ widthMax: 800px;
+ linesMax: 12;
+}
+
almostTransparent: #ffffff0d;
boxScroll: flatScroll(solidScroll) {
width: 18px;
@@ -593,13 +624,6 @@ scrollCountries: flatScroll(scrollDef) {
lnkText: #0f7dc7;
-labelDefFlat: flatLabel {
- font: font(fsize);
- minWidth: 100px;
- width: 0px;
- align: align(left);
-}
-
introBtnTop: 288px;
introSkip: 45px;
introFinishSkip: 15px;
@@ -937,6 +961,7 @@ dlgActiveUnreadColor: #5b94bf;
dlgActiveUnreadBG: white;
dlgActiveColor: white;
dlgActiveDateColor: #d3e2ee;
+dlgActiveUnreadMutedBG: dlgActiveDateColor;
topBarHeight: 54px;
topBarBG: white;
@@ -950,6 +975,21 @@ topBarBackAlpha: 0.8;
topBarBackImg: sprite(65px, 112px, 9px, 16px);
topBarBackColor: #005faf;
topBarBackFont: font(16px);
+topBarSearch: iconedButton(btnDefIconed) {
+ bgColor: transparent;
+ overBgColor: transparent;
+
+ icon: sprite(84px, 374px, 18px, 18px);
+ iconPos: point(13px, 18px);
+ downIcon: sprite(84px, 374px, 18px, 18px);
+ downIconPos: point(13px, 18px);
+
+ opacity: 0.22;
+ overOpacity: 0.36;
+
+ width: 44px;
+ height: topBarHeight;
+}
topBarMinPadding: 5px;
topBarButton: flatButton(btnDefFlat) {
color: btnYesColor;
@@ -1009,10 +1049,11 @@ msgServiceNameFont: semiboldFont;
msgServicePhotoWidth: 100px;
msgDateFont: font(13px);
msgMinWidth: 190px;
-msgPhotoSize: 30px;
+msgPhotoSize: 33px;
msgPhotoSkip: 40px;
msgPadding: margins(13px, 7px, 13px, 8px);
-msgMargin: margins(13px, 4px, 53px, 4px);
+msgMargin: margins(13px, 10px, 53px, 2px);
+msgMarginTopAttached: 3px;
msgLnkPadding: 2px; // for media open / save links
msgBorder: #f0f0f0;
msgInBg: #fff;
@@ -1044,11 +1085,27 @@ msgInReplyBarColor: #2fa9e2;
msgOutReplyBarSelColor: #4da79f;
msgInReplyBarSelColor: #2fa9e2;
+msgBotKbDuration: 200;
+msgBotKbFont: semiboldFont;
+msgBotKbOverOpacity: 0.1;
+msgBotKbIconPadding: 2px;
+msgBotKbUrlIcon: sprite(188px, 338px, 10px, 10px);
+msgBotKbCallbackIcon: msgBotKbUrlIcon;
+msgBotKbRequestPhoneIcon: msgBotKbUrlIcon;
+msgBotKbRequestLocationIcon: msgBotKbUrlIcon;
+msgBotKbButton: botKeyboardButton {
+ margin: 5px;
+ padding: 10px;
+ height: 36px;
+ textTop: 8px;
+ downTextTop: 9px;
+}
+
msgServiceBg: #89a0b47f;
msgServiceSelectBg: #bbc8d4a2;
msgServiceColor: #FFF;
msgServicePadding: margins(12px, 3px, 12px, 4px);
-msgServiceMargin: margins(10px, 7px, 80px, 7px);
+msgServiceMargin: margins(10px, 10px, 80px, 2px);
msgColor: #000;
msgDateColor: #000;
@@ -1107,7 +1164,7 @@ collapseButton: flatButton(btnDefFlat) {
textTop: 3px;
overTextTop: 3px;
downTextTop: 3px;
- height: 24px;
+ height: 25px;
}
collapseHideDuration: 200;
collapseShowDuration: 200;
@@ -1144,6 +1201,24 @@ outTextStyle: textStyle(defaultTextStyle) {
selectBg: msgOutBgSelected;
selectOverlay: msgSelectOverlay;
}
+inFwdTextStyle: textStyle(defaultTextStyle) {
+ linkFlags: semiboldFont;
+ linkFlagsOver: semiboldFont;
+ linkFg: msgInServiceFg;
+ linkFgDown: msgInServiceFg;
+}
+outFwdTextStyle: textStyle(inFwdTextStyle) {
+ linkFg: msgOutServiceFg;
+ linkFgDown: msgOutServiceFg;
+}
+inFwdTextStyleSelected: textStyle(inFwdTextStyle) {
+ linkFg: msgInServiceFgSelected;
+ linkFgDown: msgInServiceFgSelected;
+}
+outFwdTextStyleSelected: textStyle(inFwdTextStyle) {
+ linkFg: msgOutServiceFgSelected;
+ linkFgDown: msgOutServiceFgSelected;
+}
medviewSaveAsTextStyle: textStyle(defaultTextStyle) {
linkFg: #91d9ff;
linkFgDown: #91d9ff;
@@ -1265,11 +1340,21 @@ msgFileBlue: sprite(60px, 425px, 20px, 20px);
msgFileOverDuration: 200;
msgFileRadialLine: 3px;
-msgFileExtPadding: 8px;
-msgFileExtTop: 30px;
-
msgVideoSize: size(320px, 240px);
+msgWaveformBar: 2px;
+msgWaveformSkip: 1px;
+msgWaveformMin: 2px;
+msgWaveformMax: 20px;
+msgWaveformInActive: #59b6eb;
+msgWaveformInActiveSelected: #51a3d3;
+msgWaveformInInactive: #d4dee6;
+msgWaveformInInactiveSelected: #9cc1e1;
+msgWaveformOutActive: #78c67f;
+msgWaveformOutActiveSelected: #6badad;
+msgWaveformOutInactive: #b3e2b4;
+msgWaveformOutInactiveSelected: #91c3c3;
+
sendPadding: 9px;
btnSend: flatButton(btnDefFlat) {
color: btnYesColor;
@@ -1349,7 +1434,7 @@ broadcastToggle: flatCheckbox {
bgColor: white;
disColor: black;
- width: 36px;
+ width: 34px;
height: 46px;
duration: 200;
bgFunc: transition(easeOutCirc);
@@ -1364,13 +1449,23 @@ broadcastToggle: flatCheckbox {
disImageRect: sprite(18px, 125px, 22px, 21px);
chkDisImageRect: sprite(18px, 125px, 22px, 21px);
- imagePos: point(7px, 12px);
+ imagePos: point(6px, 12px);
+}
+silentToggle: flatCheckbox(broadcastToggle) {
+ width: 33px;
+
+ imageRect: sprite(354px, 242px, 21px, 21px);
+ chkImageRect: sprite(354px, 221px, 21px, 21px);
+ overImageRect: sprite(375px, 242px, 21px, 21px);
+ chkOverImageRect: sprite(375px, 221px, 21px, 21px);
+ disImageRect: sprite(354px, 242px, 21px, 21px);
+ chkDisImageRect: sprite(354px, 221px, 21px, 21px);
}
btnRecordAudio: sprite(379px, 390px, 16px, 24px);
btnRecordAudioActive: sprite(379px, 366px, 16px, 24px);
recordSignalColor: #f17077;
recordSignalMin: 5px;
-recordSignalMax: 10px;
+recordSignalMax: 12px;
recordCancel: #aaa;
recordCancelActive: #ec6466;
recordFont: font(13px);
@@ -1383,6 +1478,7 @@ replyTop: 8px;
replyBottom: 6px;
replyIconPos: point(13px, 13px);
replyIcon: sprite(343px, 197px, 24px, 24px);
+editIcon: sprite(371px, 286px, 24px, 24px);
replyCancel: iconedButton(btnDefIconed) {
icon: sprite(165px, 24px, 14px, 14px);
iconPos: point(17px, 17px);
@@ -1447,6 +1543,7 @@ reportSpamBg: #fffffff0;
newMsgSound: ':/gui/art/newmsg.wav';
unreadBarHeight: 32px;
+unreadBarMargin: 8px;
unreadBarFont: semiboldFont;
unreadBarBG: #fcfbfa;
unreadBarBorder: shadowColor;
@@ -2017,17 +2114,17 @@ verifiedCheckInv: sprite(299px, 221px, 14px, 14px);
verifiedCheckPos: point(4px, 2px);
botKbDuration: 200;
-botKbBg: #f7f7f7;
-botKbOverBg: #e8ecef;
-botKbDownBg: #dfe3e6;
-botKbColor: #8a8a8f;
-botKbFont: font(16px);
+botKbBg: #edf1f5;
+botKbOverBg: #d8e2ec;
+botKbDownBg: #d8e2ec;
+botKbColor: #4b565f;
+botKbFont: font(15px semibold);
botKbButton: botKeyboardButton {
margin: 10px;
padding: 10px;
- height: 36px;
- textTop: 8px;
- downTextTop: 9px;
+ height: 38px;
+ textTop: 9px;
+ downTextTop: 10px;
}
botKbTinyButton: botKeyboardButton {
margin: 4px;
@@ -2045,6 +2142,7 @@ minPhotoSize: 100px;
maxMediaSize: 420px;
maxStickerSize: 256px;
maxGifSize: 320px;
+maxSignatureSize: 144px;
mvBgColor: #222;
mvBgOpacity: 0.92;
@@ -2180,6 +2278,9 @@ overviewFileStatusTop: 27px;
overviewFileDateTop: 49px;
overviewFileChecked: #2fa9e2;
overviewFileCheck: #00000066;
+overviewFileExtPadding: 5px;
+overviewFileExtTop: 24px;
+overviewFileExtFont: font(18px semibold);
// Mac specific
@@ -2376,7 +2477,7 @@ linksDateMargin: margins(0px, 15px, 0px, 2px);
linksPhotoCheck: sprite(184px, 196px, 16px, 16px);
linksPhotoChecked: sprite(168px, 196px, 16px, 16px);
-inlineResultsLeft: 15px;
+inlineResultsLeft: 11px;
inlineResultsSkip: 3px;
inlineMediaHeight: 96px;
inlineThumbSize: 64px;
@@ -2385,5 +2486,12 @@ inlineDescriptionFg: #8a8a8a;
inlineRowMargin: 6px;
inlineRowBorder: linksBorder;
inlineRowBorderFg: linksBorderFg;
+inlineRowFileNameTop: 2px;
+inlineRowFileDescriptionTop: 23px;
inlineResultsMinWidth: 64px;
inlineDurationMargin: 3px;
+
+editTextArea: InputArea(defaultInputArea) {
+ textMargins: margins(1px, 6px, 1px, 4px);
+ heightMax: 256px;
+}
diff --git a/Telegram/Resources/style_classes.txt b/Telegram/Resources/style_classes.txt
index 611b367a41..229b3398bb 100644
--- a/Telegram/Resources/style_classes.txt
+++ b/Telegram/Resources/style_classes.txt
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
textStyle {
linkFlags: font;
@@ -272,6 +272,20 @@ PopupMenu {
widthMax: number;
}
+Tooltip {
+ textBg: color;
+ textFg: color;
+ textFont: font;
+ textBorder: color;
+ textPadding: margins;
+
+ shift: point;
+ skip: number;
+
+ widthMax: number;
+ linesMax: number;
+}
+
botKeyboardButton {
margin: number;
padding: number;
diff --git a/Telegram/SourceFiles/_other/genemoji.cpp b/Telegram/SourceFiles/_other/genemoji.cpp
index 5e6fce4bf5..22e3fd1bd3 100644
--- a/Telegram/SourceFiles/_other/genemoji.cpp
+++ b/Telegram/SourceFiles/_other/genemoji.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "genemoji.h"
@@ -1942,7 +1942,7 @@ In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
*/\n";
tcpp << "#include \"stdafx.h\"\n#include \"gui/emoji_config.h\"\n\n";
diff --git a/Telegram/SourceFiles/_other/genemoji.h b/Telegram/SourceFiles/_other/genemoji.h
index 61c0e3e767..ecf35ef48e 100644
--- a/Telegram/SourceFiles/_other/genemoji.h
+++ b/Telegram/SourceFiles/_other/genemoji.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include
#include
diff --git a/Telegram/SourceFiles/_other/genlang.cpp b/Telegram/SourceFiles/_other/genlang.cpp
index 60e77ec79d..614f81de5b 100644
--- a/Telegram/SourceFiles/_other/genlang.cpp
+++ b/Telegram/SourceFiles/_other/genlang.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "genlang.h"
@@ -189,7 +189,7 @@ void readKeyValue(const char *&from, const char *end) {
if (*from == ':') {
start = ++from;
-
+
QVector &counted(keysCounted[varName][tagName]);
QByteArray subvarValue;
bool foundtag = false;
@@ -391,7 +391,7 @@ bool genLang(const QString &lang_in, const QString &lang_out) {
th.setCodec("ISO 8859-1");
th << "\
/*\n\
-Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\
+Created from \'/Resources/lang.strings\' by \'/MetaLang\' project\n\
\n\
WARNING! All changes made in this file will be lost!\n\
\n\
@@ -412,7 +412,7 @@ In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
*/\n";
th << "#pragma once\n\n";
@@ -475,7 +475,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
tcpp << "\
/*\n\
-Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\
+Created from \'/Resources/lang.strings\' by \'/MetaLang\' project\n\
\n\
WARNING! All changes made in this file will be lost!\n\
\n\
@@ -496,7 +496,7 @@ In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
*/\n";
tcpp << "#include \"stdafx.h\"\n#include \"lang.h\"\n\n";
tcpp << "namespace {\n";
@@ -606,13 +606,22 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
++depth;
current += ich;
- if (tag == current) {
+ bool exact = (tag == current);
+ if (exact) {
tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " == e) {\n";
tcpp << tab.repeated(depth + 1) << "\treturn lt_" << tag << ";\n";
tcpp << tab.repeated(depth + 1) << "}\n";
}
- tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
+ QByteArray nexttag = j.key();
+ if (exact && depth > 0 && nexttag.mid(0, depth) != current) {
+ current.chop(1);
+ --depth;
+ tcpp << tab.repeated(depth + 1) << "break;\n";
+ break;
+ } else {
+ tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
+ }
} while (true);
++j;
}
@@ -637,7 +646,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
tcpp << "\tswitch (*(ch + " << depth << ")) {\n";
for (LangKeys::const_iterator i = keys.cbegin(), j = i + 1, e = keys.cend(); i != e; ++i) {
QByteArray key = i.key();
- while (key.mid(0, depth) != current) {
+ while (depth > 0 && key.mid(0, depth) != current) {
tcpp << tab.repeated(depth - 3) << "}\n";
current.chop(1);
--depth;
@@ -645,7 +654,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
}
do {
if (key == current) break;
-
+
char ich = i.key().at(current.size());
tcpp << tab.repeated(current.size() - 3) << "case '" << ich << "':\n";
if (j == e || ich != ((j.key().size() > depth) ? j.key().at(depth) : 0)) {
@@ -661,13 +670,22 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
++depth;
current += ich;
- if (key == current) {
+ bool exact = (key == current);
+ if (exact) {
tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " == e) {\n";
tcpp << tab.repeated(depth - 3) << "\treturn " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n";
tcpp << tab.repeated(depth - 3) << "}\n";
}
- tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
+ QByteArray nextkey = j.key();
+ if (exact && depth > 0 && nextkey.mid(0, depth) != current) {
+ current.chop(1);
+ --depth;
+ tcpp << tab.repeated(depth - 3) << "break;\n";
+ break;
+ } else {
+ tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
+ }
} while (true);
++j;
}
@@ -707,16 +725,25 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
tcpp << "\tif (index >= lngtags_max_counted_values) return lngkeys_cnt;\n\n";
if (!tags.isEmpty()) {
tcpp << "\tswitch (key) {\n";
- for (int i = 0, l = keysOrder.size(); i < l; ++i) {
- QVector &tagsList(keysTags[keysOrder[i]]);
+ for (auto key : keysOrder) {
+ QVector &tagsList(keysTags[key]);
if (tagsList.isEmpty()) continue;
- QMap > &countedTags(keysCounted[keysOrder[i]]);
- tcpp << "\tcase " << keysOrder[i] << "__tagged: {\n";
+ QMap > &countedTags(keysCounted[key]);
+ bool hasCounted = false;
+ for (auto tag : tagsList) {
+ if (!countedTags[tag].isEmpty()) {
+ hasCounted = true;
+ break;
+ }
+ }
+ if (!hasCounted) continue;
+
+ tcpp << "\tcase " << key << "__tagged: {\n";
tcpp << "\t\tswitch (tag) {\n";
- for (int j = 0, s = tagsList.size(); j < s; ++j) {
- if (!countedTags[tagsList[j]].isEmpty()) {
- tcpp << "\t\tcase lt_" << tagsList[j] << ": return LangKey(" << keysOrder[i] << "__" << tagsList[j] << "0 + index);\n";
+ for (auto tag : tagsList) {
+ if (!countedTags[tag].isEmpty()) {
+ tcpp << "\t\tcase lt_" << tag << ": return LangKey(" << key << "__" << tag << "0 + index);\n";
}
}
tcpp << "\t\t}\n";
@@ -724,7 +751,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
}
tcpp << "\t}\n\n";
}
- tcpp << "\treturn lngkeys_cnt;";
+ tcpp << "\treturn lngkeys_cnt;\n";
tcpp << "}\n\n";
tcpp << "bool LangLoader::feedKeyValue(LangKey key, const QString &value) {\n";
diff --git a/Telegram/SourceFiles/_other/genlang.h b/Telegram/SourceFiles/_other/genlang.h
index dadeb8c09b..b88f6d0306 100644
--- a/Telegram/SourceFiles/_other/genlang.h
+++ b/Telegram/SourceFiles/_other/genlang.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include
#include
diff --git a/Telegram/SourceFiles/_other/genstyles.cpp b/Telegram/SourceFiles/_other/genstyles.cpp
index 709117d522..55fe18a87b 100644
--- a/Telegram/SourceFiles/_other/genstyles.cpp
+++ b/Telegram/SourceFiles/_other/genstyles.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "genstyles.h"
@@ -381,9 +381,9 @@ In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
*/\n";
- tout << "#pragma once\n\n#include \"style.h\"\n\nnamespace style {\n";
+ tout << "#pragma once\n\n#include \"gui/style.h\"\n\nnamespace style {\n";
for (int i = 0, l = byIndex.size(); i < l; ++i) {
ClassData &cls(byIndex[i]);
classes.insert(cls.name, cls);
@@ -1542,9 +1542,9 @@ In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
*/\n";
- tout << "#pragma once\n\n#include \"style.h\"\n\nnamespace st {\n";
+ tout << "#pragma once\n\n#include \"gui/style.h\"\n\nnamespace st {\n";
tcpp << "\
/*\n\
Created from \'/Resources/style.txt\' by \'/MetaStyle\' project\n\
@@ -1568,7 +1568,7 @@ In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
*/\n";
tcpp << "#include \"stdafx.h\"\n#include \"style_auto.h\"\n\nnamespace {\n";
for (int i = 0, l = scalars.size(); i < l; ++i) {
@@ -1945,7 +1945,7 @@ In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
*/\n";
tnum << "#include \"stdafx.h\"\n#include \"numbers.h\"\n\n";
tnum << "QVector phoneNumberParse(const QString &number) {\n";
diff --git a/Telegram/SourceFiles/_other/genstyles.h b/Telegram/SourceFiles/_other/genstyles.h
index 40a6d6ce67..05fa8c333e 100644
--- a/Telegram/SourceFiles/_other/genstyles.h
+++ b/Telegram/SourceFiles/_other/genstyles.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include
#include
diff --git a/Telegram/SourceFiles/_other/memain.cpp b/Telegram/SourceFiles/_other/memain.cpp
index 6daade312c..aaf5b63415 100644
--- a/Telegram/SourceFiles/_other/memain.cpp
+++ b/Telegram/SourceFiles/_other/memain.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "memain.h"
diff --git a/Telegram/SourceFiles/_other/memain.h b/Telegram/SourceFiles/_other/memain.h
index c2faceb850..c3293a73c8 100644
--- a/Telegram/SourceFiles/_other/memain.h
+++ b/Telegram/SourceFiles/_other/memain.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include
diff --git a/Telegram/SourceFiles/_other/mlmain.cpp b/Telegram/SourceFiles/_other/mlmain.cpp
index 490ce50efe..42c01b0d28 100644
--- a/Telegram/SourceFiles/_other/mlmain.cpp
+++ b/Telegram/SourceFiles/_other/mlmain.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "mlmain.h"
diff --git a/Telegram/SourceFiles/_other/mlmain.h b/Telegram/SourceFiles/_other/mlmain.h
index a909bb5025..6516c86b25 100644
--- a/Telegram/SourceFiles/_other/mlmain.h
+++ b/Telegram/SourceFiles/_other/mlmain.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include
diff --git a/Telegram/SourceFiles/_other/msmain.cpp b/Telegram/SourceFiles/_other/msmain.cpp
index cd31683884..ac85f52e41 100644
--- a/Telegram/SourceFiles/_other/msmain.cpp
+++ b/Telegram/SourceFiles/_other/msmain.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "msmain.h"
#include
diff --git a/Telegram/SourceFiles/_other/msmain.h b/Telegram/SourceFiles/_other/msmain.h
index f0e13771a2..4db6725527 100644
--- a/Telegram/SourceFiles/_other/msmain.h
+++ b/Telegram/SourceFiles/_other/msmain.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include
diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp
index 7cf2da9552..e2a6f128af 100644
--- a/Telegram/SourceFiles/_other/packer.cpp
+++ b/Telegram/SourceFiles/_other/packer.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "packer.h"
diff --git a/Telegram/SourceFiles/_other/packer.h b/Telegram/SourceFiles/_other/packer.h
index b82ccd71c2..ccdff11cf8 100644
--- a/Telegram/SourceFiles/_other/packer.h
+++ b/Telegram/SourceFiles/_other/packer.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
diff --git a/Telegram/SourceFiles/_other/updater.cpp b/Telegram/SourceFiles/_other/updater.cpp
index 103803a882..f70e65ca87 100644
--- a/Telegram/SourceFiles/_other/updater.cpp
+++ b/Telegram/SourceFiles/_other/updater.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "updater.h"
@@ -255,7 +255,7 @@ bool update() {
} else {
break;
}
- } while (copyTries < 30);
+ } while (copyTries < 100);
if (!copyResult) {
writeLog(L"Error: failed to copy, asking to retry..");
WCHAR errMsg[2048];
@@ -328,14 +328,11 @@ void updateRegistry() {
}
}
-#include
-
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) {
openLog();
-#ifdef _NEED_WIN_GENERATE_DUMP
_oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter);
-#endif
+// CAPIHook apiHook("kernel32.dll", "SetUnhandledExceptionFilter", (PROC)RedirectedSetUnhandledExceptionFilter);
writeLog(L"Updaters started..");
@@ -459,13 +456,12 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
ShellExecute(0, 0, (updateTo + L"Telegram.exe").c_str(), (L"-noupdate" + targs).c_str(), 0, SW_SHOWNORMAL);
}
- writeLog(L"Executed Telegram.exe, closing log and quiting..");
+ writeLog(L"Executed Telegram.exe, closing log and quitting..");
closeLog();
return 0;
}
-#ifdef _NEED_WIN_GENERATE_DUMP
static const WCHAR *_programName = L"Telegram Desktop"; // folder in APPDATA, if current path is unavailable for writing
static const WCHAR *_exeName = L"Updater.exe";
@@ -486,13 +482,18 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) {
static const int maxFileLen = MAX_PATH * 10;
WCHAR szPath[maxFileLen];
- wsprintf(szPath, L"%stdumps\\", path);
-
+ wsprintf(szPath, L"%stdata\\", path);
if (!CreateDirectory(szPath, NULL)) {
if (GetLastError() != ERROR_ALREADY_EXISTS) {
return 0;
}
}
+ wsprintf(szPath, L"%sdumps\\", path);
+ if (!CreateDirectory(szPath, NULL)) {
+ if (GetLastError() != ERROR_ALREADY_EXISTS) {
+ return 0;
+ }
+ }
WCHAR szFileName[maxFileLen];
WCHAR szExeName[maxFileLen];
@@ -507,10 +508,10 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) {
GetLocalTime(&stLocalTime);
- wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
- szPath, szExeName, updaterVersionStr,
- stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
- stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
+ wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
+ szPath, szExeName, updaterVersionStr,
+ stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
+ stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
GetCurrentProcessId(), GetCurrentThreadId());
return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
}
@@ -546,7 +547,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
hDumpFile = _generateDumpFileAtPath(wstrPath);
}
}
-
+
if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) {
return;
}
@@ -564,4 +565,10 @@ LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) {
return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH;
}
-#endif
+// see http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
+LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
+ // When the CRT calls SetUnhandledExceptionFilter with NULL parameter
+ // our handler will not get removed.
+ _oldWndExceptionFilter = lpTopLevelExceptionFilter;
+ return 0;
+}
diff --git a/Telegram/SourceFiles/_other/updater.h b/Telegram/SourceFiles/_other/updater.h
index 6afd341a33..8b2709c272 100644
--- a/Telegram/SourceFiles/_other/updater.h
+++ b/Telegram/SourceFiles/_other/updater.h
@@ -16,13 +16,19 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include
#include
+
+#pragma warning(push)
+#pragma warning(disable:4091)
#include
+#include
+#pragma warning(pop)
+
#include
#include
@@ -32,12 +38,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
using std::deque;
using std::wstring;
-#define _NEED_WIN_GENERATE_DUMP
-
-#ifdef _NEED_WIN_GENERATE_DUMP
extern LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter;
LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers);
-#endif _NEED_WIN_GENERATE_DUMP
+LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
static int updaterVersion = 1000;
static const WCHAR *updaterVersionStr = L"0.1.0";
diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp
index 35d67072c9..7ae72c3709 100644
--- a/Telegram/SourceFiles/_other/updater_linux.cpp
+++ b/Telegram/SourceFiles/_other/updater_linux.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include
#include
@@ -324,7 +324,7 @@ bool update() {
int main(int argc, char *argv[]) {
bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false;
- char *key = 0;
+ char *key = 0, *crashreport = 0;
for (int i = 1; i < argc; ++i) {
if (equal(argv[i], "-noupdate")) {
needupdate = false;
@@ -342,7 +342,9 @@ int main(int argc, char *argv[]) {
key = argv[i];
} else if (equal(argv[i], "-workpath") && ++i < argc) {
workDir = argv[i];
- }
+ } else if (equal(argv[i], "-crashreport") && ++i < argc) {
+ crashreport = argv[i];
+ }
}
openLog();
@@ -408,24 +410,27 @@ int main(int argc, char *argv[]) {
char *args[MaxArgsCount] = {0}, p_noupdate[] = "-noupdate", p_autostart[] = "-autostart", p_debug[] = "-debug", p_tosettings[] = "-tosettings", p_key[] = "-key", p_startintray[] = "-startintray", p_testmode[] = "-testmode";
int argIndex = 0;
args[argIndex++] = path;
- args[argIndex++] = p_noupdate;
- if (autostart) args[argIndex++] = p_autostart;
- if (debug) args[argIndex++] = p_debug;
- if (startintray) args[argIndex++] = p_startintray;
- if (testmode) args[argIndex++] = p_testmode;
- if (tosettings) args[argIndex++] = p_tosettings;
- if (key) {
- args[argIndex++] = p_key;
- args[argIndex++] = key;
- }
-
+ if (crashreport) {
+ args[argIndex++] = crashreport;
+ } else {
+ args[argIndex++] = p_noupdate;
+ if (autostart) args[argIndex++] = p_autostart;
+ if (debug) args[argIndex++] = p_debug;
+ if (startintray) args[argIndex++] = p_startintray;
+ if (testmode) args[argIndex++] = p_testmode;
+ if (tosettings) args[argIndex++] = p_tosettings;
+ if (key) {
+ args[argIndex++] = p_key;
+ args[argIndex++] = key;
+ }
+ }
pid_t pid = fork();
switch (pid) {
case -1: writeLog("fork() failed!"); return 1;
case 0: execv(path, args); return 1;
}
- writeLog("Executed Telegram, closing log and quiting..");
+ writeLog("Executed Telegram, closing log and quitting..");
closeLog();
return 0;
diff --git a/Telegram/SourceFiles/_other/updater_osx.m b/Telegram/SourceFiles/_other/updater_osx.m
index 4c3e7c4fbb..ef13dfc065 100644
--- a/Telegram/SourceFiles/_other/updater_osx.m
+++ b/Telegram/SourceFiles/_other/updater_osx.m
@@ -16,13 +16,14 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#import
NSString *appName = @"Telegram.app";
NSString *appDir = nil;
NSString *workDir = nil;
+NSString *crashReportArg = nil;
#ifdef _DEBUG
BOOL _debug = YES;
@@ -101,6 +102,10 @@ int main(int argc, const char * argv[]) {
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue];
}
+ } else if ([@"-crashreport" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
+ if (++i < argc) {
+ crashReportArg = [NSString stringWithUTF8String:argv[i]];
+ }
} else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
update = NO;
} else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
@@ -214,15 +219,17 @@ int main(int argc, const char * argv[]) {
}
NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""];
- NSMutableArray *args = [[NSMutableArray alloc] initWithObjects:@"-noupdate", nil];
- if (toSettings) [args addObject:@"-tosettings"];
- if (_debug) [args addObject:@"-debug"];
- if (startInTray) [args addObject:@"-startintray"];
- if (testMode) [args addObject:@"-testmode"];
- if (autoStart) [args addObject:@"-autostart"];
- if (key) {
- [args addObject:@"-key"];
- [args addObject:key];
+ NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: crashReportArg ? crashReportArg : @"-noupdate", nil];
+ if (!crashReportArg) {
+ if (toSettings) [args addObject:@"-tosettings"];
+ if (_debug) [args addObject:@"-debug"];
+ if (startInTray) [args addObject:@"-startintray"];
+ if (testMode) [args addObject:@"-testmode"];
+ if (autoStart) [args addObject:@"-autostart"];
+ if (key) {
+ [args addObject:@"-key"];
+ [args addObject:key];
+ }
}
writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
NSError *error = nil;
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 46b95ce1c3..af7408859d 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
-#include "style.h"
+#include "gui/style.h"
#include "lang.h"
#include "application.h"
@@ -29,89 +29,65 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
#include "localstorage.h"
-ApiWrap::ApiWrap(QObject *parent) : QObject(parent) {
+ApiWrap::ApiWrap(QObject *parent) : QObject(parent)
+, _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) {
App::initBackground();
- connect(&_replyToTimer, SIGNAL(timeout()), this, SLOT(resolveReplyTo()));
connect(&_webPagesTimer, SIGNAL(timeout()), this, SLOT(resolveWebPages()));
}
void ApiWrap::init() {
}
-void ApiWrap::itemRemoved(HistoryItem *item) {
- if (HistoryReply *reply = item->toHistoryReply()) {
- ChannelData *channel = reply->history()->peer->asChannel();
- ReplyToRequests *requests(replyToRequests(channel, true));
- if (requests) {
- ReplyToRequests::iterator i = requests->find(reply->replyToId());
- if (i != requests->cend()) {
- for (QList::iterator j = i->replies.begin(); j != i->replies.end();) {
- if ((*j) == reply) {
- j = i->replies.erase(j);
- } else {
- ++j;
- }
- }
- if (i->replies.isEmpty()) {
- requests->erase(i);
- }
- }
- if (channel && requests->isEmpty()) {
- _channelReplyToRequests.remove(channel);
- }
- }
- }
+void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback *callback) {
+ MessageDataRequest::CallbackPtr pcallback(callback);
+ MessageDataRequest &req(channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
+ req.callbacks.append(pcallback);
+ if (!req.req) _messageDataResolveDelayed->call();
}
-void ApiWrap::requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id) {
- ReplyToRequest &req(channel ? _channelReplyToRequests[channel][id] : _replyToRequests[id]);
- req.replies.append(reply);
- if (!req.req) _replyToTimer.start(1);
-}
-
-ApiWrap::MessageIds ApiWrap::collectMessageIds(const ReplyToRequests &requests) {
+ApiWrap::MessageIds ApiWrap::collectMessageIds(const MessageDataRequests &requests) {
MessageIds result;
result.reserve(requests.size());
- for (ReplyToRequests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) {
+ for (MessageDataRequests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) {
if (i.value().req > 0) continue;
result.push_back(MTP_int(i.key()));
}
return result;
}
-ApiWrap::ReplyToRequests *ApiWrap::replyToRequests(ChannelData *channel, bool onlyExisting) {
+ApiWrap::MessageDataRequests *ApiWrap::messageDataRequests(ChannelData *channel, bool onlyExisting) {
if (channel) {
- ChannelReplyToRequests::iterator i = _channelReplyToRequests.find(channel);
- if (i == _channelReplyToRequests.cend()) {
+ ChannelMessageDataRequests::iterator i = _channelMessageDataRequests.find(channel);
+ if (i == _channelMessageDataRequests.cend()) {
if (onlyExisting) return 0;
- i = _channelReplyToRequests.insert(channel, ReplyToRequests());
+ i = _channelMessageDataRequests.insert(channel, MessageDataRequests());
}
return &i.value();
}
- return &_replyToRequests;
+ return &_messageDataRequests;
}
-void ApiWrap::resolveReplyTo() {
- if (_replyToRequests.isEmpty() && _channelReplyToRequests.isEmpty()) return;
+void ApiWrap::resolveMessageDatas() {
+ if (_messageDataRequests.isEmpty() && _channelMessageDataRequests.isEmpty()) return;
- MessageIds ids = collectMessageIds(_replyToRequests);
+ MessageIds ids = collectMessageIds(_messageDataRequests);
if (!ids.isEmpty()) {
- mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector(ids)), rpcDone(&ApiWrap::gotReplyTo, (ChannelData*)0), RPCFailHandlerPtr(), 0, 5);
- for (ReplyToRequests::iterator i = _replyToRequests.begin(); i != _replyToRequests.cend(); ++i) {
+ mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector(ids)), rpcDone(&ApiWrap::gotMessageDatas, (ChannelData*)nullptr), RPCFailHandlerPtr(), 0, 5);
+ for (MessageDataRequests::iterator i = _messageDataRequests.begin(); i != _messageDataRequests.cend(); ++i) {
if (i.value().req > 0) continue;
i.value().req = req;
}
}
- for (ChannelReplyToRequests::iterator j = _channelReplyToRequests.begin(); j != _channelReplyToRequests.cend();) {
+ for (ChannelMessageDataRequests::iterator j = _channelMessageDataRequests.begin(); j != _channelMessageDataRequests.cend();) {
if (j->isEmpty()) {
- j = _channelReplyToRequests.erase(j);
+ j = _channelMessageDataRequests.erase(j);
continue;
}
MessageIds ids = collectMessageIds(j.value());
if (!ids.isEmpty()) {
- mtpRequestId req = MTP::send(MTPchannels_GetMessages(j.key()->inputChannel, MTP_vector(ids)), rpcDone(&ApiWrap::gotReplyTo, j.key()), RPCFailHandlerPtr(), 0, 5);
- for (ReplyToRequests::iterator i = j->begin(); i != j->cend(); ++i) {
+ mtpRequestId req = MTP::send(MTPchannels_GetMessages(j.key()->inputChannel, MTP_vector(ids)), rpcDone(&ApiWrap::gotMessageDatas, j.key()), RPCFailHandlerPtr(), 0, 5);
+ for (MessageDataRequests::iterator i = j->begin(); i != j->cend(); ++i) {
if (i.value().req > 0) continue;
i.value().req = req;
}
@@ -120,7 +96,7 @@ void ApiWrap::resolveReplyTo() {
}
}
-void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) {
+void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) {
switch (msgs.type()) {
case mtpc_messages_messages: {
const MTPDmessages_messages &d(msgs.c_messages_messages());
@@ -141,10 +117,10 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs,
if (channel) {
channel->ptsReceived(d.vpts.v);
} else {
- LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotReplyTo)"));
+ LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)"));
}
if (d.has_collapsed()) { // should not be returned
- LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (ApiWrap::gotReplyTo)"));
+ LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (ApiWrap::gotDependencyItem)"));
}
App::feedUsers(d.vusers);
@@ -152,16 +128,12 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs,
App::feedMsgs(d.vmessages, NewMessageExisting);
} break;
}
- ReplyToRequests *requests(replyToRequests(channel, true));
+ MessageDataRequests *requests(messageDataRequests(channel, true));
if (requests) {
- for (ReplyToRequests::iterator i = requests->begin(); i != requests->cend();) {
+ for (MessageDataRequests::iterator i = requests->begin(); i != requests->cend();) {
if (i.value().req == req) {
- for (QList::const_iterator j = i.value().replies.cbegin(), e = i.value().replies.cend(); j != e; ++j) {
- if (*j) {
- (*j)->updateReplyTo(true);
- } else {
- App::main()->updateReplyTo();
- }
+ for (MessageDataRequest::Callbacks::const_iterator j = i.value().callbacks.cbegin(), e = i.value().callbacks.cend(); j != e; ++j) {
+ (*j)->call(channel, i.key());
}
i = requests->erase(i);
} else {
@@ -169,7 +141,7 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs,
}
}
if (channel && requests->isEmpty()) {
- _channelReplyToRequests.remove(channel);
+ _channelMessageDataRequests.remove(channel);
}
}
}
@@ -258,7 +230,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
}
if (f.has_migrated_from_chat_id()) {
if (!channel->mgInfo) {
- channel->flags |= MTPDchannel::flag_megagroup;
+ channel->flags |= MTPDchannel::Flag::f_megagroup;
channel->flagsUpdated();
}
ChatData *cfrom = App::chat(peerFromChat(f.vmigrated_from_chat_id));
@@ -273,7 +245,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
if (!h->isEmpty()) {
h->clear(true);
}
- if (!hto->dialogs.isEmpty() && !h->dialogs.isEmpty()) {
+ if (hto->inChatList() && h->inChatList()) {
App::removeDialog(h);
}
}
@@ -317,6 +289,13 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
h->asChannelHistory()->unreadCountAll = f.vunread_count.v;
}
}
+ if (channel->isMegagroup()) {
+ if (f.has_pinned_msg_id()) {
+ channel->mgInfo->pinnedMsgId = f.vpinned_msg_id.v;
+ } else {
+ channel->mgInfo->pinnedMsgId = 0;
+ }
+ }
channel->fullUpdated();
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings);
@@ -344,12 +323,21 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) {
const MTPDuserFull &d(result.c_userFull());
App::feedUsers(MTP_vector(1, d.vuser), false);
- App::feedPhoto(d.vprofile_photo);
+ if (d.has_profile_photo()) {
+ App::feedPhoto(d.vprofile_photo);
+ }
App::feedUserLink(MTP_int(peerToUser(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link, false);
- App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings);
+ if (App::main()) {
+ App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings);
+ }
- peer->asUser()->setBotInfo(d.vbot_info);
- peer->asUser()->blocked = mtpIsTrue(d.vblocked) ? UserIsBlocked : UserIsNotBlocked;
+ if (d.has_bot_info()) {
+ peer->asUser()->setBotInfo(d.vbot_info);
+ } else {
+ peer->asUser()->setBotInfoVersion(-1);
+ }
+ peer->asUser()->blocked = d.is_blocked() ? UserIsBlocked : UserIsNotBlocked;
+ peer->asUser()->about = d.has_about() ? qs(d.vabout) : QString();
if (req) {
QMap::iterator i = _fullPeerRequests.find(peer);
@@ -419,18 +407,18 @@ void ApiWrap::requestLastParticipants(ChannelData *peer, bool fromStart) {
return;
}
}
- mtpRequestId req = MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsRecent(), MTP_int(fromStart ? 0 : peer->mgInfo->lastParticipants.size()), MTP_int(cMaxGroupCount())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer));
+ mtpRequestId req = MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsRecent(), MTP_int(fromStart ? 0 : peer->mgInfo->lastParticipants.size()), MTP_int(Global::ChatSizeMax())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer));
_participantsRequests.insert(peer, fromStart ? req : -req);
}
void ApiWrap::requestBots(ChannelData *peer) {
if (!peer || !peer->isMegagroup() || _botsRequests.contains(peer)) return;
- _botsRequests.insert(peer, MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsBots(), MTP_int(0), MTP_int(cMaxGroupCount())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer)));
+ _botsRequests.insert(peer, MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsBots(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer)));
}
void ApiWrap::gotChat(PeerData *peer, const MTPmessages_Chats &result) {
_peerRequests.remove(peer);
-
+
if (result.type() == mtpc_messages_chats) {
const QVector &v(result.c_messages_chats().vchats.c_vector().v);
bool badVersion = false;
@@ -524,7 +512,7 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP
UserData *u = App::user(userId);
if (bots) {
if (u->botInfo) {
- peer->mgInfo->bots.insert(u, true);
+ peer->mgInfo->bots.insert(u);
botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
if (!u->botInfo->inited) {
needBotsInfos = true;
@@ -536,9 +524,9 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP
} else {
if (peer->mgInfo->lastParticipants.indexOf(u) < 0) {
peer->mgInfo->lastParticipants.push_back(u);
- if (admin) peer->mgInfo->lastAdmins.insert(u, true);
+ if (admin) peer->mgInfo->lastAdmins.insert(u);
if (u->botInfo) {
- peer->mgInfo->bots.insert(u, true);
+ peer->mgInfo->bots.insert(u);
if (peer->mgInfo->botStatus != 0 && peer->mgInfo->botStatus < 2) {
peer->mgInfo->botStatus = 2;
}
@@ -645,15 +633,23 @@ void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mt
int32 i = kick.first->asChannel()->mgInfo->lastParticipants.indexOf(kick.second);
if (i >= 0) {
kick.first->asChannel()->mgInfo->lastParticipants.removeAt(i);
- kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second);
}
- kick.first->asChannel()->mgInfo->bots.remove(kick.second);
if (kick.first->asChannel()->count > 1) {
- kick.first->asChannel()->count--;
+ --kick.first->asChannel()->count;
} else {
kick.first->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
kick.first->asChannel()->mgInfo->lastParticipantsCount = 0;
}
+ if (kick.first->asChannel()->mgInfo->lastAdmins.contains(kick.second)) {
+ kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second);
+ if (kick.first->asChannel()->adminsCount > 1) {
+ --kick.first->asChannel()->adminsCount;
+ }
+ }
+ kick.first->asChannel()->mgInfo->bots.remove(kick.second);
+ if (kick.first->asChannel()->mgInfo->bots.isEmpty() && kick.first->asChannel()->mgInfo->botStatus > 0) {
+ kick.first->asChannel()->mgInfo->botStatus = -1;
+ }
}
emit fullPeerUpdated(kick.first);
}
@@ -672,9 +668,9 @@ void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) {
void ApiWrap::requestStickerSets() {
for (QMap >::iterator i = _stickerSetRequests.begin(), j = i, e = _stickerSetRequests.end(); i != e; i = j) {
+ ++j;
if (i.value().second) continue;
- ++j;
int32 wait = (j == e) ? 0 : 10;
i.value().second = MTP::send(MTPmessages_GetStickerSet(MTP_inputStickerSetID(MTP_long(i.key()), MTP_long(i.value().first))), rpcDone(&ApiWrap::gotStickerSet, i.key()), rpcFail(&ApiWrap::gotStickerSetFail, i.key()), 0, wait);
}
@@ -682,15 +678,15 @@ void ApiWrap::requestStickerSets() {
void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {
_stickerSetRequests.remove(setId);
-
+
if (result.type() != mtpc_messages_stickerSet) return;
const MTPDmessages_stickerSet &d(result.c_messages_stickerSet());
-
+
if (d.vset.type() != mtpc_stickerSet) return;
const MTPDstickerSet &s(d.vset.c_stickerSet());
- StickerSets &sets(cRefStickerSets());
- StickerSets::iterator it = sets.find(setId);
+ Stickers::Sets &sets(Global::RefStickerSets());
+ auto it = sets.find(setId);
if (it == sets.cend()) return;
it->access = s.vaccess_hash.v;
@@ -700,7 +696,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
it->flags = s.vflags.v;
const QVector &d_docs(d.vdocuments.c_vector().v);
- StickerSets::iterator custom = sets.find(CustomStickerSetId);
+ auto custom = sets.find(Stickers::CustomSetId);
StickerPack pack;
pack.reserve(d_docs.size());
@@ -731,12 +727,32 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
++i;
}
}
+
if (pack.isEmpty()) {
- int32 removeIndex = cStickerSetsOrder().indexOf(setId);
- if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex);
+ int removeIndex = Global::StickerSetsOrder().indexOf(setId);
+ if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex);
sets.erase(it);
} else {
it->stickers = pack;
+ it->emoji.clear();
+ const QVector &v(d.vpacks.c_vector().v);
+ for (int32 i = 0, l = v.size(); i < l; ++i) {
+ if (v.at(i).type() != mtpc_stickerPack) continue;
+
+ const MTPDstickerPack &pack(v.at(i).c_stickerPack());
+ if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) {
+ const QVector &stickers(pack.vdocuments.c_vector().v);
+ StickerPack p;
+ p.reserve(stickers.size());
+ for (int32 j = 0, c = stickers.size(); j < c; ++j) {
+ DocumentData *doc = App::document(stickers.at(j).v);
+ if (!doc || !doc->sticker()) continue;
+
+ p.push_back(doc);
+ }
+ it->emoji.insert(e, p);
+ }
+ }
}
if (writeRecent) {
@@ -887,10 +903,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
}
for (QMap::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
- HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting);
- if (item) {
- item->initDimensions();
- Notify::historyItemResized(item);
+ if (HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting)) {
+ item->setPendingInitDimensions();
}
}
@@ -902,8 +916,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
WebPageItems::const_iterator j = items.constFind(i.key());
if (j != items.cend()) {
for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
- k.key()->initDimensions();
- Notify::historyItemResized(k.key());
+ k.key()->setPendingInitDimensions();
}
}
}
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 5daabcf74f..8845d90bd0 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
@@ -28,9 +28,8 @@ public:
ApiWrap(QObject *parent);
void init();
- void itemRemoved(HistoryItem *item);
-
- void requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id);
+ typedef SharedCallback RequestMessageDataCallback;
+ void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback *callback);
void requestFullPeer(PeerData *peer);
void requestPeer(PeerData *peer);
@@ -59,35 +58,37 @@ signals:
public slots:
- void resolveReplyTo();
+ void resolveMessageDatas();
void resolveWebPages();
void delayedRequestParticipantsCount();
private:
- void gotReplyTo(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req);
- struct ReplyToRequest {
- ReplyToRequest() : req(0) {
+ void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req);
+ struct MessageDataRequest {
+ MessageDataRequest() : req(0) {
}
+ typedef SharedCallback::Ptr CallbackPtr;
+ typedef QList Callbacks;
mtpRequestId req;
- QList replies;
+ Callbacks callbacks;
};
- typedef QMap ReplyToRequests;
- ReplyToRequests _replyToRequests;
- typedef QMap ChannelReplyToRequests;
- ChannelReplyToRequests _channelReplyToRequests;
- SingleTimer _replyToTimer;
+ typedef QMap MessageDataRequests;
+ MessageDataRequests _messageDataRequests;
+ typedef QMap ChannelMessageDataRequests;
+ ChannelMessageDataRequests _channelMessageDataRequests;
+ SingleDelayedCall *_messageDataResolveDelayed;
typedef QVector MessageIds;
- MessageIds collectMessageIds(const ReplyToRequests &requests);
- ReplyToRequests *replyToRequests(ChannelData *channel, bool onlyExisting = false);
+ MessageIds collectMessageIds(const MessageDataRequests &requests);
+ MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false);
void gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mtpRequestId req);
void gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req);
bool gotPeerFullFailed(PeerData *peer, const RPCError &err);
typedef QMap PeerRequests;
PeerRequests _fullPeerRequests;
-
+
void gotChat(PeerData *peer, const MTPmessages_Chats &result);
void gotUser(PeerData *peer, const MTPVector &result);
void gotChats(const MTPmessages_Chats &result);
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 045f77865a..901fc71c88 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "lang.h"
@@ -25,14 +25,15 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
#include "application.h"
#include "fileuploader.h"
#include "mainwidget.h"
+#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0)
#include
-
+#endif
#include "localstorage.h"
#include "numbers.h"
namespace {
- bool quiting = false;
+ App::LaunchState _launchState = App::Launched;
UserData *self = 0;
@@ -46,32 +47,23 @@ namespace {
UpdatedPeers updatedPeers;
PhotosData photosData;
- VideosData videosData;
- AudiosData audiosData;
DocumentsData documentsData;
- typedef QHash ImageLinksData;
- ImageLinksData imageLinksData;
+ typedef QHash LocationsData;
+ LocationsData locationsData;
typedef QHash WebPagesData;
WebPagesData webPagesData;
- typedef QMap ReplyMarkups;
- ReplyMarkups replyMarkups;
- ReplyMarkup zeroMarkup(MTPDreplyKeyboardMarkup_flag_ZERO);
- typedef QMap ChannelReplyMarkups;
- ChannelReplyMarkups channelReplyMarkups;
-
PhotoItems photoItems;
- VideoItems videoItems;
- AudioItems audioItems;
DocumentItems documentItems;
WebPageItems webPageItems;
SharedContactItems sharedContactItems;
GifItems gifItems;
- typedef QMap > RepliesTo;
- RepliesTo repliesTo;
+ typedef OrderedSet DependentItemsSet;
+ typedef QMap DependentItems;
+ DependentItems dependentItems;
Histories histories;
@@ -113,9 +105,6 @@ namespace {
typedef QHash LastPhotosMap;
LastPhotosMap lastPhotosMap;
- typedef QMap InlineResultLoaders;
- InlineResultLoaders inlineResultLoaders;
-
style::color _msgServiceBg;
style::color _msgServiceSelectBg;
style::color _historyScrollBarColor;
@@ -151,12 +140,12 @@ namespace App {
return result;
}
- Application *app() {
- return Application::app();
+ AppClass *app() {
+ return AppClass::app();
}
Window *wnd() {
- return Application::wnd();
+ return AppClass::wnd();
}
MainWidget *main() {
@@ -182,21 +171,19 @@ namespace App {
return main() ? main()->api() : 0;
}
+namespace {
bool loggedOut() {
- Window *w(wnd());
if (cHasPasscode()) {
cSetHasPasscode(false);
}
if (audioPlayer()) {
audioPlayer()->stopAndClear();
}
- if (w) {
+ if (Window *w = wnd()) {
w->tempDirDelete(Local::ClearManagerAll);
w->notifyClearFast();
w->setupIntro(true);
}
- MainWidget *m(main());
- if (m) m->destroyData();
MTP::authed(0);
Local::reset();
@@ -207,13 +194,14 @@ namespace App {
globalNotifyChatsPtr = UnknownNotifySettings;
if (App::uploader()) App::uploader()->clear();
clearStorageImages();
- if (w) {
+ if (Window *w = wnd()) {
w->getTitle()->updateBackButton();
w->updateTitleStatus();
w->getTitle()->resizeEvent(0);
}
return true;
}
+} // namespace
void logOut() {
if (MTP::started()) {
@@ -329,6 +317,25 @@ namespace App {
return lng_status_lastseen_date(lt_date, dOnline.date().toString(qsl("dd.MM.yy")));
}
+ namespace {
+ // we should get a full restriction in "{fulltype}: {reason}" format and we
+ // need to find a "-all" tag in {fulltype}, otherwise ignore this restriction
+ QString extractRestrictionReason(const QString &fullRestriction) {
+ int fullTypeEnd = fullRestriction.indexOf(':');
+ if (fullTypeEnd <= 0) {
+ return QString();
+ }
+
+ // {fulltype} is in "{type}-{tag}-{tag}-{tag}" format
+ // if we find "all" tag we return the restriction string
+ QStringList typeTags = fullRestriction.mid(0, fullTypeEnd).split('-').mid(1);
+ if (typeTags.contains(qsl("all"))) {
+ return fullRestriction.midRef(fullTypeEnd + 1).trimmed().toString();
+ }
+ return QString();
+ }
+ }
+
bool onlineColorUse(UserData *user, int32 now) {
if (isServiceUser(user->id) || user->botInfo) {
return false;
@@ -353,7 +360,7 @@ namespace App {
for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
const MTPuser &user(*i);
data = 0;
- bool wasContact = false;
+ bool wasContact = false, minimal = false;
const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty();
switch (user.type()) {
@@ -375,19 +382,27 @@ namespace App {
} break;
case mtpc_user: {
const MTPDuser &d(user.c_user());
+ minimal = d.is_min();
PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer);
- data->flags = d.vflags.v;
- if (d.is_self()) {
- data->input = MTP_inputPeerSelf();
- data->inputUser = MTP_inputUserSelf();
- } else if (!d.has_access_hash()) {
- data->input = MTP_inputPeerUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access));
- data->inputUser = MTP_inputUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access));
- } else {
- data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash);
- data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash);
+ if (!minimal) {
+ data->flags = d.vflags.v;
+ if (d.is_self()) {
+ data->input = MTP_inputPeerSelf();
+ data->inputUser = MTP_inputUserSelf();
+ } else if (!d.has_access_hash()) {
+ data->input = MTP_inputPeerUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access));
+ data->inputUser = MTP_inputUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access));
+ } else {
+ data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash);
+ data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash);
+ }
+ if (d.is_restricted()) {
+ data->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason)));
+ } else {
+ data->setRestrictionReason(QString());
+ }
}
if (d.is_deleted()) {
data->setPhone(QString());
@@ -396,10 +411,14 @@ namespace App {
data->access = UserNoAccess;
status = &emptyStatus;
} else {
- QString phone = d.has_phone() ? qs(d.vphone) : QString();
- QString fname = d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString();
- QString lname = d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString();
- QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString();
+ // apply first_name and last_name from minimal user only if we don't have
+ // local values for first name and last name already, otherwise skip
+ bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty();
+ QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName;
+ QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName;
+
+ QString phone = minimal ? data->phone : (d.has_phone() ? qs(d.vphone) : QString());
+ QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString());
bool phoneChanged = (data->phone != phone);
if (phoneChanged) data->setPhone(phone);
@@ -408,11 +427,18 @@ namespace App {
bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact();
bool showPhoneChanged = !isServiceUser(data->id) && !d.is_self() && ((showPhone && data->contact) || (!showPhone && !data->contact));
+ if (minimal) {
+ showPhoneChanged = false;
+ showPhone = !isServiceUser(data->id) && (data->id != peerFromUser(MTP::authedId())) && !data->contact;
+ }
// see also Local::readPeer
QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone;
+ if (!minimal && d.is_self() && uname != data->username) {
+ SignalHandlers::setCrashAnnotation("Username", uname);
+ }
data->setName(fname, lname, pname, uname);
if (d.has_photo()) {
data->setPhoto(d.vphoto);
@@ -423,30 +449,38 @@ namespace App {
status = d.has_status() ? &d.vstatus : &emptyStatus;
}
wasContact = (data->contact > 0);
- if (d.has_bot_info_version()) {
- data->setBotInfoVersion(d.vbot_info_version.v);
- data->botInfo->readsAllHistory = d.is_bot_chat_history();
- data->botInfo->cantJoinGroups = d.is_bot_nochats();
- data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString();
- } else {
- data->setBotInfoVersion(-1);
- }
- data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone.isEmpty() ? -1 : 0);
- if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsNoButton) != dbiprsNoButton) {
- cRefReportSpamStatuses().insert(data->id, dbiprsNoButton);
- Local::writeReportSpamStatuses();
- }
- if (d.is_self() && ::self != data) {
- ::self = data;
- if (App::wnd()) App::wnd()->updateGlobalMenu();
+ if (!minimal) {
+ if (d.has_bot_info_version()) {
+ data->setBotInfoVersion(d.vbot_info_version.v);
+ data->botInfo->readsAllHistory = d.is_bot_chat_history();
+ data->botInfo->cantJoinGroups = d.is_bot_nochats();
+ data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString();
+ } else {
+ data->setBotInfoVersion(-1);
+ }
+ data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone.isEmpty() ? -1 : 0);
+ if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) {
+ cRefReportSpamStatuses().insert(data->id, dbiprsHidden);
+ Local::writeReportSpamStatuses();
+ }
+ if (d.is_self() && ::self != data) {
+ ::self = data;
+ if (App::wnd()) App::wnd()->updateGlobalMenu();
+ }
}
} break;
}
if (!data) continue;
- data->loaded = true;
- if (status) switch (status->type()) {
+ if (minimal) {
+ if (data->loadedStatus == PeerData::NotLoaded) {
+ data->loadedStatus = PeerData::MinimalLoaded;
+ }
+ } else if (data->loadedStatus != PeerData::FullLoaded) {
+ data->loadedStatus = PeerData::FullLoaded;
+ }
+ if (status && !minimal) switch (status->type()) {
case mtpc_userStatusEmpty: data->onlineTill = 0; break;
case mtpc_userStatusRecently:
if (data->onlineTill > -10) { // don't modify pseudo-online
@@ -484,6 +518,7 @@ namespace App {
for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
const MTPchat &chat(*i);
data = 0;
+ bool minimal = false;
switch (chat.type()) {
case mtpc_chat: {
const MTPDchat &d(chat.c_chat());
@@ -501,7 +536,7 @@ namespace App {
const MTPDinputChannel &c(d.vmigrated_to.c_inputChannel());
ChannelData *channel = App::channel(peerFromChannel(c.vchannel_id));
if (!channel->mgInfo) {
- channel->flags |= MTPDchannel::flag_megagroup;
+ channel->flags |= MTPDchannel::Flag::f_megagroup;
channel->flagsUpdated();
}
if (!channel->access) {
@@ -520,7 +555,7 @@ namespace App {
if (!h->isEmpty()) {
h->clear(true);
}
- if (!hto->dialogs.isEmpty() && !h->dialogs.isEmpty()) {
+ if (hto->inChatList() && h->inChatList()) {
App::removeDialog(h);
}
}
@@ -532,7 +567,7 @@ namespace App {
}
}
- if (!(cdata->flags & MTPDchat::flag_admins_enabled) && (d.vflags.v & MTPDchat::flag_admins_enabled)) {
+ if (!(cdata->flags & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) {
cdata->invalidateParticipants();
}
cdata->flags = d.vflags.v;
@@ -564,24 +599,41 @@ namespace App {
const MTPDchannel &d(chat.c_channel());
PeerId peer(peerFromChannel(d.vid.v));
- data = App::channel(peer);
- data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash);
+ minimal = d.is_min();
+ if (minimal) {
+ data = App::channelLoaded(peer);
+ if (!data) {
+ continue; // minimal is not loaded, need to make getDifference
+ }
+ } else {
+ data = App::channel(peer);
+ data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0));
+ }
ChannelData *cdata = data->asChannel();
- cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash);
-
+ if (minimal) {
+ int32 mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy;
+ cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask);
+ } else {
+ cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash);
+ cdata->access = d.vaccess_hash.v;
+ cdata->date = d.vdate.v;
+ cdata->flags = d.vflags.v;
+ if (cdata->version < d.vversion.v) {
+ cdata->version = d.vversion.v;
+ }
+ if (d.is_restricted()) {
+ cdata->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason)));
+ } else {
+ cdata->setRestrictionReason(QString());
+ }
+ }
QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString();
cdata->setName(qs(d.vtitle), uname);
- cdata->access = d.vaccess_hash.v;
- cdata->date = d.vdate.v;
- cdata->flags = d.vflags.v;
cdata->isForbidden = false;
cdata->flagsUpdated();
cdata->setPhoto(d.vphoto);
- if (cdata->version < d.vversion.v) {
- cdata->version = d.vversion.v;
- }
} break;
case mtpc_channelForbidden: {
const MTPDchannelForbidden &d(chat.c_channelForbidden());
@@ -604,7 +656,13 @@ namespace App {
}
if (!data) continue;
- data->loaded = true;
+ if (minimal) {
+ if (data->loadedStatus == PeerData::NotLoaded) {
+ data->loadedStatus = PeerData::MinimalLoaded;
+ }
+ } else if (data->loadedStatus != PeerData::FullLoaded) {
+ data->loadedStatus = PeerData::FullLoaded;
+ }
if (App::main()) {
if (emitPeerUpdated) {
App::main()->peerUpdated(data);
@@ -636,7 +694,7 @@ namespace App {
int32 pversion = chat->participants.isEmpty() ? 1 : (chat->participants.begin().value() + 1);
chat->invitedByMe = ChatData::InvitedByMe();
chat->admins = ChatData::Admins();
- chat->flags &= ~MTPDchat::flag_admin;
+ chat->flags &= ~MTPDchat::Flag::f_admin;
for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
int32 uid = 0, inviter = 0;
switch (i->type()) {
@@ -662,12 +720,12 @@ namespace App {
if (user) {
chat->participants[user] = pversion;
if (inviter == MTP::authedId()) {
- chat->invitedByMe[user] = true;
+ chat->invitedByMe.insert(user);
}
if (i->type() == mtpc_chatParticipantAdmin) {
- chat->admins[user] = true;
+ chat->admins.insert(user);
if (user->isSelf()) {
- chat->flags |= MTPDchat::flag_admin;
+ chat->flags |= MTPDchat::Flag::f_admin;
}
}
} else {
@@ -734,7 +792,7 @@ namespace App {
} else if (chat->participants.find(user) == chat->participants.end()) {
chat->participants[user] = (chat->participants.isEmpty() ? 1 : chat->participants.begin().value());
if (d.vinviter_id.v == MTP::authedId()) {
- chat->invitedByMe[user] = true;
+ chat->invitedByMe.insert(user);
} else {
chat->invitedByMe.remove(user);
}
@@ -787,7 +845,7 @@ namespace App {
chat->invitedByMe.remove(user);
chat->admins.remove(user);
if (user->isSelf()) {
- chat->flags &= ~MTPDchat::flag_admin;
+ chat->flags &= ~MTPDchat::Flag::f_admin;
}
History *h = App::historyLoaded(chat->id);
@@ -834,13 +892,12 @@ namespace App {
}
chat->version = d.vversion.v;
if (mtpIsTrue(d.venabled)) {
- chat->flags |= MTPDchat::flag_admins_enabled;
+ chat->flags |= MTPDchat::Flag::f_admins_enabled;
if (!badVersion) {
chat->invalidateParticipants();
}
} else {
- chat->flags &= ~MTPDchat::flag_admins_enabled;
- chat->flags &= ~MTPDchat::flag_admin;
+ chat->flags &= ~MTPDchat::Flag::f_admins_enabled;
}
if (emitPeerUpdated) {
App::main()->peerUpdated(chat);
@@ -869,16 +926,16 @@ namespace App {
if (user) {
if (mtpIsTrue(d.vis_admin)) {
if (user->isSelf()) {
- chat->flags |= MTPDchat::flag_admin;
+ chat->flags |= MTPDchat::Flag::f_admin;
}
if (chat->noParticipantInfo()) {
App::api()->requestFullPeer(chat);
} else {
- chat->admins.insert(user, true);
+ chat->admins.insert(user);
}
} else {
if (user->isSelf()) {
- chat->flags &= ~MTPDchat::flag_admin;
+ chat->flags &= ~MTPDchat::Flag::f_admin;
}
chat->admins.remove(user);
}
@@ -902,12 +959,9 @@ namespace App {
}
if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText());
- existing->initDimensions();
- Notify::historyItemResized(existing);
-
- existing->updateMedia(m.has_media() ? (&m.vmedia) : 0, true);
- existing->addToOverview(AddToOverviewNew);
+ existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
+ existing->addToOverview(AddToOverviewNew);
if (!existing->detached()) {
App::checkSavedGif(existing);
@@ -919,21 +973,33 @@ namespace App {
return false;
}
+ void updateEditedMessage(const MTPDmessage &m) {
+ PeerId peerId = peerFromMTP(m.vto_id);
+ if (m.has_from_id() && peerToUser(peerId) == MTP::authedId()) {
+ peerId = peerFromUser(m.vfrom_id);
+ }
+ if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
+ existing->applyEdition(m);
+ }
+ }
+
void addSavedGif(DocumentData *doc) {
SavedGifs &saved(cRefSavedGifs());
int32 index = saved.indexOf(doc);
if (index) {
if (index > 0) saved.remove(index);
saved.push_front(doc);
- if (saved.size() > cSavedGifsLimit()) saved.pop_back();
+ if (saved.size() > Global::SavedGifsLimit()) saved.pop_back();
Local::writeSavedGifs();
if (App::main()) emit App::main()->savedGifsUpdated();
+ cSetLastSavedGifsUpdate(0);
+ App::main()->updateStickers();
}
}
void checkSavedGif(HistoryItem *item) {
- if (!item->toHistoryForwarded() && item->out()) {
+ if (!item->Has() && (item->out() || item->history()->peer == App::self())) {
if (HistoryMedia *media = item->getMedia()) {
if (DocumentData *doc = media->getDocument()) {
if (doc->isGifv()) {
@@ -1054,7 +1120,6 @@ namespace App {
}
void feedWereDeleted(ChannelId channelId, const QVector &msgsIds) {
- bool resized = false;
MsgsData *data = fetchMsgsData(channelId, false);
if (!data) return;
@@ -1065,9 +1130,6 @@ namespace App {
MsgsData::const_iterator j = data->constFind(i->v);
if (j != data->cend()) {
History *h = (*j)->history();
- if (App::main() && (h->peer == App::main()->peer() || (App::main()->peer() && h->peer->migrateTo() == App::main()->peer())) && !(*j)->detached()) {
- resized = true;
- }
(*j)->destroy();
if (!h->lastMsg) historiesToCheck.insert(h, true);
} else {
@@ -1079,9 +1141,6 @@ namespace App {
}
}
}
- if (resized) {
- Notify::historyItemsResized();
- }
if (main()) {
for (QMap::const_iterator i = historiesToCheck.cbegin(), e = historiesToCheck.cend(); i != e; ++i) {
main()->checkPeerHistory(i.key()->peer);
@@ -1120,8 +1179,8 @@ namespace App {
switch (myLink.type()) {
case mtpc_contactLinkContact:
user->contact = 1;
- if (user->contact == 1 && cReportSpamStatuses().value(user->id, dbiprsNoButton) != dbiprsNoButton) {
- cRefReportSpamStatuses().insert(user->id, dbiprsNoButton);
+ if (user->contact == 1 && cReportSpamStatuses().value(user->id, dbiprsHidden) != dbiprsHidden) {
+ cRefReportSpamStatuses().insert(user->id, dbiprsHidden);
Local::writeReportSpamStatuses();
}
break;
@@ -1286,26 +1345,6 @@ namespace App {
return App::photoSet(photo.vid.v, convert, 0, 0, ImagePtr(), ImagePtr(), ImagePtr());
}
- VideoData *feedVideo(const MTPDvideo &video, VideoData *convert) {
- return App::videoSet(video.vid.v, convert, video.vaccess_hash.v, video.vdate.v, video.vduration.v, video.vw.v, video.vh.v, App::image(video.vthumb), video.vdc_id.v, video.vsize.v);
- }
-
- AudioData *feedAudio(const MTPaudio &audio, AudioData *convert) {
- switch (audio.type()) {
- case mtpc_audio: {
- return feedAudio(audio.c_audio(), convert);
- } break;
- case mtpc_audioEmpty: {
- return App::audioSet(audio.c_audioEmpty().vid.v, convert, 0, 0, QString(), 0, 0, 0);
- } break;
- }
- return App::audio(0);
- }
-
- AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert) {
- return App::audioSet(audio.vid.v, convert, audio.vaccess_hash.v, audio.vdate.v, qs(audio.vmime_type), audio.vduration.v, audio.vdc_id.v, audio.vsize.v);
- }
-
DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb) {
switch (document.type()) {
case mtpc_document: {
@@ -1354,41 +1393,16 @@ namespace App {
return 0;
}
- PeerData *peerLoaded(const PeerId &peer) {
- PeersData::const_iterator i = peersData.constFind(peer);
- return (i != peersData.cend()) ? i.value() : 0;
- }
-
- UserData *userLoaded(const PeerId &id) {
- PeerData *peer = peerLoaded(id);
- return (peer && peer->loaded) ? peer->asUser() : 0;
- }
- ChatData *chatLoaded(const PeerId &id) {
- PeerData *peer = peerLoaded(id);
- return (peer && peer->loaded) ? peer->asChat() : 0;
- }
- ChannelData *channelLoaded(const PeerId &id) {
- PeerData *peer = peerLoaded(id);
- return (peer && peer->loaded) ? peer->asChannel() : 0;
- }
- UserData *userLoaded(int32 user_id) {
- return userLoaded(peerFromUser(user_id));
- }
- ChatData *chatLoaded(int32 chat_id) {
- return chatLoaded(peerFromChat(chat_id));
- }
- ChannelData *channelLoaded(int32 channel_id) {
- return channelLoaded(peerFromChannel(channel_id));
- }
-
UserData *curUser() {
return user(MTP::authedId());
}
- PeerData *peer(const PeerId &id) {
- PeersData::const_iterator i = peersData.constFind(id);
+ PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction) {
+ if (!id) return nullptr;
+
+ auto i = peersData.constFind(id);
if (i == peersData.cend()) {
- PeerData *newData = 0;
+ PeerData *newData = nullptr;
if (peerIsUser(id)) {
newData = new UserData(id);
} else if (peerIsChat(id)) {
@@ -1396,45 +1410,38 @@ namespace App {
} else if (peerIsChannel(id)) {
newData = new ChannelData(id);
}
- if (!newData) return 0;
+ t_assert(newData != nullptr);
newData->input = MTPinputPeer(MTP_inputPeerEmpty());
i = peersData.insert(id, newData);
}
+ switch (restriction) {
+ case PeerData::MinimalLoaded: {
+ if (i.value()->loadedStatus == PeerData::NotLoaded) {
+ return nullptr;
+ }
+ } break;
+ case PeerData::FullLoaded: {
+ if (i.value()->loadedStatus != PeerData::FullLoaded) {
+ return nullptr;
+ }
+ } break;
+ }
return i.value();
}
- UserData *user(const PeerId &id) {
- return peer(id)->asUser();
- }
- ChatData *chat(const PeerId &id) {
- return peer(id)->asChat();
- }
- ChannelData *channel(const PeerId &id) {
- return peer(id)->asChannel();
- }
- UserData *user(int32 user_id) {
- return user(peerFromUser(user_id));
- }
- ChatData *chat(int32 chat_id) {
- return chat(peerFromChat(chat_id));
- }
- ChannelData *channel(int32 channel_id) {
- return channel(peerFromChannel(channel_id));
- }
-
UserData *self() {
return ::self;
}
PeerData *peerByName(const QString &username) {
QString uname(username.trimmed());
- for (PeersData::const_iterator i = peersData.cbegin(), e = peersData.cend(); i != e; ++i) {
- if (!i.value()->userName().compare(uname, Qt::CaseInsensitive)) {
- return i.value()->asUser();
+ for_const (PeerData *peer, peersData) {
+ if (!peer->userName().compare(uname, Qt::CaseInsensitive)) {
+ return peer;
}
}
- return 0;
+ return nullptr;
}
void updateImage(ImagePtr &old, ImagePtr now) {
@@ -1511,110 +1518,6 @@ namespace App {
return result;
}
- VideoData *video(const VideoId &video) {
- VideosData::const_iterator i = ::videosData.constFind(video);
- if (i == ::videosData.cend()) {
- i = ::videosData.insert(video, new VideoData(video));
- }
- return i.value();
- }
-
- VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) {
- if (convert) {
- if (convert->id != video) {
- VideosData::iterator i = ::videosData.find(convert->id);
- if (i != ::videosData.cend() && i.value() == convert) {
- ::videosData.erase(i);
- }
- convert->id = video;
- convert->status = FileReady;
- }
- if (date) {
- convert->access = access;
- convert->date = date;
- updateImage(convert->thumb, thumb);
- convert->duration = duration;
- convert->w = w;
- convert->h = h;
- convert->dc = dc;
- convert->size = size;
- }
- }
- VideosData::const_iterator i = ::videosData.constFind(video);
- VideoData *result;
- if (i == ::videosData.cend()) {
- if (convert) {
- result = convert;
- } else {
- result = new VideoData(video, access, date, duration, w, h, thumb, dc, size);
- }
- ::videosData.insert(video, result);
- } else {
- result = i.value();
- if (result != convert && date) {
- result->access = access;
- result->date = date;
- result->duration = duration;
- result->w = w;
- result->h = h;
- updateImage(result->thumb, thumb);
- result->dc = dc;
- result->size = size;
- }
- }
- return result;
- }
-
- AudioData *audio(const AudioId &audio) {
- AudiosData::const_iterator i = ::audiosData.constFind(audio);
- if (i == ::audiosData.cend()) {
- i = ::audiosData.insert(audio, new AudioData(audio));
- }
- return i.value();
- }
-
- AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) {
- if (convert) {
- if (convert->id != audio) {
- AudiosData::iterator i = ::audiosData.find(convert->id);
- if (i != ::audiosData.cend() && i.value() == convert) {
- ::audiosData.erase(i);
- }
- convert->id = audio;
- convert->status = FileReady;
- }
- if (date) {
- convert->access = access;
- convert->date = date;
- convert->mime = mime;
- convert->duration = duration;
- convert->dc = dc;
- convert->size = size;
- }
- }
- AudiosData::const_iterator i = ::audiosData.constFind(audio);
- AudioData *result;
- if (i == ::audiosData.cend()) {
- if (convert) {
- result = convert;
- } else {
- result = new AudioData(audio, access, date, mime, duration, dc, size);
- }
- ::audiosData.insert(audio, result);
- } else {
- result = i.value();
- if (result != convert && date) {
- result->access = access;
- result->date = date;
- result->mime = mime;
- result->duration = duration;
- result->dc = dc;
- result->size = size;
- }
- }
- return result;
- }
-
DocumentData *document(const DocumentId &document) {
DocumentsData::const_iterator i = ::documentsData.constFind(document);
if (i == ::documentsData.cend()) {
@@ -1631,10 +1534,15 @@ namespace App {
if (i != ::documentsData.cend() && i.value() == convert) {
::documentsData.erase(i);
}
- Local::copyStickerImage(mediaKey(DocumentFileLocation, convert->dc, convert->id), mediaKey(DocumentFileLocation, dc, document));
+
+ // inline bot sent gifs caching
+ if (!convert->voice() && !convert->isVideo()) {
+ Local::copyStickerImage(mediaKey(DocumentFileLocation, convert->dc, convert->id), mediaKey(DocumentFileLocation, dc, document));
+ }
+
convert->id = document;
convert->status = FileReady;
- sentSticker = !!convert->sticker();
+ sentSticker = (convert->sticker() != 0);
}
if (date) {
convert->access = access;
@@ -1642,7 +1550,7 @@ namespace App {
convert->setattributes(attributes);
convert->mime = mime;
if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) {
- convert->thumb = thumb;
+ updateImage(convert->thumb, thumb);
}
convert->dc = dc;
convert->size = size;
@@ -1658,7 +1566,7 @@ namespace App {
const FileLocation &loc(convert->location(true));
if (!loc.isEmpty()) {
- Local::writeFileLocation(mediaKey(DocumentFileLocation, convert->dc, convert->id), loc);
+ Local::writeFileLocation(convert->mediaKey(), loc);
}
}
DocumentsData::const_iterator i = ::documentsData.constFind(document);
@@ -1669,7 +1577,9 @@ namespace App {
} else {
result = new DocumentData(document, access, date, attributes, mime, thumb, dc, size);
result->recountIsImage();
- if (result->sticker()) result->sticker()->loc = thumbLocation;
+ if (result->sticker()) {
+ result->sticker()->loc = thumbLocation;
+ }
}
::documentsData.insert(document, result);
} else {
@@ -1690,7 +1600,9 @@ namespace App {
}
}
}
- if (sentSticker && App::main()) App::main()->incrementSticker(result);
+ if (sentSticker && App::main()) {
+ App::main()->incrementSticker(result);
+ }
return result;
}
@@ -1762,43 +1674,24 @@ namespace App {
return result;
}
- ImageLinkData *imageLink(const QString &imageLink) {
- ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink);
- if (i == imageLinksData.cend()) {
- i = imageLinksData.insert(imageLink, new ImageLinkData(imageLink));
+ LocationData *location(const LocationCoords &coords) {
+ LocationsData::const_iterator i = locationsData.constFind(coords);
+ if (i == locationsData.cend()) {
+ i = locationsData.insert(coords, new LocationData(coords));
}
return i.value();
}
- ImageLinkData *imageLinkSet(const QString &imageLink, ImageLinkType type, const QString &url) {
- ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink);
- ImageLinkData *result;
- if (i == imageLinksData.cend()) {
- result = new ImageLinkData(imageLink);
- imageLinksData.insert(imageLink, result);
- result->type = type;
- } else {
- result = i.value();
- }
- return result;
- }
-
void forgetMedia() {
lastPhotos.clear();
lastPhotosMap.clear();
for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) {
i.value()->forget();
}
- for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) {
- i.value()->forget();
- }
- for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) {
- i.value()->forget();
- }
for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) {
i.value()->forget();
}
- for (ImageLinksData::const_iterator i = imageLinksData.cbegin(), e = imageLinksData.cend(); i != e; ++i) {
+ for (LocationsData::const_iterator i = ::locationsData.cbegin(), e = ::locationsData.cend(); i != e; ++i) {
i.value()->thumb->forget();
}
}
@@ -1887,38 +1780,53 @@ namespace App {
MsgsData *data = fetchMsgsData(item->channelId(), false);
if (!data) return;
- MsgsData::iterator i = data->find(item->id);
+ auto i = data->find(item->id);
if (i != data->cend()) {
if (i.value() == item) {
data->erase(i);
}
}
historyItemDetached(item);
- RepliesTo::iterator j = ::repliesTo.find(item);
- if (j != ::repliesTo.cend()) {
- for (QMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
- k.key()->replyToReplaced(item, 0);
+ auto j = ::dependentItems.find(item);
+ if (j != ::dependentItems.cend()) {
+ DependentItemsSet items;
+ std::swap(items, j.value());
+ ::dependentItems.erase(j);
+
+ for_const (HistoryItem *dependent, items) {
+ dependent->dependencyItemRemoved(item);
}
- ::repliesTo.erase(j);
}
- if (App::main() && !App::quiting()) {
+ if (App::main() && !App::quitting()) {
App::main()->itemRemoved(item);
}
}
- void historyClearMsgs() {
- ::repliesTo.clear();
-
- QVector toDelete;
- for (MsgsData::const_iterator i = msgsData.cbegin(), e = msgsData.cend(); i != e; ++i) {
- if ((*i)->detached()) {
- toDelete.push_back(*i);
+ void historyUpdateDependent(HistoryItem *item) {
+ DependentItems::iterator j = ::dependentItems.find(item);
+ if (j != ::dependentItems.cend()) {
+ for_const (HistoryItem *dependent, j.value()) {
+ dependent->updateDependencyItem();
}
}
- for (ChannelMsgsData::const_iterator j = channelMsgsData.cbegin(), end = channelMsgsData.cend(); j != end; ++j) {
- for (MsgsData::const_iterator i = j->cbegin(), e = j->cend(); i != e; ++i) {
- if ((*i)->detached()) {
- toDelete.push_back(*i);
+ if (App::main()) {
+ App::main()->itemEdited(item);
+ }
+ }
+
+ void historyClearMsgs() {
+ ::dependentItems.clear();
+
+ QVector toDelete;
+ for_const (HistoryItem *item, msgsData) {
+ if (item->detached()) {
+ toDelete.push_back(item);
+ }
+ }
+ for_const (const MsgsData &chMsgsData, channelMsgsData) {
+ for_const (HistoryItem *item, chMsgsData) {
+ if (item->detached()) {
+ toDelete.push_back(item);
}
}
}
@@ -1929,8 +1837,6 @@ namespace App {
}
::hoveredItem = ::pressedItem = ::hoveredLinkItem = ::pressedLinkItem = ::contextItem = 0;
- replyMarkups.clear();
- channelReplyMarkups.clear();
}
void historyClearItems() {
@@ -1940,41 +1846,35 @@ namespace App {
updatedPeers.clear();
cSetSavedPeers(SavedPeers());
cSetSavedPeersByTime(SavedPeersByTime());
- for (PeersData::const_iterator i = peersData.cbegin(), e = peersData.cend(); i != e; ++i) {
- delete *i;
+ cSetRecentInlineBots(RecentInlineBots());
+ for_const (PeerData *peer, peersData) {
+ delete peer;
}
peersData.clear();
- for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) {
- delete *i;
+ for_const (PhotoData *photo, ::photosData) {
+ delete photo;
}
::photosData.clear();
- for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) {
- delete *i;
- }
- ::videosData.clear();
- for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) {
- delete *i;
- }
- ::audiosData.clear();
- for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) {
- delete *i;
+ for_const (DocumentData *document, ::documentsData) {
+ delete document;
}
::documentsData.clear();
- for (WebPagesData::const_iterator i = webPagesData.cbegin(), e = webPagesData.cend(); i != e; ++i) {
- delete *i;
+ for_const (WebPageData *webpage, webPagesData) {
+ delete webpage;
}
webPagesData.clear();
if (api()) api()->clearWebPageRequests();
cSetRecentStickers(RecentStickerPack());
- cSetStickerSets(StickerSets());
- cSetStickerSetsOrder(StickerSetsOrder());
- cSetLastStickersUpdate(0);
+ Global::SetStickerSets(Stickers::Sets());
+ Global::SetStickerSetsOrder(Stickers::Order());
+ Global::SetLastStickersUpdate(0);
cSetSavedGifs(SavedGifs());
cSetLastSavedGifsUpdate(0);
cSetReportSpamStatuses(ReportSpamStatuses());
+ cSetAutoDownloadPhoto(0);
+ cSetAutoDownloadAudio(0);
+ cSetAutoDownloadGif(0);
::photoItems.clear();
- ::videoItems.clear();
- ::audioItems.clear();
::documentItems.clear();
::webPageItems.clear();
::sharedContactItems.clear();
@@ -1985,16 +1885,16 @@ namespace App {
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
- void historyRegReply(HistoryReply *reply, HistoryItem *to) {
- ::repliesTo[to].insert(reply, true);
+ void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency) {
+ ::dependentItems[dependency].insert(dependent);
}
- void historyUnregReply(HistoryReply *reply, HistoryItem *to) {
- RepliesTo::iterator i = ::repliesTo.find(to);
- if (i != ::repliesTo.cend()) {
- i.value().remove(reply);
+ void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency) {
+ auto i = ::dependentItems.find(dependency);
+ if (i != ::dependentItems.cend()) {
+ i.value().remove(dependent);
if (i.value().isEmpty()) {
- ::repliesTo.erase(i);
+ ::dependentItems.erase(i);
}
}
}
@@ -2104,6 +2004,7 @@ namespace App {
::cornersMask[i]->setDevicePixelRatio(cRetinaFactor());
}
prepareCorners(BlackCorners, st::msgRadius, st::black);
+ prepareCorners(WhiteCorners, st::msgRadius, st::white);
prepareCorners(ServiceCorners, st::msgRadius, st::msgServiceBg);
prepareCorners(ServiceSelectedCorners, st::msgRadius, st::msgServiceSelectBg);
prepareCorners(SelectedOverlayCorners, st::msgRadius, st::msgSelectOverlay);
@@ -2132,8 +2033,8 @@ namespace App {
}
void clearHistories() {
- textlnkOver(TextLinkPtr());
- textlnkDown(TextLinkPtr());
+ ClickHandler::clearActive();
+ ClickHandler::unpressed();
histories().clear();
@@ -2271,23 +2172,22 @@ namespace App {
}
void quit() {
- if (quiting()) return;
+ if (quitting()) return;
+ setLaunchState(QuitRequested);
- setQuiting();
- if (wnd()) {
- wnd()->quit();
- }
- if (app()) {
- app()->quit();
- }
+ Application::quit();
}
- bool quiting() {
- return ::quiting;
+ bool quitting() {
+ return _launchState != Launched;
}
- void setQuiting() {
- ::quiting = true;
+ LaunchState launchState() {
+ return _launchState;
+ }
+
+ void setLaunchState(LaunchState state) {
+ _launchState = state;
}
QImage readImage(QByteArray data, QByteArray *format, bool opaque, bool *animated) {
@@ -2375,38 +2275,6 @@ namespace App {
return ::photosData;
}
- void regVideoItem(VideoData *data, HistoryItem *item) {
- ::videoItems[data].insert(item, NullType());
- }
-
- void unregVideoItem(VideoData *data, HistoryItem *item) {
- ::videoItems[data].remove(item);
- }
-
- const VideoItems &videoItems() {
- return ::videoItems;
- }
-
- const VideosData &videosData() {
- return ::videosData;
- }
-
- void regAudioItem(AudioData *data, HistoryItem *item) {
- ::audioItems[data].insert(item, NullType());
- }
-
- void unregAudioItem(AudioData*data, HistoryItem *item) {
- ::audioItems[data].remove(item);
- }
-
- const AudioItems &audioItems() {
- return ::audioItems;
- }
-
- const AudiosData &audiosData() {
- return ::audiosData;
- }
-
void regDocumentItem(DocumentData *data, HistoryItem *item) {
::documentItems[data].insert(item, NullType());
}
@@ -2468,7 +2336,7 @@ namespace App {
QString phoneFromSharedContact(int32 userId) {
SharedContactItems::const_iterator i = ::sharedContactItems.constFind(userId);
- if (i != ::sharedContactItems.cend()) {
+ if (i != ::sharedContactItems.cend() && !i->isEmpty()) {
HistoryMedia *media = i->cbegin().key()->getMedia();
if (media && media->type() == MediaTypeContact) {
return static_cast(media)->phone();
@@ -2505,125 +2373,36 @@ namespace App {
if (changeInMin) App::main()->updateMutedIn(changeInMin);
}
- void regInlineResultLoader(FileLoader *loader, InlineResult *result) {
- ::inlineResultLoaders.insert(loader, result);
- }
-
- void unregInlineResultLoader(FileLoader *loader) {
- ::inlineResultLoaders.remove(loader);
- }
-
- InlineResult *inlineResultFromLoader(FileLoader *loader) {
- InlineResultLoaders::const_iterator i = ::inlineResultLoaders.find(loader);
- return (i == ::inlineResultLoaders.cend()) ? 0 : i.value();
- }
-
- inline void insertReplyMarkup(ChannelId channelId, MsgId msgId, const ReplyMarkup &markup) {
- if (channelId == NoChannel) {
- replyMarkups.insert(msgId, markup);
- } else {
- channelReplyMarkups[channelId].insert(msgId, markup);
- }
- }
-
- void feedReplyMarkup(ChannelId channelId, MsgId msgId, const MTPReplyMarkup &markup) {
- ReplyMarkup data;
- ReplyMarkup::Commands &commands(data.commands);
- switch (markup.type()) {
- case mtpc_replyKeyboardMarkup: {
- const MTPDreplyKeyboardMarkup &d(markup.c_replyKeyboardMarkup());
- data.flags = d.vflags.v;
-
- const QVector &v(d.vrows.c_vector().v);
- if (!v.isEmpty()) {
- commands.reserve(v.size());
- for (int32 i = 0, l = v.size(); i < l; ++i) {
- switch (v.at(i).type()) {
- case mtpc_keyboardButtonRow: {
- const MTPDkeyboardButtonRow &r(v.at(i).c_keyboardButtonRow());
- const QVector &b(r.vbuttons.c_vector().v);
- if (!b.isEmpty()) {
- QList btns;
- btns.reserve(b.size());
- for (int32 j = 0, s = b.size(); j < s; ++j) {
- switch (b.at(j).type()) {
- case mtpc_keyboardButton: {
- btns.push_back(qs(b.at(j).c_keyboardButton().vtext));
- } break;
- }
- }
- if (!btns.isEmpty()) commands.push_back(btns);
- }
- } break;
- }
- }
- if (!commands.isEmpty()) {
- insertReplyMarkup(channelId, msgId, data);
- }
- }
- } break;
-
- case mtpc_replyKeyboardHide: {
- const MTPDreplyKeyboardHide &d(markup.c_replyKeyboardHide());
- if (d.vflags.v) {
- insertReplyMarkup(channelId, msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_ZERO));
- }
- } break;
-
- case mtpc_replyKeyboardForceReply: {
- const MTPDreplyKeyboardForceReply &d(markup.c_replyKeyboardForceReply());
- insertReplyMarkup(channelId, msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_FORCE_REPLY));
- } break;
- }
- }
-
- void clearReplyMarkup(ChannelId channelId, MsgId msgId) {
- if (channelId == NoChannel) {
- replyMarkups.remove(msgId);
- } else {
- ChannelReplyMarkups::iterator i = channelReplyMarkups.find(channelId);
- if (i != channelReplyMarkups.cend()) {
- i->remove(msgId);
- if (i->isEmpty()) {
- channelReplyMarkups.erase(i);
- }
- }
- }
- }
-
- inline const ReplyMarkup &replyMarkup(const ReplyMarkups &markups, MsgId msgId) {
- ReplyMarkups::const_iterator i = markups.constFind(msgId);
- if (i == markups.cend()) return zeroMarkup;
- return i.value();
- }
- const ReplyMarkup &replyMarkup(ChannelId channelId, MsgId msgId) {
- if (channelId == NoChannel) {
- return replyMarkup(replyMarkups, msgId);
- }
- ChannelReplyMarkups::const_iterator j = channelReplyMarkups.constFind(channelId);
- if (j == channelReplyMarkups.cend()) return zeroMarkup;
- return replyMarkup(*j, msgId);
- }
-
void setProxySettings(QNetworkAccessManager &manager) {
+#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
manager.setProxy(getHttpProxySettings());
+#endif
}
+#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
QNetworkProxy getHttpProxySettings() {
- if (cConnectionType() == dbictHttpProxy) {
- const ConnectionProxy &p(cConnectionProxy());
- return QNetworkProxy(QNetworkProxy::HttpProxy, p.host, p.port, p.user, p.password);
+ const ConnectionProxy *proxy = 0;
+ if (Global::started()) {
+ proxy = (cConnectionType() == dbictHttpProxy) ? (&cConnectionProxy()) : 0;
+ } else {
+ proxy = Sandbox::PreLaunchProxy().host.isEmpty() ? 0 : (&Sandbox::PreLaunchProxy());
+ }
+ if (proxy) {
+ return QNetworkProxy(QNetworkProxy::HttpProxy, proxy->host, proxy->port, proxy->user, proxy->password);
}
return QNetworkProxy(QNetworkProxy::DefaultProxy);
}
+#endif
void setProxySettings(QTcpSocket &socket) {
+#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
if (cConnectionType() == dbictTcpProxy) {
const ConnectionProxy &p(cConnectionProxy());
socket.setProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, p.host, p.port, p.user, p.password));
} else {
socket.setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
}
+#endif
}
QImage **cornersMask() {
diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h
index c01fdf9802..3b53eb0611 100644
--- a/Telegram/SourceFiles/app.h
+++ b/Telegram/SourceFiles/app.h
@@ -16,13 +16,13 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
-#include "types.h"
+#include "basic_types.h"
-class Application;
+class AppClass;
class Window;
class MainWidget;
class SettingsWidget;
@@ -36,30 +36,18 @@ class FileUploader;
typedef QMap HistoryItemsMap;
typedef QHash PhotoItems;
-typedef QHash VideoItems;
-typedef QHash AudioItems;
typedef QHash DocumentItems;
typedef QHash WebPageItems;
typedef QHash SharedContactItems;
typedef QHash GifItems;
typedef QHash PhotosData;
-typedef QHash VideosData;
-typedef QHash AudiosData;
typedef QHash DocumentsData;
-struct ReplyMarkup {
- ReplyMarkup(int32 flags = 0) : flags(flags) {
- }
- typedef QList > Commands;
- Commands commands;
- int32 flags;
-};
-
class LayeredWidget;
namespace App {
- Application *app();
+ AppClass *app();
Window *wnd();
MainWidget *main();
SettingsWidget *settings();
@@ -68,7 +56,6 @@ namespace App {
ApiWrap *api();
void logOut();
- bool loggedOut();
QString formatPhone(QString phone);
@@ -85,6 +72,7 @@ namespace App {
void feedChatAdmins(const MTPDupdateChatAdmins &d, bool emitPeerUpdated = true);
void feedParticipantAdmin(const MTPDupdateChatParticipantAdmin &d, bool emitPeerUpdated = true);
bool checkEntitiesAndViewsUpdate(const MTPDmessage &m); // returns true if item found and it is not detached
+ void updateEditedMessage(const MTPDmessage &m);
void addSavedGif(DocumentData *doc);
void checkSavedGif(HistoryItem *item);
void feedMsgs(const QVector &msgs, NewMessageType type);
@@ -106,9 +94,6 @@ namespace App {
PhotoData *feedPhoto(const MTPPhoto &photo, const PreparedPhotoThumbs &thumbs);
PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = 0);
PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert = 0);
- VideoData *feedVideo(const MTPDvideo &video, VideoData *convert = 0);
- AudioData *feedAudio(const MTPaudio &audio, AudioData *convert = 0);
- AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert = 0);
DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb);
DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = 0);
DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert = 0);
@@ -116,36 +101,57 @@ namespace App {
WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert = 0);
WebPageData *feedWebPage(const MTPWebPage &webpage);
- PeerData *peerLoaded(const PeerId &id);
- UserData *userLoaded(const PeerId &id);
- ChatData *chatLoaded(const PeerId &id);
- ChannelData *channelLoaded(const PeerId &id);
- UserData *userLoaded(int32 user);
- ChatData *chatLoaded(int32 chat);
- ChannelData *channelLoaded(int32 channel);
+ PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded);
+ inline UserData *user(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
+ return asUser(peer(id, restriction));
+ }
+ inline ChatData *chat(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
+ return asChat(peer(id, restriction));
+ }
+ inline ChannelData *channel(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
+ return asChannel(peer(id, restriction));
+ }
+ inline UserData *user(UserId userId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
+ return asUser(peer(peerFromUser(userId), restriction));
+ }
+ inline ChatData *chat(ChatId chatId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
+ return asChat(peer(peerFromChat(chatId), restriction));
+ }
+ inline ChannelData *channel(ChannelId channelId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
+ return asChannel(peer(peerFromChannel(channelId), restriction));
+ }
+ inline PeerData *peerLoaded(const PeerId &id) {
+ return peer(id, PeerData::FullLoaded);
+ }
+ inline UserData *userLoaded(const PeerId &id) {
+ return user(id, PeerData::FullLoaded);
+ }
+ inline ChatData *chatLoaded(const PeerId &id) {
+ return chat(id, PeerData::FullLoaded);
+ }
+ inline ChannelData *channelLoaded(const PeerId &id) {
+ return channel(id, PeerData::FullLoaded);
+ }
+ inline UserData *userLoaded(UserId userId) {
+ return user(userId, PeerData::FullLoaded);
+ }
+ inline ChatData *chatLoaded(ChatId chatId) {
+ return chat(chatId, PeerData::FullLoaded);
+ }
+ inline ChannelData *channelLoaded(ChannelId channelId) {
+ return channel(channelId, PeerData::FullLoaded);
+ }
- PeerData *peer(const PeerId &id);
- UserData *user(const PeerId &id);
- ChatData *chat(const PeerId &id);
- ChannelData *channel(const PeerId &id);
- UserData *user(int32 user_id);
- ChatData *chat(int32 chat_id);
- ChannelData *channel(int32 channel_id);
UserData *self();
PeerData *peerByName(const QString &username);
QString peerName(const PeerData *peer, bool forDialogs = false);
PhotoData *photo(const PhotoId &photo);
PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full);
- VideoData *video(const VideoId &video);
- VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size);
- AudioData *audio(const AudioId &audio);
- AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size);
DocumentData *document(const DocumentId &document);
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation);
WebPageData *webPage(const WebPageId &webPage);
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill);
- ImageLinkData *imageLink(const QString &imageLink);
- ImageLinkData *imageLinkSet(const QString &imageLink, ImageLinkType type, const QString &url);
+ LocationData *location(const LocationCoords &coords);
void forgetMedia();
MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo);
@@ -155,16 +161,27 @@ namespace App {
History *historyFromDialog(const PeerId &peer, int32 unreadCnt, int32 maxInboxRead);
History *historyLoaded(const PeerId &peer);
HistoryItem *histItemById(ChannelId channelId, MsgId itemId);
+ inline History *history(const PeerData *peer) {
+ t_assert(peer != nullptr);
+ return history(peer->id);
+ }
+ inline History *historyLoaded(const PeerData *peer) {
+ return peer ? historyLoaded(peer->id) : nullptr;
+ }
+ inline HistoryItem *histItemById(const ChannelData *channel, MsgId itemId) {
+ return histItemById(channel ? peerToChannel(channel->id) : 0, itemId);
+ }
inline HistoryItem *histItemById(const FullMsgId &msgId) {
return histItemById(msgId.channel, msgId.msg);
}
void historyRegItem(HistoryItem *item);
void historyItemDetached(HistoryItem *item);
void historyUnregItem(HistoryItem *item);
+ void historyUpdateDependent(HistoryItem *item);
void historyClearMsgs();
void historyClearItems();
- void historyRegReply(HistoryReply *reply, HistoryItem *to);
- void historyUnregReply(HistoryReply *reply, HistoryItem *to);
+ void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency);
+ void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency);
void historyRegRandom(uint64 randomId, const FullMsgId &itemId);
void historyUnregRandom(uint64 randomId);
@@ -202,9 +219,15 @@ namespace App {
bool isValidPhone(QString phone);
+ enum LaunchState {
+ Launched = 0,
+ QuitRequested = 1,
+ QuitProcessed = 2,
+ };
void quit();
- bool quiting();
- void setQuiting();
+ bool quitting();
+ LaunchState launchState();
+ void setLaunchState(LaunchState state);
QImage readImage(QByteArray data, QByteArray *format = 0, bool opaque = true, bool *animated = 0);
QImage readImage(const QString &file, QByteArray *format = 0, bool opaque = true, bool *animated = 0, QByteArray *content = 0);
@@ -214,16 +237,6 @@ namespace App {
const PhotoItems &photoItems();
const PhotosData &photosData();
- void regVideoItem(VideoData *data, HistoryItem *item);
- void unregVideoItem(VideoData *data, HistoryItem *item);
- const VideoItems &videoItems();
- const VideosData &videosData();
-
- void regAudioItem(AudioData *data, HistoryItem *item);
- void unregAudioItem(AudioData*data, HistoryItem *item);
- const AudioItems &audioItems();
- const AudiosData &audiosData();
-
void regDocumentItem(DocumentData *data, HistoryItem *item);
void unregDocumentItem(DocumentData *data, HistoryItem *item);
const DocumentItems &documentItems();
@@ -246,16 +259,10 @@ namespace App {
void unregMuted(PeerData *peer);
void updateMuted();
- void regInlineResultLoader(FileLoader *loader, InlineResult *result);
- void unregInlineResultLoader(FileLoader *loader);
- InlineResult *inlineResultFromLoader(FileLoader *loader);
-
- void feedReplyMarkup(ChannelId channelId, MsgId msgId, const MTPReplyMarkup &markup);
- void clearReplyMarkup(ChannelId channelId, MsgId msgId);
- const ReplyMarkup &replyMarkup(ChannelId channelId, MsgId msgId);
-
void setProxySettings(QNetworkAccessManager &manager);
+#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
QNetworkProxy getHttpProxySettings();
+#endif
void setProxySettings(QTcpSocket &socket);
QImage **cornersMask();
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index ca89de07c0..38180bfd19 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -16,11 +16,13 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "application.h"
-#include "style.h"
+#include "gui/style.h"
+
+#include "shortcuts.h"
#include "pspecific.h"
#include "fileuploader.h"
@@ -35,10 +37,6 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
#include "autoupdater.h"
namespace {
- Application *mainApp = 0;
- FileUploader *uploader = 0;
- QString lng;
-
void mtpStateChanged(int32 dc, int32 state) {
if (App::wnd()) {
App::wnd()->mtpStateChanged(dc, state);
@@ -51,545 +49,6 @@ namespace {
}
}
- class EventFilterForKeys : public QObject {
- public:
-
- EventFilterForKeys(QObject *parent) : QObject(parent) {
-
- }
- bool eventFilter(QObject *o, QEvent *e) {
- if (e->type() == QEvent::KeyPress) {
- QKeyEvent *ev = static_cast(e);
- if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
- if (ev->key() == Qt::Key_W && (ev->modifiers() & Qt::ControlModifier)) {
- if (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray) {
- App::wnd()->minimizeToTray();
- return true;
- } else {
- App::wnd()->hide();
- App::wnd()->updateIsActive(cOfflineBlurTimeout());
- App::wnd()->updateGlobalMenu();
- return true;
- }
- } else if (ev->key() == Qt::Key_M && (ev->modifiers() & Qt::ControlModifier)) {
- App::wnd()->setWindowState(Qt::WindowMinimized);
- return true;
- }
- }
- if (ev->key() == Qt::Key_MediaPlay) {
- if (App::main()) App::main()->player()->playPressed();
- } else if (ev->key() == Qt::Key_MediaPause) {
- if (App::main()) App::main()->player()->pausePressed();
- } else if (ev->key() == Qt::Key_MediaTogglePlayPause) {
- if (App::main()) App::main()->player()->playPausePressed();
- } else if (ev->key() == Qt::Key_MediaStop) {
- if (App::main()) App::main()->player()->stopPressed();
- } else if (ev->key() == Qt::Key_MediaPrevious) {
- if (App::main()) App::main()->player()->prevPressed();
- } else if (ev->key() == Qt::Key_MediaNext) {
- if (App::main()) App::main()->player()->nextPressed();
- }
- }
- return QObject::eventFilter(o, e);
- }
-
- };
-}
-
-Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
- serverName(psServerPrefix() + cGUIDStr()), closing(false),
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- updateRequestId(0), updateReply(0), updateThread(0), updateDownloader(0),
- #endif
- _translator(0) {
-
- DEBUG_LOG(("Application Info: creation.."));
-
- QByteArray d(QDir((cPlatform() == dbipWindows ? cExeDir() : cWorkingDir()).toLower()).absolutePath().toUtf8());
- char h[33] = { 0 };
- hashMd5Hex(d.constData(), d.size(), h);
- serverName = psServerPrefix() + h + '-' + cGUIDStr();
-
- if (mainApp) {
- DEBUG_LOG(("Application Error: another Application was created, terminating.."));
- exit(0);
- }
- mainApp = this;
-
- installEventFilter(new EventFilterForKeys(this));
-
- QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Regular.ttf"));
- QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Bold.ttf"));
- QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Semibold.ttf"));
-
- float64 dpi = primaryScreen()->logicalDotsPerInch();
- if (dpi <= 108) { // 0-96-108
- cSetScreenScale(dbisOne);
- } else if (dpi <= 132) { // 108-120-132
- cSetScreenScale(dbisOneAndQuarter);
- } else if (dpi <= 168) { // 132-144-168
- cSetScreenScale(dbisOneAndHalf);
- } else { // 168-192-inf
- cSetScreenScale(dbisTwo);
- }
-
- if (devicePixelRatio() > 1) {
- cSetRetina(true);
- cSetRetinaFactor(devicePixelRatio());
- cSetIntRetinaFactor(int32(cRetinaFactor()));
- cSetConfigScale(dbisOne);
- cSetRealScale(dbisOne);
- }
-
- if (cLang() < languageTest) {
- cSetLang(languageId());
- }
- if (cLang() == languageTest) {
- if (QFileInfo(cLangFile()).exists()) {
- LangLoaderPlain loader(cLangFile());
- cSetLangErrors(loader.errors());
- if (!cLangErrors().isEmpty()) {
- LOG(("Lang load errors: %1").arg(cLangErrors()));
- } else if (!loader.warnings().isEmpty()) {
- LOG(("Lang load warnings: %1").arg(loader.warnings()));
- }
- } else {
- cSetLang(languageDefault);
- }
- } else if (cLang() > languageDefault && cLang() < languageCount) {
- LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()] + qsl(".strings"));
- if (!loader.errors().isEmpty()) {
- LOG(("Lang load errors: %1").arg(loader.errors()));
- } else if (!loader.warnings().isEmpty()) {
- LOG(("Lang load warnings: %1").arg(loader.warnings()));
- }
- }
-
- installTranslator(_translator = new Translator());
-
- style::startManager();
- anim::startManager();
- historyInit();
-
- DEBUG_LOG(("Application Info: inited.."));
-
- window = new Window();
-
- psInstallEventFilter();
-
- connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected()));
- connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
- connect(&socket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(socketError(QLocalSocket::LocalSocketError)));
- connect(&socket, SIGNAL(bytesWritten(qint64)), this, SLOT(socketWritten(qint64)));
- connect(&socket, SIGNAL(readyRead()), this, SLOT(socketReading()));
- connect(&server, SIGNAL(newConnection()), this, SLOT(newInstanceConnected()));
- connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication()));
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- connect(&updateCheckTimer, SIGNAL(timeout()), this, SLOT(startUpdateCheck()));
- connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed()));
- connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady()));
- #endif
- connect(this, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onAppStateChanged(Qt::ApplicationState)));
-
- connect(&_mtpUnpauseTimer, SIGNAL(timeout()), this, SLOT(doMtpUnpause()));
-
- connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions()));
-
- if (cManyInstance()) {
- startApp();
- } else {
- DEBUG_LOG(("Application Info: connecting local socket to %1..").arg(serverName));
- socket.connectToServer(serverName);
- }
-}
-
-#ifndef TDESKTOP_DISABLE_AUTOUPDATE
-void Application::updateGotCurrent() {
- if (!updateReply || updateThread) return;
-
- cSetLastUpdateCheck(unixtime());
- QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromUtf8(updateReply->readAll()));
- if (m.hasMatch()) {
- uint64 currentVersion = m.captured(1).toULongLong();
- QString url = m.captured(2);
- bool betaVersion = false;
- if (url.startsWith(qstr("beta_"))) {
- betaVersion = true;
- url = url.mid(5) + '_' + countBetaVersionSignature(currentVersion);
- }
- if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) {
- updateThread = new QThread();
- connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater()));
- updateDownloader = new UpdateDownloader(updateThread, url);
- updateThread->start();
- }
- }
- if (updateReply) updateReply->deleteLater();
- updateReply = 0;
- if (!updateThread) {
- QDir updates(cWorkingDir() + "tupdates");
- if (updates.exists()) {
- QFileInfoList list = updates.entryInfoList(QDir::Files);
- for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
- if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
- QFile(i->absoluteFilePath()).remove();
- }
- }
- }
- emit updateLatest();
- }
- startUpdateCheck(true);
- Local::writeSettings();
-}
-
-void Application::updateFailedCurrent(QNetworkReply::NetworkError e) {
- LOG(("App Error: could not get current version (update check): %1").arg(e));
- if (updateReply) updateReply->deleteLater();
- updateReply = 0;
-
- emit updateFailed();
- startUpdateCheck(true);
-}
-
-void Application::onUpdateReady() {
- if (updateDownloader) {
- updateDownloader->deleteLater();
- updateDownloader = 0;
- }
- updateCheckTimer.stop();
-
- cSetLastUpdateCheck(unixtime());
- Local::writeSettings();
-}
-
-void Application::onUpdateFailed() {
- if (updateDownloader) {
- updateDownloader->deleteLater();
- updateDownloader = 0;
- if (updateThread) updateThread->quit();
- updateThread = 0;
- }
-
- cSetLastUpdateCheck(unixtime());
- Local::writeSettings();
-}
-#endif
-
-void Application::regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId) {
- photoUpdates.insert(msgId, peer);
-}
-
-void Application::clearPhotoUpdates() {
- photoUpdates.clear();
-}
-
-bool Application::isPhotoUpdating(const PeerId &peer) {
- for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e; ++i) {
- if (i.value() == peer) {
- return true;
- }
- }
- return false;
-}
-
-void Application::cancelPhotoUpdate(const PeerId &peer) {
- for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e;) {
- if (i.value() == peer) {
- i = photoUpdates.erase(i);
- } else {
- ++i;
- }
- }
-}
-
-void Application::mtpPause() {
- MTP::pause();
- _mtpUnpauseTimer.start(st::slideDuration * 2);
-}
-
-void Application::mtpUnpause() {
- _mtpUnpauseTimer.start(1);
-}
-
-void Application::doMtpUnpause() {
- MTP::unpause();
-}
-
-void Application::selfPhotoCleared(const MTPUserProfilePhoto &result) {
- if (!App::self()) return;
- App::self()->setPhoto(result);
- emit peerPhotoDone(App::self()->id);
-}
-
-void Application::chatPhotoCleared(PeerId peer, const MTPUpdates &updates) {
- if (App::main()) {
- App::main()->sentUpdatesReceived(updates);
- }
- cancelPhotoUpdate(peer);
- emit peerPhotoDone(peer);
-}
-
-void Application::selfPhotoDone(const MTPphotos_Photo &result) {
- if (!App::self()) return;
- const MTPDphotos_photo &photo(result.c_photos_photo());
- App::feedPhoto(photo.vphoto);
- App::feedUsers(photo.vusers);
- cancelPhotoUpdate(App::self()->id);
- emit peerPhotoDone(App::self()->id);
-}
-
-void Application::chatPhotoDone(PeerId peer, const MTPUpdates &updates) {
- if (App::main()) {
- App::main()->sentUpdatesReceived(updates);
- }
- cancelPhotoUpdate(peer);
- emit peerPhotoDone(peer);
-}
-
-bool Application::peerPhotoFail(PeerId peer, const RPCError &error) {
- if (mtpIsFlood(error)) return false;
-
- LOG(("Application Error: update photo failed %1: %2").arg(error.type()).arg(error.description()));
- cancelPhotoUpdate(peer);
- emit peerPhotoFail(peer);
- return true;
-}
-
-void Application::peerClearPhoto(PeerId id) {
- if (MTP::authedId() && peerToUser(id) == MTP::authedId()) {
- MTP::send(MTPphotos_UpdateProfilePhoto(MTP_inputPhotoEmpty(), MTP_inputPhotoCropAuto()), rpcDone(&Application::selfPhotoCleared), rpcFail(&Application::peerPhotoFail, id));
- } else if (peerIsChat(id)) {
- MTP::send(MTPmessages_EditChatPhoto(peerToBareMTPInt(id), MTP_inputChatPhotoEmpty()), rpcDone(&Application::chatPhotoCleared, id), rpcFail(&Application::peerPhotoFail, id));
- } else if (peerIsChannel(id)) {
- if (ChannelData *channel = App::channelLoaded(id)) {
- MTP::send(MTPchannels_EditPhoto(channel->inputChannel, MTP_inputChatPhotoEmpty()), rpcDone(&Application::chatPhotoCleared, id), rpcFail(&Application::peerPhotoFail, id));
- }
- }
-}
-
-void Application::killDownloadSessionsStart(int32 dc) {
- if (killDownloadSessionTimes.constFind(dc) == killDownloadSessionTimes.cend()) {
- killDownloadSessionTimes.insert(dc, getms() + MTPAckSendWaiting + MTPKillFileSessionTimeout);
- }
- if (!killDownloadSessionsTimer.isActive()) {
- killDownloadSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout + 5);
- }
-}
-
-void Application::killDownloadSessionsStop(int32 dc) {
- killDownloadSessionTimes.remove(dc);
- if (killDownloadSessionTimes.isEmpty() && killDownloadSessionsTimer.isActive()) {
- killDownloadSessionsTimer.stop();
- }
-}
-
-void Application::checkLocalTime() {
- if (App::main()) App::main()->checkLastUpdate(checkms());
-}
-
-void Application::onAppStateChanged(Qt::ApplicationState state) {
- checkLocalTime();
- if (window) window->updateIsActive((state == Qt::ApplicationActive) ? cOnlineFocusTimeout() : cOfflineBlurTimeout());
-}
-
-void Application::killDownloadSessions() {
- uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout;
- for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {
- if (i.value() <= ms) {
- for (int j = 0; j < MTPDownloadSessionsCount; ++j) {
- MTP::stopSession(MTP::dld[j] + i.key());
- }
- i = killDownloadSessionTimes.erase(i);
- } else {
- if (i.value() - ms < left) {
- left = i.value() - ms;
- }
- ++i;
- }
- }
- if (!killDownloadSessionTimes.isEmpty()) {
- killDownloadSessionsTimer.start(left);
- }
-}
-
-void Application::photoUpdated(const FullMsgId &msgId, const MTPInputFile &file) {
- if (!App::self()) return;
-
- QMap::iterator i = photoUpdates.find(msgId);
- if (i != photoUpdates.end()) {
- PeerId id = i.value();
- if (MTP::authedId() && peerToUser(id) == MTP::authedId()) {
- MTP::send(MTPphotos_UploadProfilePhoto(file, MTP_string(""), MTP_inputGeoPointEmpty(), MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100))), rpcDone(&Application::selfPhotoDone), rpcFail(&Application::peerPhotoFail, id));
- } else if (peerIsChat(id)) {
- History *hist = App::history(id);
- hist->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(hist->peer->asChat()->inputChat, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&Application::chatPhotoDone, id), rpcFail(&Application::peerPhotoFail, id), 0, 0, hist->sendRequestId);
- } else if (peerIsChannel(id)) {
- History *hist = App::history(id);
- hist->sendRequestId = MTP::send(MTPchannels_EditPhoto(hist->peer->asChannel()->inputChannel, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&Application::chatPhotoDone, id), rpcFail(&Application::peerPhotoFail, id), 0, 0, hist->sendRequestId);
- }
- }
-}
-
-void Application::onSwitchDebugMode() {
- if (cDebug()) {
- QFile(cWorkingDir() + qsl("tdata/withdebug")).remove();
- cSetDebug(false);
- cSetRestarting(true);
- cSetRestartingToSettings(true);
- App::quit();
- } else {
- logsInitDebug();
- cSetDebug(true);
- QFile f(cWorkingDir() + qsl("tdata/withdebug"));
- if (f.open(QIODevice::WriteOnly)) {
- f.write("1");
- f.close();
- }
- Ui::hideLayer();
- }
-}
-
-void Application::onSwitchTestMode() {
- if (cTestMode()) {
- QFile(cWorkingDir() + qsl("tdata/withtestmode")).remove();
- cSetTestMode(false);
- } else {
- QFile f(cWorkingDir() + qsl("tdata/withtestmode"));
- if (f.open(QIODevice::WriteOnly)) {
- f.write("1");
- f.close();
- }
- cSetTestMode(true);
- }
- cSetRestarting(true);
- cSetRestartingToSettings(true);
- App::quit();
-}
-
-Application::UpdatingState Application::updatingState() {
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- if (!updateThread) return Application::UpdatingNone;
- if (!updateDownloader) return Application::UpdatingReady;
- return Application::UpdatingDownload;
- #else
- return Application::UpdatingNone;
- #endif
-}
-
-#ifndef TDESKTOP_DISABLE_AUTOUPDATE
-int32 Application::updatingSize() {
- if (!updateDownloader) return 0;
- return updateDownloader->size();
-}
-
-int32 Application::updatingReady() {
- if (!updateDownloader) return 0;
- return updateDownloader->ready();
-}
-#endif
-
-FileUploader *Application::uploader() {
- if (!::uploader && !App::quiting()) ::uploader = new FileUploader();
- return ::uploader;
-}
-
-void Application::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) {
- PreparedPhotoThumbs photoThumbs;
- QVector photoSizes;
-
- QPixmap thumb = QPixmap::fromImage(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
- photoThumbs.insert('a', thumb);
- photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0)));
-
- QPixmap medium = QPixmap::fromImage(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
- photoThumbs.insert('b', medium);
- photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
-
- QPixmap full = QPixmap::fromImage(tosend, Qt::ColorOnly);
- photoThumbs.insert('c', full);
- photoSizes.push_back(MTP_photoSize(MTP_string("c"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)));
-
- QByteArray jpeg;
- QBuffer jpegBuffer(&jpeg);
- full.save(&jpegBuffer, "JPG", 87);
-
- PhotoId id = MTP::nonce();
-
- MTPPhoto photo(MTP_photo(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes)));
-
- QString file, filename;
- int32 filesize = 0;
- QByteArray data;
-
- ReadyLocalMedia ready(PreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, MTP_audioEmpty(MTP_long(0)), photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, false, 0);
-
- connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&, const MTPInputFile&)), App::app(), SLOT(photoUpdated(const FullMsgId&, const MTPInputFile&)), Qt::UniqueConnection);
-
- FullMsgId newId(peerToChannel(peerId), clientMsgId());
- App::app()->regPhotoUpdate(peerId, newId);
- App::uploader()->uploadMedia(newId, ready);
-}
-
-#ifndef TDESKTOP_DISABLE_AUTOUPDATE
-void Application::stopUpdate() {
- if (updateReply) {
- updateReply->abort();
- updateReply->deleteLater();
- updateReply = 0;
- }
- if (updateDownloader) {
- updateDownloader->deleteLater();
- updateDownloader = 0;
- if (updateThread) updateThread->quit();
- updateThread = 0;
- }
-}
-
-void Application::startUpdateCheck(bool forceWait) {
- updateCheckTimer.stop();
- if (updateRequestId || updateThread || updateReply || !cAutoUpdate()) return;
-
- int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart;
- int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(MTP::nonce() % randDelay) - unixtime();
- bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay));
- if (!sendRequest && !forceWait) {
- QDir updates(cWorkingDir() + "tupdates");
- if (updates.exists()) {
- QFileInfoList list = updates.entryInfoList(QDir::Files);
- for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
- if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
- sendRequest = true;
- }
- }
- }
- }
- if (cManyInstance() && !cDebug()) return; // only main instance is updating
-
- if (sendRequest) {
- QUrl url(cUpdateURL());
- if (cBetaVersion()) {
- url.setQuery(qsl("version=%1&beta=%2").arg(AppVersion).arg(cBetaVersion()));
- } else if (cDevVersion()) {
- url.setQuery(qsl("version=%1&dev=1").arg(AppVersion));
- } else {
- url.setQuery(qsl("version=%1").arg(AppVersion));
- }
- QString u = url.toString();
- QNetworkRequest checkVersion(url);
- if (updateReply) updateReply->deleteLater();
-
- App::setProxySettings(updateManager);
- updateReply = updateManager.get(checkVersion);
- connect(updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent()));
- connect(updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError)));
- emit updateChecking();
- } else {
- updateCheckTimer.start((updateInSecs + 5) * 1000);
- }
-}
-#endif
-
-namespace {
QChar _toHex(ushort v) {
v = v & 0x000F;
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
@@ -629,9 +88,50 @@ namespace {
}
}
+AppClass *AppObject = 0;
+
+Application::Application(int &argc, char **argv) : QApplication(argc, argv)
+, _secondInstance(false)
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
+, _updateReply(0)
+, _updateThread(0)
+, _updateChecker(0)
+#endif
+{
+ QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath()));
+ char h[33] = { 0 };
+ hashMd5Hex(d.constData(), d.size(), h);
+ _localServerName = psServerPrefix() + h + '-' + cGUIDStr();
+
+ connect(&_localSocket, SIGNAL(connected()), this, SLOT(socketConnected()));
+ connect(&_localSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
+ connect(&_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(socketError(QLocalSocket::LocalSocketError)));
+ connect(&_localSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(socketWritten(qint64)));
+ connect(&_localSocket, SIGNAL(readyRead()), this, SLOT(socketReading()));
+ connect(&_localServer, SIGNAL(newConnection()), this, SLOT(newInstanceConnected()));
+
+ QTimer::singleShot(0, this, SLOT(startApplication()));
+ connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication()));
+
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
+ connect(&_updateCheckTimer, SIGNAL(timeout()), this, SLOT(updateCheck()));
+ connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed()));
+ connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady()));
+#endif
+
+ if (cManyInstance()) {
+ LOG(("Many instance allowed, starting..."));
+ singleInstanceChecked();
+ } else {
+ LOG(("Connecting local socket to %1...").arg(_localServerName));
+ _localSocket.connectToServer(_localServerName);
+ }
+}
+
void Application::socketConnected() {
- DEBUG_LOG(("Application Info: socket connected, this is not the first application instance, sending show command.."));
- closing = true;
+ LOG(("Socket connected, this is not the first application instance, sending show command..."));
+ _secondInstance = true;
+
QString commands;
const QStringList &lst(cSendPaths());
for (QStringList::const_iterator i = lst.cbegin(), e = lst.cend(); i != e; ++i) {
@@ -641,154 +141,109 @@ void Application::socketConnected() {
commands += qsl("OPEN:") + _escapeTo7bit(cStartUrl()) + ';';
}
commands += qsl("CMD:show;");
+
DEBUG_LOG(("Application Info: writing commands %1").arg(commands));
- socket.write(commands.toLocal8Bit());
+ _localSocket.write(commands.toLatin1());
}
void Application::socketWritten(qint64/* bytes*/) {
- if (socket.state() != QLocalSocket::ConnectedState) {
- DEBUG_LOG(("Application Error: socket is not connected %1").arg(socket.state()));
+ if (_localSocket.state() != QLocalSocket::ConnectedState) {
+ LOG(("Socket is not connected %1").arg(_localSocket.state()));
return;
}
- if (socket.bytesToWrite()) {
+ if (_localSocket.bytesToWrite()) {
return;
}
- DEBUG_LOG(("Application Info: show command written, waiting response.."));
+ LOG(("Show command written, waiting response..."));
}
void Application::socketReading() {
- if (socket.state() != QLocalSocket::ConnectedState) {
- DEBUG_LOG(("Application Error: socket is not connected %1").arg(socket.state()));
+ if (_localSocket.state() != QLocalSocket::ConnectedState) {
+ LOG(("Socket is not connected %1").arg(_localSocket.state()));
return;
}
- socketRead.append(socket.readAll());
- if (QRegularExpression("RES:(\\d+);").match(socketRead).hasMatch()) {
- uint64 pid = socketRead.mid(4, socketRead.length() - 5).toULongLong();
+ _localSocketReadData.append(_localSocket.readAll());
+ if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) {
+ uint64 pid = _localSocketReadData.mid(4, _localSocketReadData.length() - 5).toULongLong();
psActivateProcess(pid);
- DEBUG_LOG(("Application Info: show command response received, pid = %1, activating and quiting..").arg(pid));
+ LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid));
return App::quit();
}
}
void Application::socketError(QLocalSocket::LocalSocketError e) {
- if (closing) {
- DEBUG_LOG(("Application Error: could not write show command, error %1, quiting..").arg(e));
+ if (App::quitting()) return;
+
+ if (_secondInstance) {
+ LOG(("Could not write show command, error %1, quitting...").arg(e));
return App::quit();
}
if (e == QLocalSocket::ServerNotFoundError) {
- DEBUG_LOG(("Application Info: this is the only instance of Telegram, starting server and app.."));
+ LOG(("This is the only instance of Telegram, starting server and app..."));
} else {
- DEBUG_LOG(("Application Info: socket connect error %1, starting server and app..").arg(e));
+ LOG(("Socket connect error %1, starting server and app...").arg(e));
}
- socket.close();
+ _localSocket.close();
- psCheckLocalSocket(serverName);
+ psCheckLocalSocket(_localServerName);
- if (!server.listen(serverName)) {
- DEBUG_LOG(("Application Error: failed to start listening to %1 server, error %2").arg(serverName).arg(int(server.serverError())));
+ if (!_localServer.listen(_localServerName)) {
+ LOG(("Failed to start listening to %1 server, error %2").arg(_localServerName).arg(int(_localServer.serverError())));
return App::quit();
}
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (!cNoStartUpdate() && checkReadyUpdate()) {
cSetRestartingUpdate(true);
- DEBUG_LOG(("Application Info: installing update instead of starting app.."));
+ DEBUG_LOG(("Application Info: installing update instead of starting app..."));
return App::quit();
}
- #endif
+#endif
- startApp();
+ singleInstanceChecked();
}
-void Application::checkMapVersion() {
- if (Local::oldMapVersion() < AppVersion) {
- if (Local::oldMapVersion()) {
- QString versionFeatures;
- if (cDevVersion() && Local::oldMapVersion() < 9016) {
-// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D));
- versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed();
- } else if (Local::oldMapVersion() < 9016) {
- versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed();
+void Application::singleInstanceChecked() {
+ if (cManyInstance()) {
+ Logs::multipleInstances();
+ }
+
+ Sandbox::start();
+
+ if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
+ new NotStartedWindow();
+ } else {
+ SignalHandlers::Status status = SignalHandlers::start();
+ if (status == SignalHandlers::CantOpen) {
+ new NotStartedWindow();
+ } else if (status == SignalHandlers::LastCrashed) {
+ if (Sandbox::LastCrashDump().isEmpty()) { // don't handle bad closing for now
+ if (SignalHandlers::restart() == SignalHandlers::CantOpen) {
+ new NotStartedWindow();
+ } else {
+ Sandbox::launch();
+ }
} else {
- versionFeatures = lang(lng_new_version_minor).trimmed();
+ new LastCrashedWindow();
}
- if (!versionFeatures.isEmpty()) {
- versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog"));
- window->serviceNotification(versionFeatures);
- }
- }
- }
- if (cNeedConfigResave()) {
- Local::writeUserSettings();
- }
-}
-
-void Application::startApp() {
- cChangeTimeFormat(QLocale::system().timeFormat(QLocale::ShortFormat));
-
- DEBUG_LOG(("Application Info: starting app.."));
-
- QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database
-
- window->createWinId();
- window->init();
-
- DEBUG_LOG(("Application Info: window created.."));
-
- initImageLinkManager();
- App::initMedia();
-
- Local::ReadMapState state = Local::readMap(QByteArray());
- if (state == Local::ReadMapPassNeeded) {
- cSetHasPasscode(true);
- DEBUG_LOG(("Application Info: passcode nneded.."));
- } else {
- DEBUG_LOG(("Application Info: local map read.."));
- MTP::start();
- }
-
- MTP::setStateChangedHandler(mtpStateChanged);
- MTP::setSessionResetHandler(mtpSessionReset);
-
- DEBUG_LOG(("Application Info: MTP started.."));
-
- DEBUG_LOG(("Application Info: showing."));
- if (state == Local::ReadMapPassNeeded) {
- window->setupPasscode(false);
- } else {
- if (MTP::authedId()) {
- window->setupMain(false);
} else {
- window->setupIntro(false);
+ Sandbox::launch();
}
}
- window->firstShow();
-
- if (cStartToSettings()) {
- window->showSettings();
- }
-
- QNetworkProxyFactory::setUseSystemConfiguration(true);
-
- if (state != Local::ReadMapPassNeeded) {
- checkMapVersion();
- }
-
- window->updateIsActive(cOnlineFocusTimeout());
}
void Application::socketDisconnected() {
- if (closing) {
- DEBUG_LOG(("Application Error: socket disconnected before command response received, quiting.."));
+ if (_secondInstance) {
+ DEBUG_LOG(("Application Error: socket disconnected before command response received, quitting..."));
return App::quit();
}
}
void Application::newInstanceConnected() {
DEBUG_LOG(("Application Info: new local socket connected"));
- for (QLocalSocket *client = server.nextPendingConnection(); client; client = server.nextPendingConnection()) {
- clients.push_back(ClientSocket(client, QByteArray()));
+ for (QLocalSocket *client = _localServer.nextPendingConnection(); client; client = _localServer.nextPendingConnection()) {
+ _localClients.push_back(LocalClient(client, QByteArray()));
connect(client, SIGNAL(readyRead()), this, SLOT(readClients()));
connect(client, SIGNAL(disconnected()), this, SLOT(removeClients()));
}
@@ -797,16 +252,16 @@ void Application::newInstanceConnected() {
void Application::readClients() {
QString startUrl;
QStringList toSend;
- for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e; ++i) {
+ for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) {
i->second.append(i->first->readAll());
if (i->second.size()) {
- QString cmds(QString::fromLocal8Bit(i->second));
+ QString cmds(QString::fromLatin1(i->second));
int32 from = 0, l = cmds.length();
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
QStringRef cmd(&cmds, from, to - from);
if (cmd.startsWith(qsl("CMD:"))) {
- execExternal(cmds.mid(from + 4, to - from - 4));
- QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toUtf8());
+ Sandbox::execExternal(cmds.mid(from + 4, to - from - 4));
+ QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toLatin1());
i->first->write(response.data(), response.size());
} else if (cmd.startsWith(qsl("SEND:"))) {
if (cSendPaths().isEmpty()) {
@@ -846,65 +301,764 @@ void Application::readClients() {
}
void Application::removeClients() {
- DEBUG_LOG(("Application Info: remove clients slot called, clients %1").arg(clients.size()));
- for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e;) {
+ DEBUG_LOG(("Application Info: remove clients slot called, clients %1").arg(_localClients.size()));
+ for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e;) {
if (i->first->state() != QLocalSocket::ConnectedState) {
DEBUG_LOG(("Application Info: removing client"));
- i = clients.erase(i);
- e = clients.end();
+ i = _localClients.erase(i);
+ e = _localClients.end();
} else {
++i;
}
}
}
-void Application::execExternal(const QString &cmd) {
- DEBUG_LOG(("Application Info: executing external command '%1'").arg(cmd));
- if (cmd == "show") {
- window->activate();
+void Application::startApplication() {
+ if (App::quitting()) {
+ quit();
}
}
void Application::closeApplication() {
- // close server
- server.close();
- for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e; ++i) {
+ if (App::launchState() == App::QuitProcessed) return;
+ App::setLaunchState(App::QuitProcessed);
+
+ delete AppObject;
+ AppObject = 0;
+
+ Sandbox::finish();
+
+ _localServer.close();
+ for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) {
disconnect(i->first, SIGNAL(disconnected()), this, SLOT(removeClients()));
i->first->close();
}
- clients.clear();
+ _localClients.clear();
- MTP::stop();
+ _localSocket.close();
+
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
+ delete _updateReply;
+ _updateReply = 0;
+ if (_updateChecker) _updateChecker->deleteLater();
+ _updateChecker = 0;
+ if (_updateThread) _updateThread->quit();
+ _updateThread = 0;
+#endif
}
-Application::~Application() {
- App::setQuiting();
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
+void Application::updateCheck() {
+ startUpdateCheck(false);
+}
- window->setParent(0);
+void Application::updateGotCurrent() {
+ if (!_updateReply || _updateThread) return;
+ cSetLastUpdateCheck(unixtime());
+ QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromLatin1(_updateReply->readAll()));
+ if (m.hasMatch()) {
+ uint64 currentVersion = m.captured(1).toULongLong();
+ QString url = m.captured(2);
+ bool betaVersion = false;
+ if (url.startsWith(qstr("beta_"))) {
+ betaVersion = true;
+ url = url.mid(5) + '_' + countBetaVersionSignature(currentVersion);
+ }
+ if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) {
+ _updateThread = new QThread();
+ connect(_updateThread, SIGNAL(finished()), _updateThread, SLOT(deleteLater()));
+ _updateChecker = new UpdateChecker(_updateThread, url);
+ _updateThread->start();
+ }
+ }
+ if (_updateReply) _updateReply->deleteLater();
+ _updateReply = 0;
+ if (!_updateThread) {
+ QDir updates(cWorkingDir() + "tupdates");
+ if (updates.exists()) {
+ QFileInfoList list = updates.entryInfoList(QDir::Files);
+ for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
+ if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
+ QFile(i->absoluteFilePath()).remove();
+ }
+ }
+ }
+ emit updateLatest();
+ }
+ startUpdateCheck(true);
+ Local::writeSettings();
+}
+
+void Application::updateFailedCurrent(QNetworkReply::NetworkError e) {
+ LOG(("App Error: could not get current version (update check): %1").arg(e));
+ if (_updateReply) _updateReply->deleteLater();
+ _updateReply = 0;
+
+ emit updateFailed();
+ startUpdateCheck(true);
+}
+
+void Application::onUpdateReady() {
+ if (_updateChecker) {
+ _updateChecker->deleteLater();
+ _updateChecker = 0;
+ }
+ _updateCheckTimer.stop();
+
+ cSetLastUpdateCheck(unixtime());
+ Local::writeSettings();
+}
+
+void Application::onUpdateFailed() {
+ if (_updateChecker) {
+ _updateChecker->deleteLater();
+ _updateChecker = 0;
+ if (_updateThread) _updateThread->quit();
+ _updateThread = 0;
+ }
+
+ cSetLastUpdateCheck(unixtime());
+ Local::writeSettings();
+}
+
+Application::UpdatingState Application::updatingState() {
+ if (!_updateThread) return Application::UpdatingNone;
+ if (!_updateChecker) return Application::UpdatingReady;
+ return Application::UpdatingDownload;
+}
+
+int32 Application::updatingSize() {
+ if (!_updateChecker) return 0;
+ return _updateChecker->size();
+}
+
+int32 Application::updatingReady() {
+ if (!_updateChecker) return 0;
+ return _updateChecker->ready();
+}
+
+void Application::stopUpdate() {
+ if (_updateReply) {
+ _updateReply->abort();
+ _updateReply->deleteLater();
+ _updateReply = 0;
+ }
+ if (_updateChecker) {
+ _updateChecker->deleteLater();
+ _updateChecker = 0;
+ if (_updateThread) _updateThread->quit();
+ _updateThread = 0;
+ }
+}
+
+void Application::startUpdateCheck(bool forceWait) {
+ _updateCheckTimer.stop();
+ if (_updateThread || _updateReply || !cAutoUpdate()) return;
+
+ int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart;
+ int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(rand() % randDelay) - unixtime();
+ bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay));
+ if (!sendRequest && !forceWait) {
+ QDir updates(cWorkingDir() + "tupdates");
+ if (updates.exists()) {
+ QFileInfoList list = updates.entryInfoList(QDir::Files);
+ for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
+ if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
+ sendRequest = true;
+ }
+ }
+ }
+ }
+ if (cManyInstance() && !cDebug()) return; // only main instance is updating
+
+ if (sendRequest) {
+ QUrl url(cUpdateURL());
+ if (cBetaVersion()) {
+ url.setQuery(qsl("version=%1&beta=%2").arg(AppVersion).arg(cBetaVersion()));
+ } else if (cDevVersion()) {
+ url.setQuery(qsl("version=%1&dev=1").arg(AppVersion));
+ } else {
+ url.setQuery(qsl("version=%1").arg(AppVersion));
+ }
+ QString u = url.toString();
+ QNetworkRequest checkVersion(url);
+ if (_updateReply) _updateReply->deleteLater();
+
+ App::setProxySettings(_updateManager);
+ _updateReply = _updateManager.get(checkVersion);
+ connect(_updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent()));
+ connect(_updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError)));
+ emit updateChecking();
+ } else {
+ _updateCheckTimer.start((updateInSecs + 5) * 1000);
+ }
+}
+
+#endif
+
+inline Application *application() {
+ return qobject_cast(QApplication::instance());
+}
+
+namespace Sandbox {
+
+ QRect availableGeometry() {
+ if (Application *a = application()) {
+ return a->desktop()->availableGeometry();
+ }
+ return QDesktopWidget().availableGeometry();
+ }
+
+ QRect screenGeometry(const QPoint &p) {
+ if (Application *a = application()) {
+ return a->desktop()->screenGeometry(p);
+ }
+ return QDesktopWidget().screenGeometry(p);
+ }
+
+ void setActiveWindow(QWidget *window) {
+ if (Application *a = application()) {
+ a->setActiveWindow(window);
+ }
+ }
+
+ bool isSavingSession() {
+ if (Application *a = application()) {
+ return a->isSavingSession();
+ }
+ return false;
+ }
+
+ void installEventFilter(QObject *filter) {
+ if (Application *a = application()) {
+ a->installEventFilter(filter);
+ }
+ }
+
+ void execExternal(const QString &cmd) {
+ DEBUG_LOG(("Application Info: executing external command '%1'").arg(cmd));
+ if (cmd == "show") {
+ if (App::wnd()) {
+ App::wnd()->activate();
+ } else if (PreLaunchWindow::instance()) {
+ PreLaunchWindow::instance()->activate();
+ }
+ }
+ }
+
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
+
+ void startUpdateCheck() {
+ if (Application *a = application()) {
+ return a->startUpdateCheck(false);
+ }
+ }
+
+ void stopUpdate() {
+ if (Application *a = application()) {
+ return a->stopUpdate();
+ }
+ }
+
+ Application::UpdatingState updatingState() {
+ if (Application *a = application()) {
+ return a->updatingState();
+ }
+ return Application::UpdatingNone;
+ }
+
+ int32 updatingSize() {
+ if (Application *a = application()) {
+ return a->updatingSize();
+ }
+ return 0;
+ }
+
+ int32 updatingReady() {
+ if (Application *a = application()) {
+ return a->updatingReady();
+ }
+ return 0;
+ }
+
+ void updateChecking() {
+ if (Application *a = application()) {
+ emit a->updateChecking();
+ }
+ }
+
+ void updateLatest() {
+ if (Application *a = application()) {
+ emit a->updateLatest();
+ }
+ }
+
+ void updateProgress(qint64 ready, qint64 total) {
+ if (Application *a = application()) {
+ emit a->updateProgress(ready, total);
+ }
+ }
+
+ void updateFailed() {
+ if (Application *a = application()) {
+ emit a->updateFailed();
+ }
+ }
+
+ void updateReady() {
+ if (Application *a = application()) {
+ emit a->updateReady();
+ }
+ }
+
+#endif
+
+ void connect(const char *signal, QObject *object, const char *method) {
+ if (Application *a = application()) {
+ a->connect(a, signal, object, method);
+ }
+ }
+
+ void launch() {
+ t_assert(application() != 0);
+
+ float64 dpi = Application::primaryScreen()->logicalDotsPerInch();
+ if (dpi <= 108) { // 0-96-108
+ cSetScreenScale(dbisOne);
+ } else if (dpi <= 132) { // 108-120-132
+ cSetScreenScale(dbisOneAndQuarter);
+ } else if (dpi <= 168) { // 132-144-168
+ cSetScreenScale(dbisOneAndHalf);
+ } else { // 168-192-inf
+ cSetScreenScale(dbisTwo);
+ }
+
+ if (application()->devicePixelRatio() > 1) {
+ cSetRetina(true);
+ cSetRetinaFactor(application()->devicePixelRatio());
+ cSetIntRetinaFactor(int32(cRetinaFactor()));
+ cSetConfigScale(dbisOne);
+ cSetRealScale(dbisOne);
+ }
+
+ new AppClass();
+ }
+
+}
+
+AppClass::AppClass() : QObject()
+, _lastActionTime(0)
+, _window(0)
+, _uploader(0)
+, _translator(0) {
+ AppObject = this;
+
+ Fonts::start();
+
+ ThirdParty::start();
+ Global::start();
+ Local::start();
+ if (Local::oldSettingsVersion() < AppVersion) {
+ psNewVersion();
+ }
+
+ if (cLaunchMode() == LaunchModeAutoStart && !cAutoStart()) {
+ psAutoStart(false, true);
+ application()->quit();
+ return;
+ }
+
+ if (cRetina()) {
+ cSetConfigScale(dbisOne);
+ cSetRealScale(dbisOne);
+ }
+
+ if (cLang() < languageTest) {
+ cSetLang(Sandbox::LangSystem());
+ }
+ if (cLang() == languageTest) {
+ if (QFileInfo(cLangFile()).exists()) {
+ LangLoaderPlain loader(cLangFile());
+ cSetLangErrors(loader.errors());
+ if (!cLangErrors().isEmpty()) {
+ LOG(("Lang load errors: %1").arg(cLangErrors()));
+ } else if (!loader.warnings().isEmpty()) {
+ LOG(("Lang load warnings: %1").arg(loader.warnings()));
+ }
+ } else {
+ cSetLang(languageDefault);
+ }
+ } else if (cLang() > languageDefault && cLang() < languageCount) {
+ LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings"));
+ if (!loader.errors().isEmpty()) {
+ LOG(("Lang load errors: %1").arg(loader.errors()));
+ } else if (!loader.warnings().isEmpty()) {
+ LOG(("Lang load warnings: %1").arg(loader.warnings()));
+ }
+ }
+
+ application()->installTranslator(_translator = new Translator());
+
+ style::startManager();
+ anim::startManager();
+ historyInit();
+
+ DEBUG_LOG(("Application Info: inited..."));
+
+ application()->installNativeEventFilter(psNativeEventFilter());
+
+ cChangeTimeFormat(QLocale::system().timeFormat(QLocale::ShortFormat));
+
+ connect(&_mtpUnpauseTimer, SIGNAL(timeout()), this, SLOT(doMtpUnpause()));
+
+ connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions()));
+
+ DEBUG_LOG(("Application Info: starting app..."));
+
+ QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database
+
+ _window = new Window();
+ _window->createWinId();
+ _window->init();
+
+ Sandbox::connect(SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onAppStateChanged(Qt::ApplicationState)));
+
+ DEBUG_LOG(("Application Info: window created..."));
+
+ Shortcuts::start();
+
+ initImageLinkManager();
+ App::initMedia();
+
+ Local::ReadMapState state = Local::readMap(QByteArray());
+ if (state == Local::ReadMapPassNeeded) {
+ cSetHasPasscode(true);
+ DEBUG_LOG(("Application Info: passcode needed..."));
+ } else {
+ DEBUG_LOG(("Application Info: local map read..."));
+ MTP::start();
+ }
+
+ MTP::setStateChangedHandler(mtpStateChanged);
+ MTP::setSessionResetHandler(mtpSessionReset);
+
+ DEBUG_LOG(("Application Info: MTP started..."));
+
+ DEBUG_LOG(("Application Info: showing."));
+ if (state == Local::ReadMapPassNeeded) {
+ _window->setupPasscode(false);
+ } else {
+ if (MTP::authedId()) {
+ _window->setupMain(false);
+ } else {
+ _window->setupIntro(false);
+ }
+ }
+ _window->firstShow();
+
+ if (cStartToSettings()) {
+ _window->showSettings();
+ }
+
+#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
+ QNetworkProxyFactory::setUseSystemConfiguration(true);
+#endif
+
+ if (state != Local::ReadMapPassNeeded) {
+ checkMapVersion();
+ }
+
+ _window->updateIsActive(Global::OnlineFocusTimeout());
+
+ if (!Shortcuts::errors().isEmpty()) {
+ const QStringList &errors(Shortcuts::errors());
+ for (QStringList::const_iterator i = errors.cbegin(), e = errors.cend(); i != e; ++i) {
+ LOG(("Shortcuts Error: %1").arg(*i));
+ }
+ }
+}
+
+void AppClass::regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId) {
+ photoUpdates.insert(msgId, peer);
+}
+
+void AppClass::clearPhotoUpdates() {
+ photoUpdates.clear();
+}
+
+bool AppClass::isPhotoUpdating(const PeerId &peer) {
+ for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e; ++i) {
+ if (i.value() == peer) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void AppClass::cancelPhotoUpdate(const PeerId &peer) {
+ for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e;) {
+ if (i.value() == peer) {
+ i = photoUpdates.erase(i);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void AppClass::mtpPause() {
+ MTP::pause();
+ _mtpUnpauseTimer.start(st::slideDuration * 2);
+}
+
+void AppClass::mtpUnpause() {
+ _mtpUnpauseTimer.start(1);
+}
+
+void AppClass::doMtpUnpause() {
+ MTP::unpause();
+}
+
+void AppClass::selfPhotoCleared(const MTPUserProfilePhoto &result) {
+ if (!App::self()) return;
+ App::self()->setPhoto(result);
+ emit peerPhotoDone(App::self()->id);
+}
+
+void AppClass::chatPhotoCleared(PeerId peer, const MTPUpdates &updates) {
+ if (App::main()) {
+ App::main()->sentUpdatesReceived(updates);
+ }
+ cancelPhotoUpdate(peer);
+ emit peerPhotoDone(peer);
+}
+
+void AppClass::selfPhotoDone(const MTPphotos_Photo &result) {
+ if (!App::self()) return;
+ const MTPDphotos_photo &photo(result.c_photos_photo());
+ App::feedPhoto(photo.vphoto);
+ App::feedUsers(photo.vusers);
+ cancelPhotoUpdate(App::self()->id);
+ emit peerPhotoDone(App::self()->id);
+}
+
+void AppClass::chatPhotoDone(PeerId peer, const MTPUpdates &updates) {
+ if (App::main()) {
+ App::main()->sentUpdatesReceived(updates);
+ }
+ cancelPhotoUpdate(peer);
+ emit peerPhotoDone(peer);
+}
+
+bool AppClass::peerPhotoFail(PeerId peer, const RPCError &error) {
+ if (mtpIsFlood(error)) return false;
+
+ LOG(("Application Error: update photo failed %1: %2").arg(error.type()).arg(error.description()));
+ cancelPhotoUpdate(peer);
+ emit peerPhotoFail(peer);
+ return true;
+}
+
+void AppClass::peerClearPhoto(PeerId id) {
+ if (MTP::authedId() && peerToUser(id) == MTP::authedId()) {
+ MTP::send(MTPphotos_UpdateProfilePhoto(MTP_inputPhotoEmpty(), MTP_inputPhotoCropAuto()), rpcDone(&AppClass::selfPhotoCleared), rpcFail(&AppClass::peerPhotoFail, id));
+ } else if (peerIsChat(id)) {
+ MTP::send(MTPmessages_EditChatPhoto(peerToBareMTPInt(id), MTP_inputChatPhotoEmpty()), rpcDone(&AppClass::chatPhotoCleared, id), rpcFail(&AppClass::peerPhotoFail, id));
+ } else if (peerIsChannel(id)) {
+ if (ChannelData *channel = App::channelLoaded(id)) {
+ MTP::send(MTPchannels_EditPhoto(channel->inputChannel, MTP_inputChatPhotoEmpty()), rpcDone(&AppClass::chatPhotoCleared, id), rpcFail(&AppClass::peerPhotoFail, id));
+ }
+ }
+}
+
+void AppClass::killDownloadSessionsStart(int32 dc) {
+ if (killDownloadSessionTimes.constFind(dc) == killDownloadSessionTimes.cend()) {
+ killDownloadSessionTimes.insert(dc, getms() + MTPAckSendWaiting + MTPKillFileSessionTimeout);
+ }
+ if (!killDownloadSessionsTimer.isActive()) {
+ killDownloadSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout + 5);
+ }
+}
+
+void AppClass::killDownloadSessionsStop(int32 dc) {
+ killDownloadSessionTimes.remove(dc);
+ if (killDownloadSessionTimes.isEmpty() && killDownloadSessionsTimer.isActive()) {
+ killDownloadSessionsTimer.stop();
+ }
+}
+
+void AppClass::checkLocalTime() {
+ if (App::main()) App::main()->checkLastUpdate(checkms());
+}
+
+void AppClass::onAppStateChanged(Qt::ApplicationState state) {
+ checkLocalTime();
+ if (_window) {
+ _window->updateIsActive((state == Qt::ApplicationActive) ? Global::OnlineFocusTimeout() : Global::OfflineBlurTimeout());
+ }
+ if (state != Qt::ApplicationActive) {
+ PopupTooltip::Hide();
+ }
+}
+
+void AppClass::call_handleHistoryUpdate() {
+ Notify::handlePendingHistoryUpdate();
+}
+
+void AppClass::killDownloadSessions() {
+ uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout;
+ for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {
+ if (i.value() <= ms) {
+ for (int j = 0; j < MTPDownloadSessionsCount; ++j) {
+ MTP::stopSession(MTP::dldDcId(i.key(), j));
+ }
+ i = killDownloadSessionTimes.erase(i);
+ } else {
+ if (i.value() - ms < left) {
+ left = i.value() - ms;
+ }
+ ++i;
+ }
+ }
+ if (!killDownloadSessionTimes.isEmpty()) {
+ killDownloadSessionsTimer.start(left);
+ }
+}
+
+void AppClass::photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file) {
+ if (!App::self()) return;
+
+ QMap::iterator i = photoUpdates.find(msgId);
+ if (i != photoUpdates.end()) {
+ PeerId id = i.value();
+ if (MTP::authedId() && peerToUser(id) == MTP::authedId()) {
+ MTP::send(MTPphotos_UploadProfilePhoto(file, MTP_string(""), MTP_inputGeoPointEmpty(), MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100))), rpcDone(&AppClass::selfPhotoDone), rpcFail(&AppClass::peerPhotoFail, id));
+ } else if (peerIsChat(id)) {
+ History *hist = App::history(id);
+ hist->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(hist->peer->asChat()->inputChat, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, hist->sendRequestId);
+ } else if (peerIsChannel(id)) {
+ History *hist = App::history(id);
+ hist->sendRequestId = MTP::send(MTPchannels_EditPhoto(hist->peer->asChannel()->inputChannel, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, hist->sendRequestId);
+ }
+ }
+}
+
+void AppClass::onSwitchDebugMode() {
+ if (cDebug()) {
+ QFile(cWorkingDir() + qsl("tdata/withdebug")).remove();
+ cSetDebug(false);
+ cSetRestarting(true);
+ cSetRestartingToSettings(true);
+ App::quit();
+ } else {
+ cSetDebug(true);
+ DEBUG_LOG(("Debug logs started."));
+ QFile f(cWorkingDir() + qsl("tdata/withdebug"));
+ if (f.open(QIODevice::WriteOnly)) {
+ f.write("1");
+ f.close();
+ }
+ Ui::hideLayer();
+ }
+}
+
+void AppClass::onSwitchTestMode() {
+ if (cTestMode()) {
+ QFile(cWorkingDir() + qsl("tdata/withtestmode")).remove();
+ cSetTestMode(false);
+ } else {
+ QFile f(cWorkingDir() + qsl("tdata/withtestmode"));
+ if (f.open(QIODevice::WriteOnly)) {
+ f.write("1");
+ f.close();
+ }
+ cSetTestMode(true);
+ }
+ cSetRestarting(true);
+ cSetRestartingToSettings(true);
+ App::quit();
+}
+
+FileUploader *AppClass::uploader() {
+ if (!_uploader && !App::quitting()) _uploader = new FileUploader();
+ return _uploader;
+}
+
+void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) {
+ PreparedPhotoThumbs photoThumbs;
+ QVector photoSizes;
+
+ QPixmap thumb = QPixmap::fromImage(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
+ photoThumbs.insert('a', thumb);
+ photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0)));
+
+ QPixmap medium = QPixmap::fromImage(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
+ photoThumbs.insert('b', medium);
+ photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
+
+ QPixmap full = QPixmap::fromImage(tosend, Qt::ColorOnly);
+ photoThumbs.insert('c', full);
+ photoSizes.push_back(MTP_photoSize(MTP_string("c"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)));
+
+ QByteArray jpeg;
+ QBuffer jpegBuffer(&jpeg);
+ full.save(&jpegBuffer, "JPG", 87);
+
+ PhotoId id = rand_value();
+
+ MTPPhoto photo(MTP_photo(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes)));
+
+ QString file, filename;
+ int32 filesize = 0;
+ QByteArray data;
+
+ ReadyLocalMedia ready(PreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, false, 0);
+
+ connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), App::app(), SLOT(photoUpdated(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
+
+ FullMsgId newId(peerToChannel(peerId), clientMsgId());
+ App::app()->regPhotoUpdate(peerId, newId);
+ App::uploader()->uploadMedia(newId, ready);
+}
+
+void AppClass::checkMapVersion() {
+ if (Local::oldMapVersion() < AppVersion) {
+ if (Local::oldMapVersion()) {
+ QString versionFeatures;
+ if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9040) {
+// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Bug fixes and other minor improvements");
+ versionFeatures = langNewVersionText();
+ } else if (Local::oldMapVersion() < 9040) {
+ versionFeatures = langNewVersionText();
+ } else {
+ versionFeatures = lang(lng_new_version_minor).trimmed();
+ }
+ if (!versionFeatures.isEmpty()) {
+ versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog"));
+ _window->serviceNotification(versionFeatures);
+ }
+ }
+ }
+}
+
+AppClass::~AppClass() {
+ Shortcuts::finish();
+
+ if (Window *w = _window) {
+ _window = 0;
+ delete w;
+ }
anim::stopManager();
- socket.close();
- closeApplication();
stopWebLoadManager();
App::deinitMedia();
deinitImageLinkManager();
- mainApp = 0;
- delete ::uploader;
+ MTP::finish();
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- delete updateReply;
- updateReply = 0;
- if (updateDownloader) updateDownloader->deleteLater();
- updateDownloader = 0;
- if (updateThread) updateThread->quit();
- updateThread = 0;
- #endif
-
- Window *w = window;
- window = 0;
- delete w;
+ AppObject = 0;
+ deleteAndMark(_uploader);
+ deleteAndMark(_translator);
delete cChatBackground();
cSetChatBackground(0);
@@ -914,37 +1068,19 @@ Application::~Application() {
style::stopManager();
- delete _translator;
+ Local::finish();
+ Global::finish();
+ ThirdParty::finish();
}
-Application *Application::app() {
- return mainApp;
+AppClass *AppClass::app() {
+ return AppObject;
}
-Window *Application::wnd() {
- return mainApp ? mainApp->window : 0;
+Window *AppClass::wnd() {
+ return AppObject ? AppObject->_window : 0;
}
-QString Application::language() {
- if (!lng.length()) {
- lng = psCurrentLanguage();
- }
- if (!lng.length()) {
- lng = "en";
- }
- return lng;
-}
-
-int32 Application::languageId() {
- QByteArray l = language().toLatin1();
- for (int32 i = 0; i < languageCount; ++i) {
- if (l.at(0) == LanguageCodes[i][0] && l.at(1) == LanguageCodes[i][1]) {
- return i;
- }
- }
- return languageDefault;
-}
-
-MainWidget *Application::main() {
- return mainApp ? mainApp->window->mainWidget() : 0;
+MainWidget *AppClass::main() {
+ return (AppObject && AppObject->_window) ? AppObject->_window->mainWidget() : 0;
}
diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h
index 21866e49fa..3e0a37e565 100644
--- a/Telegram/SourceFiles/application.h
+++ b/Telegram/SourceFiles/application.h
@@ -16,35 +16,57 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
-#include
-#include
-#include
-
#include "window.h"
#include "pspecific.h"
-class MainWidget;
-class FileUploader;
-class Translator;
-class UpdateDownloader;
-
-class Application : public PsApplication, public RPCSender {
+class UpdateChecker;
+class Application : public QApplication {
Q_OBJECT
public:
Application(int &argc, char **argv);
- ~Application();
-
- static Application *app();
- static Window *wnd();
- static QString language();
- static int32 languageId();
- static MainWidget *main();
+
+// Single instance application
+public slots:
+
+ void socketConnected();
+ void socketError(QLocalSocket::LocalSocketError e);
+ void socketDisconnected();
+ void socketWritten(qint64 bytes);
+ void socketReading();
+ void newInstanceConnected();
+
+ void readClients();
+ void removeClients();
+
+ void startApplication(); // will be done in exec()
+ void closeApplication(); // will be done in aboutToQuit()
+
+private:
+
+ typedef QPair LocalClient;
+ typedef QList LocalClients;
+
+ QString _localServerName, _localSocketReadData;
+ QLocalServer _localServer;
+ QLocalSocket _localSocket;
+ LocalClients _localClients;
+ bool _secondInstance;
+
+ void singleInstanceChecked();
+
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
+
+// Autoupdating
+public:
+
+ void startUpdateCheck(bool forceWait);
+ void stopUpdate();
enum UpdatingState {
UpdatingNone,
@@ -52,11 +74,87 @@ public:
UpdatingReady,
};
UpdatingState updatingState();
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
int32 updatingSize();
int32 updatingReady();
+
+signals:
+
+ void updateChecking();
+ void updateLatest();
+ void updateProgress(qint64 ready, qint64 total);
+ void updateReady();
+ void updateFailed();
+
+public slots:
+
+ void updateCheck();
+
+ void updateGotCurrent();
+ void updateFailedCurrent(QNetworkReply::NetworkError e);
+
+ void onUpdateReady();
+ void onUpdateFailed();
+
+private:
+
+ SingleTimer _updateCheckTimer;
+ QNetworkReply *_updateReply;
+ QNetworkAccessManager _updateManager;
+ QThread *_updateThread;
+ UpdateChecker *_updateChecker;
+
+#endif
+};
+
+namespace Sandbox {
+
+ QRect availableGeometry();
+ QRect screenGeometry(const QPoint &p);
+ void setActiveWindow(QWidget *window);
+ bool isSavingSession();
+
+ void installEventFilter(QObject *filter);
+
+ void execExternal(const QString &cmd);
+
+#ifndef TDESKTOP_DISABLE_AUTOUPDATE
+
+ void startUpdateCheck();
void stopUpdate();
- #endif
+
+ Application::UpdatingState updatingState();
+ int32 updatingSize();
+ int32 updatingReady();
+
+ void updateChecking();
+ void updateLatest();
+ void updateProgress(qint64 ready, qint64 total);
+ void updateFailed();
+ void updateReady();
+
+#endif
+
+ void connect(const char *signal, QObject *object, const char *method);
+
+ void launch();
+
+}
+
+class MainWidget;
+class FileUploader;
+class Translator;
+
+class AppClass : public QObject, public RPCSender {
+ Q_OBJECT
+
+public:
+
+ AppClass();
+ ~AppClass();
+
+ static AppClass *app();
+ static Window *wnd();
+ static MainWidget *main();
FileUploader *uploader();
void uploadProfilePhoto(const QImage &tosend, const PeerId &peerId);
@@ -85,14 +183,6 @@ public:
signals:
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- void updateChecking();
- void updateLatest();
- void updateDownloading(qint64 ready, qint64 total);
- void updateReady();
- void updateFailed();
- #endif
-
void peerPhotoDone(PeerId peer);
void peerPhotoFail(PeerId peer);
@@ -100,31 +190,9 @@ signals:
public slots:
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- void startUpdateCheck(bool forceWait = false);
- #endif
- void socketConnected();
- void socketError(QLocalSocket::LocalSocketError e);
- void socketDisconnected();
- void socketWritten(qint64 bytes);
- void socketReading();
- void newInstanceConnected();
- void closeApplication();
-
void doMtpUnpause();
- void readClients();
- void removeClients();
-
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- void updateGotCurrent();
- void updateFailedCurrent(QNetworkReply::NetworkError e);
-
- void onUpdateReady();
- void onUpdateFailed();
- #endif
-
- void photoUpdated(const FullMsgId &msgId, const MTPInputFile &file);
+ void photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onSwitchDebugMode();
void onSwitchTestMode();
@@ -132,6 +200,8 @@ public slots:
void killDownloadSessions();
void onAppStateChanged(Qt::ApplicationState state);
+ void call_handleHistoryUpdate();
+
private:
QMap photoUpdates;
@@ -139,35 +209,12 @@ private:
QMap killDownloadSessionTimes;
SingleTimer killDownloadSessionsTimer;
- void startApp();
+ uint64 _lastActionTime;
- typedef QPair ClientSocket;
- typedef QVector ClientSockets;
-
- QString serverName;
- QLocalSocket socket;
- QString socketRead;
- QLocalServer server;
- ClientSockets clients;
- bool closing;
-
- uint64 lastActionTime;
-
- void execExternal(const QString &cmd);
-
- Window *window;
-
- #ifndef TDESKTOP_DISABLE_AUTOUPDATE
- mtpRequestId updateRequestId;
- QNetworkAccessManager updateManager;
- QNetworkReply *updateReply;
- SingleTimer updateCheckTimer;
- QThread *updateThread;
- UpdateDownloader *updateDownloader;
- #endif
+ Window *_window;
+ FileUploader *_uploader;
+ Translator *_translator;
SingleTimer _mtpUnpauseTimer;
- Translator *_translator;
-
};
diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png
index ce32997180..d919c9f3ac 100644
Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ
diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png
index 16b454f057..0ceeb20dd9 100644
Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ
diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp
index 6a5be7b68d..49a536ed13 100644
--- a/Telegram/SourceFiles/audio.cpp
+++ b/Telegram/SourceFiles/audio.cpp
@@ -16,9 +16,10 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
+
#include "audio.h"
#include
@@ -27,10 +28,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
#define AL_ALEXT_PROTOTYPES
#include
-#ifdef Q_OS_MAC
-
extern "C" {
+#include
+#include
+#include
+#include
+#ifdef Q_OS_MAC
#include
#undef iconv_open
@@ -46,10 +50,9 @@ size_t iconv (iconv_t cd, char* * inbuf, size_t *inbytesleft, char* * outbuf, s
int iconv_close (iconv_t cd) {
return libiconv_close(cd);
}
+#endif // Q_OS_MAC
-}
-
-#endif
+} // extern "C"
namespace {
ALCdevice *audioDevice = 0;
@@ -96,12 +99,8 @@ bool _checkALError() {
Q_DECLARE_METATYPE(AudioMsgId);
Q_DECLARE_METATYPE(SongMsgId);
+Q_DECLARE_METATYPE(VoiceWaveform);
void audioInit() {
- if (!audioDevice) {
- av_register_all();
- avcodec_register_all();
- }
-
if (!capture) {
capture = new AudioCapture();
cSetHasAudioCapture(capture->check());
@@ -114,7 +113,7 @@ void audioInit() {
LOG(("Audio Error: default sound device not present."));
return;
}
-
+
ALCint attributes[] = { ALC_STEREO_SOURCES, 8, 0 };
audioContext = alcCreateContext(audioDevice, attributes);
alcMakeContextCurrent(audioContext);
@@ -211,6 +210,7 @@ void audioInit() {
qRegisterMetaType();
qRegisterMetaType();
+ qRegisterMetaType();
player = new AudioPlayer();
alcDevicePauseSOFT(audioDevice);
@@ -227,12 +227,15 @@ void audioPlayNotify() {
emit audioPlayer()->faderOnTimer();
}
+// can be called at any moment when audio error
void audioFinish() {
if (player) {
delete player;
+ player = nullptr;
}
if (capture) {
delete capture;
+ capture = nullptr;
}
alSourceStop(notifySource);
@@ -246,14 +249,14 @@ void audioFinish() {
}
if (audioContext) {
- alcMakeContextCurrent(NULL);
+ alcMakeContextCurrent(nullptr);
alcDestroyContext(audioContext);
- audioContext = 0;
+ audioContext = nullptr;
}
if (audioDevice) {
alcCloseDevice(audioDevice);
- audioDevice = 0;
+ audioDevice = nullptr;
}
cSetHasAudioCapture(false);
@@ -373,8 +376,8 @@ void AudioPlayer::onStopped(const SongMsgId &song) {
bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) {
Msg *data = 0;
switch (type) {
- case OverviewAudios: data = &_audioData[_audioCurrent]; break;
- case OverviewDocuments: data = &_songData[_songCurrent]; break;
+ case OverviewVoiceFiles: data = &_audioData[_audioCurrent]; break;
+ case OverviewFiles: data = &_songData[_songCurrent]; break;
}
if (!data) return false;
@@ -387,8 +390,8 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) {
if (!_checkALError()) {
setStoppedState(data, AudioPlayerStoppedAtError);
switch (type) {
- case OverviewAudios: onError(_audioData[_audioCurrent].audio); break;
- case OverviewDocuments: onError(_songData[_songCurrent].song); break;
+ case OverviewVoiceFiles: onError(_audioData[_audioCurrent].audio); break;
+ case OverviewFiles: onError(_songData[_songCurrent].song); break;
}
return false;
}
@@ -400,8 +403,8 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) {
bool AudioPlayer::fadedStop(MediaOverviewType type, bool *fadedStart) {
Msg *current = 0;
switch (type) {
- case OverviewAudios: current = &_audioData[_audioCurrent]; break;
- case OverviewDocuments: current = &_songData[_songCurrent]; break;
+ case OverviewVoiceFiles: current = &_audioData[_audioCurrent]; break;
+ case OverviewFiles: current = &_songData[_songCurrent]; break;
}
if (!current) return false;
@@ -433,7 +436,7 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) {
bool fadedStart = false;
AudioMsg *current = &_audioData[_audioCurrent];
if (current->audio != audio) {
- if (fadedStop(OverviewAudios, &fadedStart)) {
+ if (fadedStop(OverviewVoiceFiles, &fadedStart)) {
stopped = current->audio;
}
if (current->audio) {
@@ -477,7 +480,7 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) {
bool fadedStart = false;
SongMsg *current = &_songData[_songCurrent];
if (current->song != song) {
- if (fadedStop(OverviewDocuments, &fadedStart)) {
+ if (fadedStop(OverviewFiles, &fadedStart)) {
stopped = current->song;
}
if (current->song) {
@@ -503,7 +506,7 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) {
if (current->file.isEmpty() && current->data.isEmpty()) {
setStoppedState(current);
if (!song.song->loading()) {
- DocumentOpenLink::doOpen(song.song);
+ DocumentOpenClickHandler::doOpen(song.song);
}
} else {
current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying;
@@ -516,13 +519,13 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) {
bool AudioPlayer::checkCurrentALError(MediaOverviewType type) {
if (_checkALError()) return true;
-
+
switch (type) {
- case OverviewAudios:
+ case OverviewVoiceFiles:
setStoppedState(&_audioData[_audioCurrent], AudioPlayerStoppedAtError);
onError(_audioData[_audioCurrent].audio);
break;
- case OverviewDocuments:
+ case OverviewFiles:
setStoppedState(&_songData[_songCurrent], AudioPlayerStoppedAtError);
onError(_songData[_songCurrent].song);
break;
@@ -536,11 +539,11 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) {
Msg *current = 0;
float64 suppressGain = 1.;
switch (type) {
- case OverviewAudios:
+ case OverviewVoiceFiles:
current = &_audioData[_audioCurrent];
suppressGain = suppressAllGain;
break;
- case OverviewDocuments:
+ case OverviewFiles:
current = &_songData[_songCurrent];
suppressGain = suppressSongGain * cSongVolume();
break;
@@ -572,14 +575,14 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) {
alSourcePlay(current->source);
if (!checkCurrentALError(type)) return;
}
- if (type == OverviewAudios) emit suppressSong();
+ if (type == OverviewVoiceFiles) emit suppressSong();
} break;
case AudioPlayerStarting:
case AudioPlayerResuming:
case AudioPlayerPlaying:
current->state = AudioPlayerPausing;
updateCurrentStarted(type);
- if (type == OverviewAudios) emit unsuppressSong();
+ if (type == OverviewVoiceFiles) emit unsuppressSong();
break;
case AudioPlayerFinishing: current->state = AudioPlayerPausing; break;
}
@@ -589,18 +592,18 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) {
void AudioPlayer::seek(int64 position) {
QMutexLocker lock(&playerMutex);
- MediaOverviewType type = OverviewDocuments;
+ MediaOverviewType type = OverviewFiles;
Msg *current = 0;
float64 suppressGain = 1.;
AudioMsgId audio;
SongMsgId song;
switch (type) {
- case OverviewAudios:
+ case OverviewVoiceFiles:
current = &_audioData[_audioCurrent];
audio = _audioData[_audioCurrent].audio;
suppressGain = suppressAllGain;
break;
- case OverviewDocuments:
+ case OverviewFiles:
current = &_songData[_songCurrent];
song = _songData[_songCurrent].song;
suppressGain = suppressSongGain * cSongVolume();
@@ -634,7 +637,7 @@ void AudioPlayer::seek(int64 position) {
case AudioPlayerPlaying:
current->state = AudioPlayerPausing;
updateCurrentStarted(type);
- if (type == OverviewAudios) emit unsuppressSong();
+ if (type == OverviewVoiceFiles) emit unsuppressSong();
break;
case AudioPlayerFinishing:
case AudioPlayerStopped:
@@ -643,8 +646,8 @@ void AudioPlayer::seek(int64 position) {
case AudioPlayerStoppedAtStart:
lock.unlock();
switch (type) {
- case OverviewAudios: if (audio) return play(audio, position);
- case OverviewDocuments: if (song) return play(song, position);
+ case OverviewVoiceFiles: if (audio) return play(audio, position);
+ case OverviewFiles: if (song) return play(song, position);
}
}
emit faderOnTimer();
@@ -652,7 +655,7 @@ void AudioPlayer::seek(int64 position) {
void AudioPlayer::stop(MediaOverviewType type) {
switch (type) {
- case OverviewAudios: {
+ case OverviewVoiceFiles: {
AudioMsgId current;
{
QMutexLocker lock(&playerMutex);
@@ -662,7 +665,7 @@ void AudioPlayer::stop(MediaOverviewType type) {
if (current) emit updated(current);
} break;
- case OverviewDocuments: {
+ case OverviewFiles: {
SongMsgId current;
{
QMutexLocker lock(&playerMutex);
@@ -759,8 +762,8 @@ void AudioPlayer::resumeDevice() {
AudioCapture::AudioCapture() : _capture(new AudioCaptureInner(&_captureThread)) {
connect(this, SIGNAL(captureOnStart()), _capture, SLOT(onStart()));
connect(this, SIGNAL(captureOnStop(bool)), _capture, SLOT(onStop(bool)));
- connect(_capture, SIGNAL(done(QByteArray,qint32)), this, SIGNAL(onDone(QByteArray,qint32)));
- connect(_capture, SIGNAL(update(qint16,qint32)), this, SIGNAL(onUpdate(qint16,qint32)));
+ connect(_capture, SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SIGNAL(onDone(QByteArray,VoiceWaveform,qint32)));
+ connect(_capture, SIGNAL(update(quint16,qint32)), this, SIGNAL(onUpdate(quint16,qint32)));
connect(_capture, SIGNAL(error()), this, SIGNAL(onError()));
connect(&_captureThread, SIGNAL(started()), _capture, SLOT(onInit()));
connect(&_captureThread, SIGNAL(finished()), _capture, SLOT(deleteLater()));
@@ -1091,7 +1094,7 @@ protected:
QFile f;
int32 dataPos;
-
+
bool openFile() {
if (data.isEmpty()) {
if (f.isOpen()) f.close();
@@ -1114,19 +1117,18 @@ protected:
};
-static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16;
-static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO;
-static const int32 _toChannels = 2;
-class FFMpegLoader : public AudioPlayerLoader {
+class AbstractFFMpegLoader : public AudioPlayerLoader {
public:
- FFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data),
- freq(AudioVoiceMsgFrequency), fmt(AL_FORMAT_STEREO16),
- sampleSize(2 * sizeof(short)), srcRate(AudioVoiceMsgFrequency), dstRate(AudioVoiceMsgFrequency),
- maxResampleSamples(1024), dstSamplesData(0), len(0),
- ioBuffer(0), ioContext(0), fmtContext(0), codec(0), codecContext(0), streamId(0), frame(0), swrContext(0),
- _opened(false) {
- frame = av_frame_alloc();
+ AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data)
+ , freq(AudioVoiceMsgFrequency)
+ , len(0)
+ , ioBuffer(0)
+ , ioContext(0)
+ , fmtContext(0)
+ , codec(0)
+ , streamId(0)
+ , _opened(false) {
}
bool open(qint64 position = 0) {
@@ -1134,31 +1136,32 @@ public:
return false;
}
+ int res = 0;
+ char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+
ioBuffer = (uchar*)av_malloc(AVBlockSize);
if (data.isEmpty()) {
- ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegLoader::_read_file, 0, &FFMpegLoader::_seek_file);
+ ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file);
} else {
- ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegLoader::_read_data, 0, &FFMpegLoader::_seek_data);
+ ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data);
}
fmtContext = avformat_alloc_context();
if (!fmtContext) {
- LOG(("Audio Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
+ DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
return false;
}
fmtContext->pb = ioContext;
- int res = 0;
- char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) {
ioBuffer = 0;
- LOG(("Audio Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+ DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return false;
}
_opened = true;
if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) {
- LOG(("Audio Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+ DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return false;
}
@@ -1168,20 +1171,130 @@ public:
return false;
}
- // Get a pointer to the codec context for the audio stream
- codecContext = fmtContext->streams[streamId]->codec;
- av_opt_set_int(codecContext, "refcounted_frames", 1, 0);
- if ((res = avcodec_open2(codecContext, codec, 0)) < 0) {
- LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
- return false;
- }
-
- freq = codecContext->sample_rate;
+ freq = fmtContext->streams[streamId]->codec->sample_rate;
if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) {
len = (fmtContext->duration * freq) / AV_TIME_BASE;
} else {
len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den;
}
+
+ return true;
+ }
+
+ int64 duration() {
+ return len;
+ }
+
+ int32 frequency() {
+ return freq;
+ }
+
+ ~AbstractFFMpegLoader() {
+ if (ioContext) av_free(ioContext);
+ if (_opened) {
+ avformat_close_input(&fmtContext);
+ } else if (ioBuffer) {
+ av_free(ioBuffer);
+ }
+ if (fmtContext) avformat_free_context(fmtContext);
+ }
+
+protected:
+
+ int32 freq;
+ int64 len;
+
+ uchar *ioBuffer;
+ AVIOContext *ioContext;
+ AVFormatContext *fmtContext;
+ AVCodec *codec;
+ int32 streamId;
+
+ bool _opened;
+
+private:
+
+ static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
+ AbstractFFMpegLoader *l = reinterpret_cast(opaque);
+
+ int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
+ if (nbytes <= 0) {
+ return 0;
+ }
+
+ memcpy(buf, l->data.constData() + l->dataPos, nbytes);
+ l->dataPos += nbytes;
+ return nbytes;
+ }
+
+ static int64_t _seek_data(void *opaque, int64_t offset, int whence) {
+ AbstractFFMpegLoader *l = reinterpret_cast(opaque);
+
+ int32 newPos = -1;
+ switch (whence) {
+ case SEEK_SET: newPos = offset; break;
+ case SEEK_CUR: newPos = l->dataPos + offset; break;
+ case SEEK_END: newPos = l->data.size() + offset; break;
+ }
+ if (newPos < 0 || newPos > l->data.size()) {
+ return -1;
+ }
+ l->dataPos = newPos;
+ return l->dataPos;
+ }
+
+ static int _read_file(void *opaque, uint8_t *buf, int buf_size) {
+ AbstractFFMpegLoader *l = reinterpret_cast(opaque);
+ return int(l->f.read((char*)(buf), buf_size));
+ }
+
+ static int64_t _seek_file(void *opaque, int64_t offset, int whence) {
+ AbstractFFMpegLoader *l = reinterpret_cast(opaque);
+
+ switch (whence) {
+ case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1;
+ case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1;
+ case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1;
+ }
+ return -1;
+ }
+};
+
+static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16;
+static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO;
+static const int32 _toChannels = 2;
+class FFMpegLoader : public AbstractFFMpegLoader {
+public:
+
+ FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data)
+ , sampleSize(2 * sizeof(uint16))
+ , fmt(AL_FORMAT_STEREO16)
+ , srcRate(AudioVoiceMsgFrequency)
+ , dstRate(AudioVoiceMsgFrequency)
+ , maxResampleSamples(1024)
+ , dstSamplesData(0)
+ , codecContext(0)
+ , frame(0)
+ , swrContext(0) {
+ frame = av_frame_alloc();
+ }
+
+ bool open(qint64 position = 0) {
+ if (!AbstractFFMpegLoader::open(position)) {
+ return false;
+ }
+
+ int res = 0;
+ char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+
+ // Get a pointer to the codec context for the audio stream
+ av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0);
+ if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) {
+ LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+ return false;
+ }
+ codecContext = fmtContext->streams[streamId]->codec;
+
uint64_t layout = codecContext->channel_layout;
inputFormat = codecContext->sample_fmt;
switch (layout) {
@@ -1190,7 +1303,7 @@ public:
case AV_SAMPLE_FMT_U8:
case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break;
case AV_SAMPLE_FMT_S16:
- case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = 2; break;
+ case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break;
default:
sampleSize = -1; // convert needed
break;
@@ -1198,8 +1311,8 @@ public:
break;
case AV_CH_LAYOUT_STEREO:
switch (inputFormat) {
- case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = sizeof(short); break;
- case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(short); break;
+ case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break;
+ case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break;
default:
sampleSize = -1; // convert needed
break;
@@ -1261,14 +1374,6 @@ public:
return true;
}
- int64 duration() {
- return len;
- }
-
- int32 frequency() {
- return freq;
- }
-
int32 format() {
return fmt;
}
@@ -1331,7 +1436,6 @@ public:
}
~FFMpegLoader() {
- if (ioContext) av_free(ioContext);
if (codecContext) avcodec_close(codecContext);
if (swrContext) swr_free(&swrContext);
if (dstSamplesData) {
@@ -1340,80 +1444,25 @@ public:
}
av_freep(&dstSamplesData);
}
- if (_opened) {
- avformat_close_input(&fmtContext);
- } else if (ioBuffer) {
- av_free(ioBuffer);
- }
- if (fmtContext) avformat_free_context(fmtContext);
av_frame_free(&frame);
}
+protected:
+ int32 sampleSize;
+
private:
- int32 freq, fmt;
- int32 sampleSize, srcRate, dstRate, maxResampleSamples;
+ int32 fmt;
+ int32 srcRate, dstRate, maxResampleSamples;
uint8_t **dstSamplesData;
- int64 len;
- uchar *ioBuffer;
- AVIOContext *ioContext;
- AVFormatContext *fmtContext;
- AVCodec *codec;
AVCodecContext *codecContext;
AVPacket avpkt;
- int32 streamId;
AVSampleFormat inputFormat;
AVFrame *frame;
SwrContext *swrContext;
- bool _opened;
-
- static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
- FFMpegLoader *l = reinterpret_cast(opaque);
-
- int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
- if (nbytes <= 0) {
- return 0;
- }
-
- memcpy(buf, l->data.constData() + l->dataPos, nbytes);
- l->dataPos += nbytes;
- return nbytes;
- }
-
- static int64_t _seek_data(void *opaque, int64_t offset, int whence) {
- FFMpegLoader *l = reinterpret_cast(opaque);
-
- int32 newPos = -1;
- switch (whence) {
- case SEEK_SET: newPos = offset; break;
- case SEEK_CUR: newPos = l->dataPos + offset; break;
- case SEEK_END: newPos = l->data.size() + offset; break;
- }
- if (newPos < 0 || newPos > l->data.size()) {
- return -1;
- }
- l->dataPos = newPos;
- return l->dataPos;
- }
-
- static int _read_file(void *opaque, uint8_t *buf, int buf_size) {
- FFMpegLoader *l = reinterpret_cast(opaque);
- return int(l->f.read((char*)(buf), buf_size));
- }
-
- static int64_t _seek_file(void *opaque, int64_t offset, int whence) {
- FFMpegLoader *l = reinterpret_cast(opaque);
-
- switch (whence) {
- case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1;
- case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1;
- case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1;
- }
- return -1;
- }
};
AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _audioLoader(0), _songLoader(0) {
@@ -1441,7 +1490,7 @@ void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) {
voice->_audioData[voice->_audioCurrent].loading = true;
}
- loadData(OverviewAudios, static_cast(&audio), position);
+ loadData(OverviewVoiceFiles, static_cast(&audio), position);
}
void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) {
@@ -1457,13 +1506,13 @@ void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) {
voice->_songData[voice->_songCurrent].loading = true;
}
- loadData(OverviewDocuments, static_cast(&song), position);
+ loadData(OverviewFiles, static_cast(&song), position);
}
void AudioPlayerLoaders::clear(MediaOverviewType type) {
switch (type) {
- case OverviewAudios: clearAudio(); break;
- case OverviewDocuments: clearSong(); break;
+ case OverviewVoiceFiles: clearAudio(); break;
+ case OverviewFiles: clearSong(); break;
}
}
@@ -1474,8 +1523,8 @@ void AudioPlayerLoaders::setStoppedState(AudioPlayer::Msg *m, AudioPlayerState s
void AudioPlayerLoaders::emitError(MediaOverviewType type) {
switch (type) {
- case OverviewAudios: emit error(clearAudio()); break;
- case OverviewDocuments: emit error(clearSong()); break;
+ case OverviewVoiceFiles: emit error(clearAudio()); break;
+ case OverviewFiles: emit error(clearSong()); break;
}
}
@@ -1496,11 +1545,11 @@ SongMsgId AudioPlayerLoaders::clearSong() {
}
void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) {
- loadData(OverviewAudios, static_cast(&audio), 0);
+ loadData(OverviewVoiceFiles, static_cast(&audio), 0);
}
void AudioPlayerLoaders::onLoad(const SongMsgId &song) {
- loadData(OverviewDocuments, static_cast(&song), 0);
+ loadData(OverviewFiles, static_cast(&song), 0);
}
void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qint64 position) {
@@ -1613,8 +1662,8 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qin
audioPlayer()->resumeDevice();
switch (type) {
- case OverviewAudios: alSourcef(m->source, AL_GAIN, suppressAllGain); break;
- case OverviewDocuments: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break;
+ case OverviewVoiceFiles: alSourcef(m->source, AL_GAIN, suppressAllGain); break;
+ case OverviewFiles: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break;
}
if (!_checkALError()) {
setStoppedState(m, AudioPlayerStoppedAtError);
@@ -1642,13 +1691,13 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
err = SetupErrorAtStart;
QMutexLocker lock(&playerMutex);
AudioPlayer *voice = audioPlayer();
- if (!voice) return 0;
+ if (!voice) return nullptr;
bool isGoodId = false;
AudioPlayer::Msg *m = 0;
AudioPlayerLoader **l = 0;
switch (type) {
- case OverviewAudios: {
+ case OverviewVoiceFiles: {
AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]);
const AudioMsgId &audio(*static_cast(objId));
if (msg.audio != audio || !msg.loading) {
@@ -1659,7 +1708,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
l = &_audioLoader;
isGoodId = (_audio == audio);
} break;
- case OverviewDocuments: {
+ case OverviewFiles: {
AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]);
const SongMsgId &song(*static_cast(objId));
if (msg.song != song || !msg.loading) {
@@ -1674,22 +1723,22 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
if (!l || !m) {
LOG(("Audio Error: trying to load part of audio, that is not current at the moment"));
err = SetupErrorNotPlaying;
- return 0;
+ return nullptr;
}
if (*l && (!isGoodId || !(*l)->check(m->file, m->data))) {
delete *l;
*l = 0;
switch (type) {
- case OverviewAudios: _audio = AudioMsgId(); break;
- case OverviewDocuments: _song = SongMsgId(); break;
+ case OverviewVoiceFiles: _audio = AudioMsgId(); break;
+ case OverviewFiles: _song = SongMsgId(); break;
}
}
if (!*l) {
switch (type) {
- case OverviewAudios: _audio = *static_cast(objId); break;
- case OverviewDocuments: _song = *static_cast(objId); break;
+ case OverviewVoiceFiles: _audio = *static_cast(objId); break;
+ case OverviewFiles: _song = *static_cast(objId); break;
}
// QByteArray header = m->data.mid(0, 8);
@@ -1698,27 +1747,26 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
// if (!f.open(QIODevice::ReadOnly)) {
// LOG(("Audio Error: could not open file '%1'").arg(m->fname));
// m->state = AudioPlayerStoppedAtStart;
-// return 0;
+// return nullptr;
// }
// header = f.read(8);
// }
// if (header.size() < 8) {
// LOG(("Audio Error: could not read header from file '%1', data size %2").arg(m->fname).arg(m->data.isEmpty() ? QFileInfo(m->fname).size() : m->data.size()));
// m->state = AudioPlayerStoppedAtStart;
-// return 0;
+// return nullptr;
// }
*l = new FFMpegLoader(m->file, m->data);
- int ret;
if (!(*l)->open(position)) {
m->state = AudioPlayerStoppedAtStart;
- return 0;
+ return nullptr;
}
int64 duration = (*l)->duration();
if (duration <= 0) {
m->state = AudioPlayerStoppedAtStart;
- return 0;
+ return nullptr;
}
m->duration = duration;
m->frequency = (*l)->frequency();
@@ -1728,7 +1776,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
if (!m->skipEnd) {
err = SetupErrorLoadedFull;
LOG(("Audio Error: trying to load part of audio, that is already loaded to the end"));
- return 0;
+ return nullptr;
}
}
return *l;
@@ -1742,13 +1790,13 @@ AudioPlayer::Msg *AudioPlayerLoaders::checkLoader(MediaOverviewType type) {
AudioPlayer::Msg *m = 0;
AudioPlayerLoader **l = 0;
switch (type) {
- case OverviewAudios: {
+ case OverviewVoiceFiles: {
AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]);
isGoodId = (msg.audio == _audio);
l = &_audioLoader;
m = &msg;
} break;
- case OverviewDocuments: {
+ case OverviewFiles: {
AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]);
isGoodId = (msg.song == _song);
l = &_songLoader;
@@ -1804,10 +1852,30 @@ void AudioPlayerLoaders::onCancel(const SongMsgId &song) {
}
struct AudioCapturePrivate {
- AudioCapturePrivate() :
- device(0), fmt(0), ioBuffer(0), ioContext(0), fmtContext(0), stream(0), codec(0), codecContext(0), opened(false),
- srcSamples(0), dstSamples(0), maxDstSamples(0), dstSamplesSize(0), fullSamples(0), srcSamplesData(0), dstSamplesData(0),
- swrContext(0), lastUpdate(0), level(0), dataPos(0) {
+ AudioCapturePrivate()
+ : device(0)
+ , fmt(0)
+ , ioBuffer(0)
+ , ioContext(0)
+ , fmtContext(0)
+ , stream(0)
+ , codec(0)
+ , codecContext(0)
+ , opened(false)
+ , srcSamples(0)
+ , dstSamples(0)
+ , maxDstSamples(0)
+ , dstSamplesSize(0)
+ , fullSamples(0)
+ , srcSamplesData(0)
+ , dstSamplesData(0)
+ , swrContext(0)
+ , lastUpdate(0)
+ , levelMax(0)
+ , dataPos(0)
+ , waveformMod(0)
+ , waveformEach(AudioVoiceMsgFrequency / 100)
+ , waveformPeak(0) {
}
ALCdevice *device;
AVOutputFormat *fmt;
@@ -1824,11 +1892,15 @@ struct AudioCapturePrivate {
SwrContext *swrContext;
int32 lastUpdate;
- int64 level;
+ uint16 levelMax;
QByteArray data;
int32 dataPos;
+ int64 waveformMod, waveformEach;
+ uint16 waveformPeak;
+ QVector waveform;
+
static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
AudioCapturePrivate *l = reinterpret_cast(opaque);
@@ -1884,7 +1956,7 @@ void AudioCaptureInner::onInit() {
}
void AudioCaptureInner::onStart() {
-
+
// Start OpenAL Capture
const ALCchar *dName = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
DEBUG_LOG(("Audio Info: Capture device name '%1'").arg(dName));
@@ -1905,7 +1977,7 @@ void AudioCaptureInner::onStart() {
// Create encoding context
d->ioBuffer = (uchar*)av_malloc(AVBlockSize);
-
+
d->ioContext = avio_alloc_context(d->ioBuffer, AVBlockSize, 1, static_cast(d), &AudioCapturePrivate::_read_data, &AudioCapturePrivate::_write_data, &AudioCapturePrivate::_seek_data);
int res = 0;
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
@@ -1962,7 +2034,7 @@ void AudioCaptureInner::onStart() {
}
// Open audio stream
- if ((res = avcodec_open2(d->codecContext, d->codec, NULL)) < 0) {
+ if ((res = avcodec_open2(d->codecContext, d->codec, nullptr)) < 0) {
LOG(("Audio Error: Unable to avcodec_open2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
@@ -2040,6 +2112,9 @@ void AudioCaptureInner::onStop(bool needResult) {
d->fullSamples = 0;
d->dataPos = 0;
d->data.clear();
+ d->waveformMod = 0;
+ d->waveformPeak = 0;
+ d->waveform.clear();
} else {
float64 coef = 1. / fadeSamples, fadedFrom = 0;
for (short *ptr = ((short*)_captured.data()) + capturedSamples, *end = ptr - fadeSamples; ptr != end; ++fadedFrom) {
@@ -2061,10 +2136,13 @@ void AudioCaptureInner::onStop(bool needResult) {
d->fullSamples = 0;
d->dataPos = 0;
d->data.clear();
+ d->waveformMod = 0;
+ d->waveformPeak = 0;
+ d->waveform.clear();
}
}
}
- DEBUG_LOG(("Audio Capture: stopping (need result: %1), size: %2, samples: %3").arg(logBool(needResult)).arg(d->data.size()).arg(d->fullSamples));
+ DEBUG_LOG(("Audio Capture: stopping (need result: %1), size: %2, samples: %3").arg(Logs::b(needResult)).arg(d->data.size()).arg(d->fullSamples));
_captured = QByteArray();
// Finish stream
@@ -2073,7 +2151,37 @@ void AudioCaptureInner::onStop(bool needResult) {
}
QByteArray result = d->fullSamples ? d->data : QByteArray();
+ VoiceWaveform waveform;
qint32 samples = d->fullSamples;
+ if (samples && !d->waveform.isEmpty()) {
+ int64 count = d->waveform.size(), sum = 0;
+ if (count >= WaveformSamplesCount) {
+ QVector peaks;
+ peaks.reserve(WaveformSamplesCount);
+
+ uint16 peak = 0;
+ for (int32 i = 0; i < count; ++i) {
+ uint16 sample = uint16(d->waveform.at(i)) * 256;
+ if (peak < sample) {
+ peak = sample;
+ }
+ sum += WaveformSamplesCount;
+ if (sum >= count) {
+ sum -= count;
+ peaks.push_back(peak);
+ peak = 0;
+ }
+ }
+
+ int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL);
+ peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
+
+ waveform.resize(peaks.size());
+ for (int32 i = 0, l = peaks.size(); i != l; ++i) {
+ waveform[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak));
+ }
+ }
+ }
if (d->device) {
alcCaptureStop(d->device);
alcCaptureCloseDevice(d->device);
@@ -2121,12 +2229,16 @@ void AudioCaptureInner::onStop(bool needResult) {
d->codec = 0;
d->lastUpdate = 0;
- d->level = 0;
+ d->levelMax = 0;
d->dataPos = 0;
d->data.clear();
+
+ d->waveformMod = 0;
+ d->waveformPeak = 0;
+ d->waveform.clear();
}
- if (needResult) emit done(result, samples);
+ if (needResult) emit done(result, waveform, samples);
}
void AudioCaptureInner::onTimeout() {
@@ -2160,18 +2272,20 @@ void AudioCaptureInner::onTimeout() {
int32 levelindex = d->fullSamples + (s / sizeof(short));
for (const short *ptr = (const short*)(_captured.constData() + s), *end = (const short*)(_captured.constData() + news); ptr < end; ++ptr, ++levelindex) {
if (levelindex > skipSamples) {
+ uint16 value = qAbs(*ptr);
if (levelindex < skipSamples + fadeSamples) {
- d->level += qRound(qAbs(*ptr) * float64(levelindex - skipSamples) / fadeSamples);
- } else {
- d->level += qAbs(*ptr);
+ value = qRound(value * float64(levelindex - skipSamples) / fadeSamples);
+ }
+ if (d->levelMax < value) {
+ d->levelMax = value;
}
}
}
qint32 samplesFull = d->fullSamples + _captured.size() / sizeof(short), samplesSinceUpdate = samplesFull - d->lastUpdate;
if (samplesSinceUpdate > AudioVoiceMsgUpdateView * AudioVoiceMsgFrequency / 1000) {
- emit update(d->level / samplesSinceUpdate, samplesFull);
+ emit update(d->levelMax, samplesFull);
d->lastUpdate = samplesFull;
- d->level = 0;
+ d->levelMax = 0;
}
// Write frames
int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0;
@@ -2211,7 +2325,7 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) {
if (d->fullSamples < skipSamples + fadeSamples) {
int32 fadedCnt = qMin(samplesCnt, skipSamples + fadeSamples - d->fullSamples);
float64 coef = 1. / fadeSamples, fadedFrom = d->fullSamples - skipSamples;
- short *ptr = (short*)srcSamplesData[0], *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt;
+ short *ptr = srcSamplesDataChannel, *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt;
for (; ptr != zeroEnd; ++ptr, ++fadedFrom) {
*ptr = 0;
}
@@ -2220,6 +2334,19 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) {
}
}
+ d->waveform.reserve(d->waveform.size() + (samplesCnt / d->waveformEach) + 1);
+ for (short *ptr = srcSamplesDataChannel, *end = ptr + samplesCnt; ptr != end; ++ptr) {
+ uint16 value = qAbs(*ptr);
+ if (d->waveformPeak < value) {
+ d->waveformPeak = value;
+ }
+ if (++d->waveformMod == d->waveformEach) {
+ d->waveformMod -= d->waveformEach;
+ d->waveform.push_back(uchar(d->waveformPeak / 256));
+ d->waveformPeak = 0;
+ }
+ }
+
// Convert to final format
d->dstSamples = av_rescale_rnd(swr_get_delay(d->swrContext, d->codecContext->sample_rate) + d->srcSamples, d->codecContext->sample_rate, d->codecContext->sample_rate, AV_ROUND_UP);
@@ -2274,65 +2401,25 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) {
av_frame_free(&frame);
}
-class FFMpegAttributesReader : public AudioPlayerLoader {
+class FFMpegAttributesReader : public AbstractFFMpegLoader {
public:
- FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data),
- ioBuffer(0), ioContext(0), fmtContext(0), codec(0), streamId(0),
- _opened(false) {
+ FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) {
}
bool open(qint64 position = 0) {
- if (!AudioPlayerLoader::openFile()) {
+ if (!AbstractFFMpegLoader::open()) {
return false;
}
- ioBuffer = (uchar*)av_malloc(AVBlockSize);
- if (data.isEmpty()) {
- ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegAttributesReader::_read_file, 0, &FFMpegAttributesReader::_seek_file);
- } else {
- ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegAttributesReader::_read_data, 0, &FFMpegAttributesReader::_seek_data);
- }
- fmtContext = avformat_alloc_context();
- if (!fmtContext) {
- DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(fname).arg(data.size()));
- return false;
- }
- fmtContext->pb = ioContext;
-
int res = 0;
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
- if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) {
- ioBuffer = 0;
- DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+ int videoStreamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
+ if (videoStreamId >= 0) {
+ DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(videoStreamId).arg(av_make_error_string(err, sizeof(err), streamId)));
return false;
}
- _opened = true;
-
- if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) {
- DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
- return false;
- }
-
- streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
- if (streamId >= 0) {
- DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId)));
- return false;
- }
-
- streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
- if (streamId < 0) {
- DEBUG_LOG(("Audio Read Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId)));
- return false;
- }
-
- freq = fmtContext->streams[streamId]->codec->sample_rate;
- if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) {
- len = (fmtContext->duration * freq) / AV_TIME_BASE;
- } else {
- len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den;
- }
for (int32 i = 0, l = fmtContext->nb_streams; i < l; ++i) {
AVStream *stream = fmtContext->streams[i];
@@ -2375,14 +2462,6 @@ public:
//}
}
- int64 duration() {
- return len;
- }
-
- int32 frequency() {
- return freq;
- }
-
int32 format() {
return 0;
}
@@ -2390,7 +2469,7 @@ public:
QString title() {
return _title;
}
-
+
QString performer() {
return _performer;
}
@@ -2413,77 +2492,14 @@ public:
}
~FFMpegAttributesReader() {
- if (ioContext) av_free(ioContext);
- if (_opened) {
- avformat_close_input(&fmtContext);
- } else if (ioBuffer) {
- av_free(ioBuffer);
- }
- if (fmtContext) avformat_free_context(fmtContext);
}
private:
- QString fname, data;
-
- int32 freq;
- int64 len;
QString _title, _performer;
QImage _cover;
QByteArray _coverBytes, _coverFormat;
- uchar *ioBuffer;
- AVIOContext *ioContext;
- AVFormatContext *fmtContext;
- AVCodec *codec;
- int32 streamId;
-
- bool _opened;
-
- static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
- FFMpegAttributesReader *l = reinterpret_cast(opaque);
-
- int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
- if (nbytes <= 0) {
- return 0;
- }
-
- memcpy(buf, l->data.constData() + l->dataPos, nbytes);
- l->dataPos += nbytes;
- return nbytes;
- }
-
- static int64_t _seek_data(void *opaque, int64_t offset, int whence) {
- FFMpegAttributesReader *l = reinterpret_cast(opaque);
-
- int32 newPos = -1;
- switch (whence) {
- case SEEK_SET: newPos = offset; break;
- case SEEK_CUR: newPos = l->dataPos + offset; break;
- case SEEK_END: newPos = l->data.size() + offset; break;
- }
- if (newPos < 0 || newPos > l->data.size()) {
- return -1;
- }
- l->dataPos = newPos;
- return l->dataPos;
- }
-
- static int _read_file(void *opaque, uint8_t *buf, int buf_size) {
- FFMpegAttributesReader *l = reinterpret_cast(opaque);
- return int(l->f.read((char*)(buf), buf_size));
- }
-
- static int64_t _seek_file(void *opaque, int64_t offset, int whence) {
- FFMpegAttributesReader *l = reinterpret_cast(opaque);
-
- switch (whence) {
- case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1;
- case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1;
- case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1;
- }
- return -1;
- }
};
MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat) {
@@ -2494,8 +2510,116 @@ MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteAr
cover = reader.cover();
coverBytes = reader.coverBytes();
coverFormat = reader.coverFormat();
- return MTP_documentAttributeAudio(MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()));
+ return MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer), MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()), MTPstring());
}
}
return MTP_documentAttributeFilename(MTP_string(fname));
}
+
+class FFMpegWaveformCounter : public FFMpegLoader {
+public:
+
+ FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data) {
+ }
+
+ bool open(qint64 position = 0) {
+ if (!FFMpegLoader::open(position)) {
+ return false;
+ }
+
+ QByteArray buffer;
+ buffer.reserve(AudioVoiceMsgBufferSize);
+ int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0;
+ if (duration() < WaveformSamplesCount) {
+ return false;
+ }
+
+ QVector peaks;
+ peaks.reserve(WaveformSamplesCount);
+
+ int32 fmt = format();
+ uint16 peak = 0;
+ while (processed < countbytes) {
+ buffer.resize(0);
+
+ int64 samples = 0;
+ int res = readMore(buffer, samples);
+ if (res < 0) {
+ break;
+ }
+ if (buffer.isEmpty()) {
+ continue;
+ }
+
+ const char *data = buffer.data();
+ if (fmt == AL_FORMAT_MONO8 || fmt == AL_FORMAT_STEREO8) {
+ for (int32 i = 0, l = buffer.size(); i + int32(sizeof(uchar)) <= l;) {
+ uint16 sample = qAbs((int32(*(uchar*)(data + i)) - 128) * 256);
+ if (peak < sample) {
+ peak = sample;
+ }
+
+ i += sizeof(uchar);
+ sumbytes += WaveformSamplesCount;
+ if (sumbytes >= countbytes) {
+ sumbytes -= countbytes;
+ peaks.push_back(peak);
+ peak = 0;
+ }
+ }
+ } else if (fmt == AL_FORMAT_MONO16 || fmt == AL_FORMAT_STEREO16) {
+ for (int32 i = 0, l = buffer.size(); i + int32(sizeof(uint16)) <= l;) {
+ uint16 sample = qAbs(int32(*(int16*)(data + i)));
+ if (peak < sample) {
+ peak = sample;
+ }
+
+ i += sizeof(uint16);
+ sumbytes += sizeof(uint16) * WaveformSamplesCount;
+ if (sumbytes >= countbytes) {
+ sumbytes -= countbytes;
+ peaks.push_back(peak);
+ peak = 0;
+ }
+ }
+ }
+ processed += sampleSize * samples;
+ }
+ if (sumbytes > 0 && peaks.size() < WaveformSamplesCount) {
+ peaks.push_back(peak);
+ }
+
+ if (peaks.isEmpty()) {
+ return false;
+ }
+
+ int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL);
+ peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
+
+ result.resize(peaks.size());
+ for (int32 i = 0, l = peaks.size(); i != l; ++i) {
+ result[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak));
+ }
+
+ return true;
+ }
+
+ const VoiceWaveform &waveform() const {
+ return result;
+ }
+
+ ~FFMpegWaveformCounter() {
+ }
+
+private:
+ VoiceWaveform result;
+
+};
+
+VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data) {
+ FFMpegWaveformCounter counter(file, data);
+ if (counter.open()) {
+ return counter.waveform();
+ }
+ return VoiceWaveform();
+}
diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h
index 9413233501..4eb75c88dd 100644
--- a/Telegram/SourceFiles/audio.h
+++ b/Telegram/SourceFiles/audio.h
@@ -16,11 +16,11 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
-#include "types.h"
+#include "basic_types.h"
void audioInit();
bool audioWorks();
@@ -56,7 +56,7 @@ public:
void play(const AudioMsgId &audio, int64 position = 0);
void play(const SongMsgId &song, int64 position = 0);
void pauseresume(MediaOverviewType type, bool fast = false);
- void seek(int64 position); // type == OverviewDocuments
+ void seek(int64 position); // type == OverviewFiles
void stop(MediaOverviewType type);
void stopAndClear();
@@ -201,8 +201,8 @@ signals:
void captureOnStart();
void captureOnStop(bool needResult);
- void onDone(QByteArray data, qint32 samples);
- void onUpdate(qint16 level, qint32 samples);
+ void onDone(QByteArray data, VoiceWaveform waveform, qint32 samples);
+ void onUpdate(quint16 level, qint32 samples);
void onError();
private:
@@ -338,8 +338,8 @@ public:
signals:
void error();
- void update(qint16 level, qint32 samples);
- void done(QByteArray data, qint32 samples);
+ void update(quint16 level, qint32 samples);
+ void done(QByteArray data, VoiceWaveform waveform, qint32 samples);
public slots:
@@ -360,3 +360,4 @@ private:
};
MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat);
+VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data);
diff --git a/Telegram/SourceFiles/autoupdater.cpp b/Telegram/SourceFiles/autoupdater.cpp
index a1a77a4ff0..6c46f60c18 100644
--- a/Telegram/SourceFiles/autoupdater.cpp
+++ b/Telegram/SourceFiles/autoupdater.cpp
@@ -16,13 +16,26 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
+
+#include "autoupdater.h"
+
+#include
+#include
+#include
+#include
+
+#ifdef Q_OS_WIN // use Lzma SDK for win
+#include
+#else // Q_OS_WIN
+#include
+#endif // else of Q_OS_WIN
+
#include "application.h"
#include "pspecific.h"
-#include "autoupdater.h"
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
@@ -34,7 +47,7 @@ typedef int VerInt;
typedef wchar_t VerChar;
#endif
-UpdateDownloader::UpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
+UpdateChecker::UpdateChecker(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
updateUrl = url;
moveToThread(thread);
manager.moveToThread(thread);
@@ -44,14 +57,14 @@ UpdateDownloader::UpdateDownloader(QThread *thread, const QString &url) : reply(
initOutput();
}
-void UpdateDownloader::initOutput() {
+void UpdateChecker::initOutput() {
QString fileName;
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
if (m.hasMatch()) {
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
}
if (fileName.isEmpty()) {
- fileName = qsl("tupdate-%1").arg(MTP::nonce() % 1000000);
+ fileName = qsl("tupdate-%1").arg(rand_value() % 1000000);
}
QString dirStr = cWorkingDir() + qsl("tupdates/");
fileName = dirStr + fileName;
@@ -99,11 +112,11 @@ void UpdateDownloader::initOutput() {
}
}
-void UpdateDownloader::start() {
+void UpdateChecker::start() {
sendRequest();
}
-void UpdateDownloader::sendRequest() {
+void UpdateChecker::sendRequest() {
QNetworkRequest req(updateUrl);
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";
req.setRawHeader("Range", rangeHeaderValue);
@@ -115,7 +128,7 @@ void UpdateDownloader::sendRequest() {
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
}
-void UpdateDownloader::partMetaGot() {
+void UpdateChecker::partMetaGot() {
typedef QList Pairs;
Pairs pairs = reply->rawHeaderPairs();
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
@@ -126,23 +139,24 @@ void UpdateDownloader::partMetaGot() {
QMutexLocker lock(&mutex);
full = m.captured(1).toInt();
}
- emit App::app()->updateDownloading(already, full);
+
+ Sandbox::updateProgress(already, full);
}
}
}
}
-int32 UpdateDownloader::ready() {
+int32 UpdateChecker::ready() {
QMutexLocker lock(&mutex);
return already;
}
-int32 UpdateDownloader::size() {
+int32 UpdateChecker::size() {
QMutexLocker lock(&mutex);
return full;
}
-void UpdateDownloader::partFinished(qint64 got, qint64 total) {
+void UpdateChecker::partFinished(qint64 got, qint64 total) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
@@ -179,11 +193,11 @@ void UpdateDownloader::partFinished(qint64 got, qint64 total) {
outputFile.close();
unpackUpdate();
} else {
- emit App::app()->updateDownloading(already, full);
+ Sandbox::updateProgress(already, full);
}
}
-void UpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
+void UpdateChecker::partFailed(QNetworkReply::NetworkError e) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
@@ -198,15 +212,15 @@ void UpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
}
}
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
- emit App::app()->updateFailed();
+ Sandbox::updateFailed();
}
-void UpdateDownloader::fatalFail() {
+void UpdateChecker::fatalFail() {
clearAll();
- emit App::app()->updateFailed();
+ Sandbox::updateFailed();
}
-void UpdateDownloader::clearAll() {
+void UpdateChecker::clearAll() {
psDeleteDir(cWorkingDir() + qsl("tupdates"));
}
@@ -225,7 +239,7 @@ void UpdateDownloader::clearAll() {
// return QString::fromWCharArray(errMsg);
//}
-void UpdateDownloader::unpackUpdate() {
+void UpdateChecker::unpackUpdate() {
QByteArray packed;
if (!outputFile.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!"));
@@ -465,10 +479,10 @@ void UpdateDownloader::unpackUpdate() {
}
outputFile.remove();
- emit App::app()->updateReady();
+ Sandbox::updateReady();
}
-UpdateDownloader::~UpdateDownloader() {
+UpdateChecker::~UpdateChecker() {
delete reply;
reply = 0;
}
@@ -477,7 +491,7 @@ bool checkReadyUpdate() {
QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp");
if (!QFile(readyFilePath).exists()) {
if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) {
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
}
return false;
}
@@ -488,30 +502,30 @@ bool checkReadyUpdate() {
QFile fVersion(versionPath);
if (!fVersion.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
VerInt versionNum;
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
if (versionNum == 0x7FFFFFFF) { // beta version
quint64 betaVersion = 0;
if (fVersion.read((char*)&betaVersion, sizeof(quint64)) != sizeof(quint64)) {
LOG(("Update Error: cant read beta version from file '%1'").arg(versionPath));
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
if (!cBetaVersion() || betaVersion <= cBetaVersion()) {
LOG(("Update Error: cant install beta version %1 having beta version %2").arg(betaVersion).arg(cBetaVersion()));
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
} else if (versionNum <= AppVersion) {
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
fVersion.close();
@@ -530,11 +544,11 @@ bool checkReadyUpdate() {
if (!updater.exists()) {
QFileInfo current(curUpdater);
if (!current.exists()) {
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
}
@@ -545,24 +559,24 @@ bool checkReadyUpdate() {
cSetWriteProtected(true);
return true;
} else {
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
}
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
#elif defined Q_OS_MAC
QDir().mkpath(QFileInfo(curUpdater).absolutePath());
- DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater));
+ DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater));
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
#elif defined Q_OS_LINUX
if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
- UpdateDownloader::clearAll();
+ UpdateChecker::clearAll();
return false;
}
#endif
@@ -578,7 +592,7 @@ QString countBetaVersionSignature(uint64 version) { // duplicated in packer.cpp
}
QByteArray signedData = (qstr("TelegramBeta_") + QString::number(version, 16).toLower()).toUtf8();
-
+
static const int32 shaSize = 20, keySize = 128;
uchar sha1Buffer[shaSize];
diff --git a/Telegram/SourceFiles/autoupdater.h b/Telegram/SourceFiles/autoupdater.h
index e2f2a9ac9b..3687a405dc 100644
--- a/Telegram/SourceFiles/autoupdater.h
+++ b/Telegram/SourceFiles/autoupdater.h
@@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
@@ -26,11 +26,11 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
#include
#include
-class UpdateDownloader : public QObject {
+class UpdateChecker : public QObject {
Q_OBJECT
public:
- UpdateDownloader(QThread *thread, const QString &url);
+ UpdateChecker(QThread *thread, const QString &url);
void unpackUpdate();
@@ -39,7 +39,7 @@ public:
static void clearAll();
- ~UpdateDownloader();
+ ~UpdateChecker();
public slots:
@@ -66,6 +66,11 @@ private:
bool checkReadyUpdate();
+#else
+class UpdateChecker : public QObject {
+ Q_OBJECT
+};
+
#endif
QString countBetaVersionSignature(uint64 version);
diff --git a/Telegram/SourceFiles/types.cpp b/Telegram/SourceFiles/basic_types.cpp
similarity index 92%
rename from Telegram/SourceFiles/types.cpp
rename to Telegram/SourceFiles/basic_types.cpp
index 25a198f234..5d1bc68749 100644
--- a/Telegram/SourceFiles/types.cpp
+++ b/Telegram/SourceFiles/basic_types.cpp
@@ -16,10 +16,20 @@ In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
-Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
+#include "basic_types.h"
+
+#include
+#include
+
+extern "C" {
+#include
+#include
+}
+
#include "application.h"
uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 };
@@ -35,6 +45,10 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 };
// Base types compile-time check
+#ifdef TDESKTOP_CUSTOM_NULLPTR
+NullPointerClass nullptr;
+#endif
+
namespace {
template
class _TypeSizeCheckerHelper {
@@ -99,15 +113,15 @@ namespace {
clock_gettime(CLOCK_REALTIME, &ts);
_msgIdMsStart = 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec);
#endif
-
+
uint32 msgIdRand;
memset_rand(&msgIdRand, sizeof(uint32));
_msgIdStart = (((uint64)((uint32)unixtime()) << 32) | (uint64)msgIdRand);
}
}
-int32 myunixtime() {
- return (int32)time(NULL);
+TimeId myunixtime() {
+ return (TimeId)time(NULL);
}
void unixtimeInit() {
@@ -135,19 +149,19 @@ void unixtimeSet(int32 serverTime, bool force) {
_initMsgIdConstants();
}
-int32 unixtime() {
- int32 result = myunixtime();
+TimeId unixtime() {
+ TimeId result = myunixtime();
QReadLocker locker(&unixtimeLock);
return result + unixtimeDelta;
}
-int32 fromServerTime(const MTPint &serverTime) {
+TimeId fromServerTime(const MTPint &serverTime) {
QReadLocker locker(&unixtimeLock);
return serverTime.v - unixtimeDelta;
}
-MTPint toServerTime(const int32 &clientTime) {
+MTPint toServerTime(const TimeId &clientTime) {
QReadLocker locker(&unixtimeLock);
return MTP_int(clientTime + unixtimeDelta);
}
@@ -187,6 +201,32 @@ namespace {
delete l;
}
+ int _ffmpegLockManager(void **mutex, AVLockOp op) {
+ switch (op) {
+ case AV_LOCK_CREATE: {
+ t_assert(*mutex == 0);
+ *mutex = reinterpret_cast(new QMutex());
+ } break;
+
+ case AV_LOCK_OBTAIN: {
+ t_assert(*mutex != 0);
+ reinterpret_cast(*mutex)->lock();
+ } break;
+
+ case AV_LOCK_RELEASE: {
+ t_assert(*mutex != 0);
+ reinterpret_cast(*mutex)->unlock();
+ }; break;
+
+ case AV_LOCK_DESTROY: {
+ t_assert(*mutex != 0);
+ delete reinterpret_cast(*mutex);
+ *mutex = 0;
+ } break;
+ }
+ return 0;
+ }
+
float64 _msFreq;
float64 _msgIdCoef;
int64 _msStart = 0, _msAddToMsStart = 0, _msAddToUnixtime = 0;
@@ -238,36 +278,51 @@ namespace {
_MsStarter _msStarter;
}
-InitOpenSSL::InitOpenSSL() {
- if (!RAND_status()) { // should be always inited in all modern OS
- char buf[16];
- memcpy(buf, &_msStart, 8);
- memcpy(buf + 8, &_msFreq, 8);
- uchar sha256Buffer[32];
- RAND_seed(hashSha256(buf, 16, sha256Buffer), 32);
- if (!RAND_status()) {
- LOG(("MTP Error: Could not init OpenSSL rand, RAND_status() is 0.."));
+namespace ThirdParty {
+
+ void start() {
+ PlatformSpecific::ThirdParty::start();
+
+ if (!RAND_status()) { // should be always inited in all modern OS
+ char buf[16];
+ memcpy(buf, &_msStart, 8);
+ memcpy(buf + 8, &_msFreq, 8);
+ uchar sha256Buffer[32];
+ RAND_seed(hashSha256(buf, 16, sha256Buffer), 32);
+ if (!RAND_status()) {
+ LOG(("MTP Error: Could not init OpenSSL rand, RAND_status() is 0..."));
+ }
}
+
+ int32 numLocks = CRYPTO_num_locks();
+ if (numLocks) {
+ _sslLocks = new QMutex[numLocks];
+ CRYPTO_set_locking_callback(_sslLockingCallback);
+ } else {
+ LOG(("MTP Error: Could not init OpenSSL threads, CRYPTO_num_locks() returned zero!"));
+ }
+ CRYPTO_THREADID_set_callback(_sslThreadId);
+ CRYPTO_set_dynlock_create_callback(_sslCreateFunction);
+ CRYPTO_set_dynlock_lock_callback(_sslLockFunction);
+ CRYPTO_set_dynlock_destroy_callback(_sslDestroyFunction);
+
+ av_register_all();
+ avcodec_register_all();
+
+ av_lockmgr_register(_ffmpegLockManager);
+
+ _sslInited = true;
}
- int32 numLocks = CRYPTO_num_locks();
- if (numLocks) {
- _sslLocks = new QMutex[numLocks];
- CRYPTO_set_locking_callback(_sslLockingCallback);
- } else {
- LOG(("MTP Error: Could not init OpenSSL threads, CRYPTO_num_locks() returned zero!"));
+ void finish() {
+ av_lockmgr_register(0);
+
+ delete[] _sslLocks;
+ _sslLocks = 0;
+
+ PlatformSpecific::ThirdParty::finish();
}
- CRYPTO_THREADID_set_callback(_sslThreadId);
- CRYPTO_set_dynlock_create_callback(_sslCreateFunction);
- CRYPTO_set_dynlock_lock_callback(_sslLockFunction);
- CRYPTO_set_dynlock_destroy_callback(_sslDestroyFunction);
- _sslInited = true;
-}
-
-InitOpenSSL::~InitOpenSSL() {
- delete[] _sslLocks;
- _sslLocks = 0;
}
bool checkms() {
@@ -640,10 +695,7 @@ char *hashMd5Hex(const int32 *hashmd5, void *dest) {
}
void memset_rand(void *data, uint32 len) {
- if (!_sslInited) {
- LOG(("Critical Error: memset_rand() called before OpenSSL init!"));
- exit(-1);
- }
+ t_assert(_sslInited);
RAND_bytes((uchar*)data, len);
}
@@ -777,8 +829,7 @@ QString translitRusEng(const QString &rus) {
result.reserve(rus.size() * 2);
int32 toSkip = 0;
- for (QString::const_iterator i = rus.cbegin(), e = rus.cend(); i != e;) {
- i += toSkip;
+ for (QString::const_iterator i = rus.cbegin(), e = rus.cend(); i != e; i += toSkip) {
result += translitLetterRusEng(*i, (i + 1 == e) ? ' ' : *(i + 1), toSkip);
}
return result;
@@ -982,3 +1033,33 @@ MimeType mimeTypeForData(const QByteArray &data) {
}
return MimeType(QMimeDatabase().mimeTypeForData(data));
}
+
+struct ComposerMetadatasMap {
+ QMap data;
+ ~ComposerMetadatasMap() {
+ for_const (const ComposerMetadata *p, data) {
+ delete p;
+ }
+ }
+};
+
+const ComposerMetadata *GetComposerMetadata(uint64 mask) {
+ static ComposerMetadatasMap ComposerMetadatas;
+ static QMutex ComposerMetadatasMutex;
+
+ QMutexLocker lock(&ComposerMetadatasMutex);
+ auto i = ComposerMetadatas.data.constFind(mask);
+ if (i == ComposerMetadatas.data.cend()) {
+ ComposerMetadata *meta = new ComposerMetadata(mask);
+ t_assert(meta != nullptr);
+
+ i = ComposerMetadatas.data.insert(mask, meta);
+ }
+ return i.value();
+}
+
+const ComposerMetadata *Composer::ZeroComposerMetadata = GetComposerMetadata(0);
+
+ComponentWrapStruct ComponentWraps[64];
+
+QAtomicInt ComponentIndexLast;
diff --git a/Telegram/SourceFiles/basic_types.h b/Telegram/SourceFiles/basic_types.h
new file mode 100644
index 0000000000..62c9c40564
--- /dev/null
+++ b/Telegram/SourceFiles/basic_types.h
@@ -0,0 +1,1362 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop 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.
+
+It 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.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+template
+void deleteAndMark(T *&link) {
+ delete link;
+ link = reinterpret_cast(0x00000BAD);
+}
+
+template
+T *getPointerAndReset(T *&ptr) {
+ T *result = nullptr;
+ qSwap(result, ptr);
+ return result;
+}
+
+struct NullType {
+};
+
+// ordered set template based on QMap
+template
+class OrderedSet {
+ typedef OrderedSet Self;
+ typedef QMap Impl;
+ typedef typename Impl::iterator IteratorImpl;
+ typedef typename Impl::const_iterator ConstIteratorImpl;
+ Impl impl_;
+
+public:
+
+ inline bool operator==(const Self &other) const { return impl_ == other.impl_; }
+ inline bool operator!=(const Self &other) const { return impl_ != other.impl_; }
+ inline int size() const { return impl_.size(); }
+ inline bool isEmpty() const { return impl_.isEmpty(); }
+ inline void detach() { return impl_.detach(); }
+ inline bool isDetached() const { return impl_.isDetached(); }
+ inline void clear() { return impl_.clear(); }
+ inline QList values() const { return impl_.keys(); }
+ inline const T &first() const { return impl_.firstKey(); }
+ inline const T &last() const { return impl_.lastKey(); }
+
+ class const_iterator;
+ class iterator {
+ public:
+ typedef typename IteratorImpl::iterator_category iterator_category;
+ typedef typename IteratorImpl::difference_type difference_type;
+ typedef T value_type;
+ typedef T *pointer;
+ typedef T &reference;
+
+ explicit iterator(const IteratorImpl &impl) : impl_(impl) {
+ }
+ inline const T &operator*() const { return impl_.key(); }
+ inline const T *operator->() const { return &impl_.key(); }
+ inline bool operator==(const iterator &other) const { return impl_ == other.impl_; }
+ inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; }
+ inline iterator &operator++() { ++impl_; return *this; }
+ inline iterator operator++(int) { return iterator(impl_++); }
+ inline iterator &operator--() { --impl_; return *this; }
+ inline iterator operator--(int) { return iterator(impl_--); }
+ inline iterator operator+(int j) const { return iterator(impl_ + j); }
+ inline iterator operator-(int j) const { return iterator(impl_ - j); }
+ inline iterator &operator+=(int j) { impl_ += j; return *this; }
+ inline iterator &operator-=(int j) { impl_ -= j; return *this; }
+
+ friend class const_iterator;
+ inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; }
+ inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; }
+
+ private:
+ IteratorImpl impl_;
+ friend class OrderedSet;
+
+ };
+ friend class iterator;
+
+ class const_iterator {
+ public:
+ typedef typename IteratorImpl::iterator_category iterator_category;
+ typedef typename IteratorImpl::difference_type difference_type;
+ typedef T value_type;
+ typedef T *pointer;
+ typedef T &reference;
+
+ explicit const_iterator(const ConstIteratorImpl &impl) : impl_(impl) {
+ }
+ inline const T &operator*() const { return impl_.key(); }
+ inline const T *operator->() const { return &impl_.key(); }
+ inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; }
+ inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; }
+ inline const_iterator &operator++() { ++impl_; return *this; }
+ inline const_iterator operator++(int) { return const_iterator(impl_++); }
+ inline const_iterator &operator--() { --impl_; return *this; }
+ inline const_iterator operator--(int) { return const_iterator(impl_--); }
+ inline const_iterator operator+(int j) const { return const_iterator(impl_ + j); }
+ inline const_iterator operator-(int j) const { return const_iterator(impl_ - j); }
+ inline const_iterator &operator+=(int j) { impl_ += j; return *this; }
+ inline const_iterator &operator-=(int j) { impl_ -= j; return *this; }
+
+ friend class iterator;
+ inline bool operator==(const iterator &other) const { return impl_ == other.impl_; }
+ inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; }
+
+ private:
+ ConstIteratorImpl impl_;
+ friend class OrderedSet;
+
+ };
+ friend class const_iterator;
+
+ // STL style
+ inline iterator begin() { return iterator(impl_.begin()); }
+ inline const_iterator begin() const { return const_iterator(impl_.cbegin()); }
+ inline const_iterator constBegin() const { return const_iterator(impl_.cbegin()); }
+ inline const_iterator cbegin() const { return const_iterator(impl_.cbegin()); }
+ inline iterator end() { detach(); return iterator(impl_.end()); }
+ inline const_iterator end() const { return const_iterator(impl_.cend()); }
+ inline const_iterator constEnd() const { return const_iterator(impl_.cend()); }
+ inline const_iterator cend() const { return const_iterator(impl_.cend()); }
+ inline iterator erase(iterator it) { return iterator(impl_.erase(it.impl_)); }
+
+ inline iterator insert(const T &value) { return iterator(impl_.insert(value, NullType())); }
+ inline iterator insert(const_iterator pos, const T &value) { return iterator(impl_.insert(pos.impl_, value, NullType())); }
+ inline int remove(const T &value) { return impl_.remove(value); }
+ inline bool contains(const T &value) const { return impl_.contains(value); }
+
+ // more Qt
+ typedef iterator Iterator;
+ typedef const_iterator ConstIterator;
+ inline int count() const { return impl_.count(); }
+ inline iterator find(const T &value) { return iterator(impl_.find(value)); }
+ inline const_iterator find(const T &value) const { return const_iterator(impl_.constFind(value)); }
+ inline const_iterator constFind(const T &value) const { return const_iterator(impl_.constFind(value)); }
+ inline Self &unite(const Self &other) { impl_.unite(other.impl_); return *this; }
+
+ // STL compatibility
+ typedef typename Impl::difference_type difference_type;
+ typedef typename Impl::size_type size_type;
+ inline bool empty() const { return impl_.empty(); }
+
+};
+
+// thanks Chromium see https://blogs.msdn.microsoft.com/the1/2004/05/07/how-would-you-get-the-count-of-an-array-in-c-2/
+template char(&ArraySizeHelper(T(&array)[N]))[N];
+#define arraysize(array) (sizeof(ArraySizeHelper(array)))
+
+#define qsl(s) QStringLiteral(s)
+#define qstr(s) QLatin1String(s, sizeof(s) - 1)
+
+// using for_const instead of plain range-based for loop to ensure usage of const_iterator
+// it is important for the copy-on-write Qt containers
+// if you have "QVector v" then "for (T * const p : v)" will still call QVector::detach(),
+// while "for_const (T *p, v)" won't and "for_const (T *&p, v)" won't compile
+#define for_const(range_declaration, range_expression) for (range_declaration : std_::as_const(range_expression))
+
+template
+inline QFlags qFlags(Enum v) {
+ return QFlags(v);
+}
+
+//typedef unsigned char uchar; // Qt has uchar
+typedef qint16 int16;
+typedef quint16 uint16;
+typedef qint32 int32;
+typedef quint32 uint32;
+typedef qint64 int64;
+typedef quint64 uint64;
+
+static const int32 ScrollMax = INT_MAX;
+
+extern uint64 _SharedMemoryLocation[];
+template
+T *SharedMemoryLocation() {
+ static_assert(N < 4, "Only 4 shared memory locations!");
+ return reinterpret_cast(_SharedMemoryLocation + N);
+}
+
+// see https://github.com/boostcon/cppnow_presentations_2012/blob/master/wed/schurr_cpp11_tools_for_class_authors.pdf
+class str_const { // constexpr string
+public:
+ template
+ constexpr str_const(const char(&a)[N]) : _str(a), _size(N - 1) {
+ }
+ constexpr char operator[](std::size_t n) const {
+ return (n < _size) ? _str[n] :
+ throw std::out_of_range("");
+ }
+ constexpr std::size_t size() const { return _size; }
+ const char *c_str() const { return _str; }
+
+private:
+ const char* const _str;
+ const std::size_t _size;
+
+};
+
+template
+inline void accumulate_max(T &a, const T &b) { if (a < b) a = b; }
+
+template
+inline void accumulate_min(T &a, const T &b) { if (a > b) a = b; }
+
+#ifdef Q_OS_WIN
+typedef float float32;
+typedef double float64;
+#else
+typedef float float32;
+typedef double float64;
+#endif
+
+#include
+#include
+
+#include
+
+#include
+
+using std::string;
+using std::exception;
+
+// we copy some parts of C++11/14/17 std:: library, because on OS X 10.6+
+// version we can use C++11/14/17, but we can not use its library :(
+namespace std_ {
+
+template
+struct integral_constant {
+ static constexpr T value = V;
+
+ using value_type = T;
+ using type = integral_constant;
+
+ constexpr operator value_type() const noexcept {
+ return (value);
+ }
+
+ constexpr value_type operator()() const noexcept {
+ return (value);
+ }
+};
+
+using true_type = integral_constant;
+using false_type = integral_constant;
+
+template
+struct remove_reference {
+ using type = T;
+};
+template
+struct remove_reference {
+ using type = T;
+};
+template
+struct remove_reference {
+ using type = T;
+};
+
+template
+struct is_lvalue_reference : false_type {
+};
+template
+struct is_lvalue_reference : true_type {
+};
+
+template
+struct is_rvalue_reference : false_type {
+};
+template
+struct is_rvalue_reference : true_type {
+};
+
+template
+inline constexpr T &&forward(typename remove_reference::type &value) noexcept {
+ return static_cast(value);
+}
+template
+inline constexpr T &&forward(typename remove_reference::type &&value) noexcept {
+ static_assert(!is_lvalue_reference::value, "bad forward call");
+ return static_cast(value);
+}
+
+template
+inline constexpr typename remove_reference::type &&move(T &&value) noexcept {
+ return static_cast::type&&>(value);
+}
+
+template
+struct add_const {
+ using type = const T;
+};
+template
+using add_const_t = typename add_const::type;
+template
+constexpr add_const_t &as_const(T& t) noexcept {
+ return t;
+}
+template
+void as_const(const T&&) = delete;
+
+} // namespace std_
+
+#include "logs.h"
+
+static volatile int *t_assert_nullptr = nullptr;
+inline void t_noop() {}
+inline void t_assert_fail(const char *message, const char *file, int32 line) {
+ QString info(qsl("%1 %2:%3").arg(message).arg(file).arg(line));
+ LOG(("Assertion Failed! %1 %2:%3").arg(info));
+ SignalHandlers::setCrashAnnotation("Assertion", info);
+ *t_assert_nullptr = 0;
+}
+#define t_assert_full(condition, message, file, line) ((!(condition)) ? t_assert_fail(message, file, line) : t_noop())
+#define t_assert_c(condition, comment) t_assert_full(condition, "\"" #condition "\" (" comment ")", __FILE__, __LINE__)
+#define t_assert(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
+
+class Exception : public exception {
+public:
+
+ Exception(const QString &msg, bool isFatal = true) : _fatal(isFatal), _msg(msg.toUtf8()) {
+ LOG(("Exception: %1").arg(msg));
+ }
+ bool fatal() const {
+ return _fatal;
+ }
+
+ virtual const char *what() const throw() {
+ return _msg.constData();
+ }
+ virtual ~Exception() throw() {
+ }
+
+private:
+ bool _fatal;
+ QByteArray _msg;
+};
+
+class MTPint;
+typedef int32 TimeId;
+TimeId myunixtime();
+void unixtimeInit();
+void unixtimeSet(TimeId servertime, bool force = false);
+TimeId unixtime();
+TimeId fromServerTime(const MTPint &serverTime);
+uint64 msgid();
+int32 reqid();
+
+inline QDateTime date(int32 time = -1) {
+ QDateTime result;
+ if (time >= 0) result.setTime_t(time);
+ return result;
+}
+
+inline QDateTime date(const MTPint &time) {
+ return date(fromServerTime(time));
+}
+
+inline void mylocaltime(struct tm * _Tm, const time_t * _Time) {
+#ifdef Q_OS_WIN
+ localtime_s(_Tm, _Time);
+#else
+ localtime_r(_Time, _Tm);
+#endif
+}
+
+namespace ThirdParty {
+
+ void start();
+ void finish();
+
+}
+
+bool checkms(); // returns true if time has changed
+uint64 getms(bool checked = false);
+
+class SingleTimer : public QTimer { // single shot timer with check
+ Q_OBJECT
+
+public:
+
+ SingleTimer();
+
+ void setSingleShot(bool); // is not available
+ void start(); // is not available
+
+public slots:
+
+ void start(int msec);
+ void startIfNotActive(int msec);
+ void adjust() {
+ uint64 n = getms(true);
+ if (isActive()) {
+ if (n >= _finishing) {
+ start(0);
+ } else {
+ start(_finishing - n);
+ }
+ }
+ }
+
+private:
+ uint64 _finishing;
+ bool _inited;
+
+};
+
+const static uint32 _md5_block_size = 64;
+class HashMd5 {
+public:
+
+ HashMd5(const void *input = 0, uint32 length = 0);
+ void feed(const void *input, uint32 length);
+ int32 *result();
+
+private:
+
+ void init();
+ void finalize();
+ void transform(const uchar *block);
+
+ bool _finalized;
+ uchar _buffer[_md5_block_size];
+ uint32 _count[2];
+ uint32 _state[4];
+ uchar _digest[16];
+
+};
+
+int32 hashCrc32(const void *data, uint32 len);
+int32 *hashSha1(const void *data, uint32 len, void *dest); // dest - ptr to 20 bytes, returns (int32*)dest
+int32 *hashSha256(const void *data, uint32 len, void *dest); // dest - ptr to 32 bytes, returns (int32*)dest
+int32 *hashMd5(const void *data, uint32 len, void *dest); // dest = ptr to 16 bytes, returns (int32*)dest
+char *hashMd5Hex(const int32 *hashmd5, void *dest); // dest = ptr to 32 bytes, returns (char*)dest
+inline char *hashMd5Hex(const void *data, uint32 len, void *dest) { // dest = ptr to 32 bytes, returns (char*)dest
+ return hashMd5Hex(HashMd5(data, len).result(), dest);
+}
+
+// good random (using openssl implementation)
+void memset_rand(void *data, uint32 len);
+template
+T rand_value() {
+ T result;
+ memset_rand(&result, sizeof(result));
+ return result;
+}
+
+inline void memset_rand_bad(void *data, uint32 len) {
+ for (uchar *i = reinterpret_cast(data), *e = i + len; i != e; ++i) {
+ *i = uchar(rand() & 0xFF);
+ }
+}
+
+template
+inline void memsetrnd_bad(T &value) {
+ memset_rand_bad(&value, sizeof(value));
+}
+
+class ReadLockerAttempt {
+public:
+
+ ReadLockerAttempt(QReadWriteLock *_lock) : success(_lock->tryLockForRead()), lock(_lock) {
+ }
+ ~ReadLockerAttempt() {
+ if (success) {
+ lock->unlock();
+ }
+ }
+
+ operator bool() const {
+ return success;
+ }
+
+private:
+
+ bool success;
+ QReadWriteLock *lock;
+
+};
+
+inline QString fromUtf8Safe(const char *str, int32 size = -1) {
+ if (!str || !size) return QString();
+ if (size < 0) size = int32(strlen(str));
+ QString result(QString::fromUtf8(str, size));
+ QByteArray back = result.toUtf8();
+ if (back.size() != size || memcmp(back.constData(), str, size)) return QString::fromLocal8Bit(str, size);
+ return result;
+}
+
+inline QString fromUtf8Safe(const QByteArray &str) {
+ return fromUtf8Safe(str.constData(), str.size());
+}
+
+static const QRegularExpression::PatternOptions reMultiline(QRegularExpression::DotMatchesEverythingOption | QRegularExpression::MultilineOption);
+
+template
+inline T snap(const T &v, const T &_min, const T &_max) {
+ return (v < _min) ? _min : ((v > _max) ? _max : v);
+}
+
+template
+class ManagedPtr {
+public:
+ ManagedPtr() : ptr(0) {
+ }
+ ManagedPtr(T *p) : ptr(p) {
+ }
+ T *operator->() const {
+ return ptr;
+ }
+ T *v() const {
+ return ptr;
+ }
+
+protected:
+
+ T *ptr;
+ typedef ManagedPtr Parent;
+};
+
+QString translitRusEng(const QString &rus);
+QString rusKeyboardLayoutSwitch(const QString &from);
+
+enum DBISendKey {
+ dbiskEnter = 0,
+ dbiskCtrlEnter = 1,
+};
+
+enum DBINotifyView {
+ dbinvShowPreview = 0,
+ dbinvShowName = 1,
+ dbinvShowNothing = 2,
+};
+
+enum DBIWorkMode {
+ dbiwmWindowAndTray = 0,
+ dbiwmTrayOnly = 1,
+ dbiwmWindowOnly = 2,
+};
+
+enum DBIConnectionType {
+ dbictAuto = 0,
+ dbictHttpAuto = 1, // not used
+ dbictHttpProxy = 2,
+ dbictTcpProxy = 3,
+};
+
+enum DBIDefaultAttach {
+ dbidaDocument = 0,
+ dbidaPhoto = 1,
+};
+
+struct ConnectionProxy {
+ ConnectionProxy() : port(0) {
+ }
+ QString host;
+ uint32 port;
+ QString user, password;
+};
+
+enum DBIScale {
+ dbisAuto = 0,
+ dbisOne = 1,
+ dbisOneAndQuarter = 2,
+ dbisOneAndHalf = 3,
+ dbisTwo = 4,
+
+ dbisScaleCount = 5,
+};
+
+static const int MatrixRowShift = 40000;
+
+enum DBIEmojiTab {
+ dbietRecent = -1,
+ dbietPeople = 0,
+ dbietNature = 1,
+ dbietFood = 2,
+ dbietActivity = 3,
+ dbietTravel = 4,
+ dbietObjects = 5,
+ dbietSymbols = 6,
+ dbietStickers = 666,
+};
+static const int emojiTabCount = 8;
+inline DBIEmojiTab emojiTabAtIndex(int index) {
+ return (index < 0 || index >= emojiTabCount) ? dbietRecent : DBIEmojiTab(index - 1);
+}
+
+enum DBIPlatform {
+ dbipWindows = 0,
+ dbipMac = 1,
+ dbipLinux64 = 2,
+ dbipLinux32 = 3,
+ dbipMacOld = 4,
+};
+
+enum DBIPeerReportSpamStatus {
+ dbiprsNoButton = 0, // hidden, but not in the cloud settings yet
+ dbiprsUnknown = 1, // contacts not loaded yet
+ dbiprsShowButton = 2, // show report spam button, each show peer request setting from cloud
+ dbiprsReportSent = 3, // report sent, but the report spam panel is not hidden yet
+ dbiprsHidden = 4, // hidden in the cloud or not needed (bots, contacts, etc), no more requests
+ dbiprsRequesting = 5, // requesting the cloud setting right now
+};
+
+typedef enum {
+ HitTestNone = 0,
+ HitTestClient,
+ HitTestSysButton,
+ HitTestIcon,
+ HitTestCaption,
+ HitTestTop,
+ HitTestTopRight,
+ HitTestRight,
+ HitTestBottomRight,
+ HitTestBottom,
+ HitTestBottomLeft,
+ HitTestLeft,
+ HitTestTopLeft,
+} HitTestType;
+
+inline QString strMakeFromLetters(const uint32 *letters, int32 len) {
+ QString result;
+ result.reserve(len);
+ for (int32 i = 0; i < len; ++i) {
+ result.push_back(QChar((((letters[i] << 16) & 0xFF) >> 8) | (letters[i] & 0xFF)));
+ }
+ return result;
+}
+
+class MimeType {
+public:
+
+ enum TypeEnum {
+ Unknown,
+ WebP,
+ };
+
+ MimeType(const QMimeType &type) : _typeStruct(type), _type(Unknown) {
+ }
+ MimeType(TypeEnum type) : _type(type) {
+ }
+ QStringList globPatterns() const;
+ QString filterString() const;
+ QString name() const;
+
+private:
+
+ QMimeType _typeStruct;
+ TypeEnum _type;
+
+};
+
+MimeType mimeTypeForName(const QString &mime);
+MimeType mimeTypeForFile(const QFileInfo &file);
+MimeType mimeTypeForData(const QByteArray &data);
+
+#include
+
+inline int rowscount(int fullCount, int countPerRow) {
+ return (fullCount + countPerRow - 1) / countPerRow;
+}
+inline int floorclamp(int value, int step, int lowest, int highest) {
+ return qMin(qMax(value / step, lowest), highest);
+}
+inline int floorclamp(float64 value, int step, int lowest, int highest) {
+ return qMin(qMax(static_cast(std::floor(value / step)), lowest), highest);
+}
+inline int ceilclamp(int value, int step, int lowest, int highest) {
+ return qMax(qMin((value + step - 1) / step, highest), lowest);
+}
+inline int ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) {
+ return qMax(qMin(static_cast(std::ceil(value / step)), highest), lowest);
+}
+
+enum ForwardWhatMessages {
+ ForwardSelectedMessages,
+ ForwardContextMessage,
+ ForwardPressedMessage,
+ ForwardPressedLinkMessage
+};
+
+enum ShowLayerOption {
+ CloseOtherLayers = 0x00,
+ KeepOtherLayers = 0x01,
+ ShowAfterOtherLayers = 0x03,
+
+ AnimatedShowLayer = 0x00,
+ ForceFastShowLayer = 0x04,
+};
+typedef QFlags ShowLayerOptions;
+
+static int32 FullArcLength = 360 * 16;
+static int32 QuarterArcLength = (FullArcLength / 4);
+static int32 MinArcLength = (FullArcLength / 360);
+static int32 AlmostFullArcLength = (FullArcLength - MinArcLength);
+
+template
+class RefPairImplementation {
+public:
+ template
+ const RefPairImplementation &operator=(const RefPairImplementation &other) const {
+ _first = other._first;
+ _second = other._second;
+ return *this;
+ }
+
+ template
+ const RefPairImplementation &operator=(const QPair &other) const {
+ _first = other.first;
+ _second = other.second;
+ return *this;
+ }
+
+private:
+ RefPairImplementation(T1 &first, T2 &second) : _first(first), _second(second) {
+ }
+ RefPairImplementation(const RefPairImplementation &other);
+
+ template
+ friend RefPairImplementation