Merge branch 'master' into logging

This commit is contained in:
pajlada 2018-06-05 13:16:20 +02:00 committed by GitHub
commit 4ad0ed4d45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
172 changed files with 5997 additions and 3348 deletions

13
BUILDING_ON_LINUX.md Normal file
View file

@ -0,0 +1,13 @@
# Linux
## Ubuntu 18.04
*most likely works the same for other Debian-like distros*
1. Install dependencies (and the C++ IDE Qt Creator) `sudo apt install qtcreator qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev`
2. Open `chatterino.pro` with QT Creator and build
## Arch Linux
install [chatterino2-git](https://aur.archlinux.org/packages/chatterino2-git/) from the aur or build manually as follows:
1. `sudo pacman -S qt5-base qt5-multimedia qt5-svg gst-plugins-ugly gst-plugins-good boost rapidjson`
2. go into project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && make`

22
BUILDING_ON_MAC.md Normal file
View file

@ -0,0 +1,22 @@
# Building on Mac OSX
1. install Xcode and Xcode Command Line Utilites
2. start Xcode, settings -> Locations, activate your Command Line Tools
3. install Qt Creator
4. install brew https://brew.sh/
5. `brew install boost openssl rapidjson`
6. build the project using Qt Creator
If the Project does not build at this point, you might need to add additional Paths/Libs, because brew does not install openssl and boost in the common path. You can get their path using
`brew info openssl`
`brew info boost`
The lines which you need to add to your project file should look similar to this
```
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
INCLUDEPATH += "/usr/local/Cellar/boost/1.67.0_1/include"
LIBS += -L"/usr/local/Cellar/boost/1.67.0_1/lib"
```

47
BUILDING_ON_WINDOWS.md Normal file
View file

@ -0,0 +1,47 @@
# Building on Windows (Recommended)
## Using Qt Creator
### Visual Studio 2017
1. Install Visual Studio 2017 and select "Desktop development with C++" and "Universal Windows Platform development.
### Boost
1. Visual Studio 2017 64-bit: https://dl.bintray.com/boostorg/release/1.66.0/binaries/boost_1_66_0-msvc-14.1-64.exe
2. When prompted, install boost to C:\local\boost
3. When the installation is finished, go to C:\local\boost and rename the "lib64-msvc-14.1" folder to "lib"
### OpenSSL
#### For our websocket library, we need OpenSSL 1.1
1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0h.exe
2. When prompted, install openssl to C:\local\openssl
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory"
#### For Qt SSL, we need OpenSSL 1.0
1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2o.exe
2. When prompted, install it anywhere
3. When prompted, copy the OpenSSL DLLS to "The OpenSSL binaries (/bin) directory"
4. Copy the OpenSSL 1.0 files from its /bin folder to C:/local/bin (You will need to create the folder)
5. Then copy the OpenSSL 1.1 files from its /bin folder to C:/local/bin (Overwrite any duplicate files)
6. Add C:/local/bin to your path folder (Follow guide here if you don't know how to do it: https://www.computerhope.com/issues/ch000549.htm#windows8 )
### Qt
1. Download Qt: https://www.qt.io/download
2. Select "Open source" at the bottom of this page
3. Then select "Download"
#### When prompted which components to install:
1. Under the latest Qt version:
- Select MSVC 2017 64-bit (or MSVC 2015 64-bit if you still use Visual Studio 2015)
- Optionally, enable Qt WebEngine
2. Under Tools:
- Select Qt Creator, and Qt Creator CDB Debugger Support
# Windows (Using MSYS2, not recommended)
Note: This guide is currently out of date and will not work as is.
Note: This build will have some features missing from the build.
Building using MSYS2 can be quite easier process. Check out MSYS2 at [msys2.org](http://www.msys2.org/).
Be sure to add `-j <number of threads>` as a make argument so it will use all your cpu cores to build. [example setup](https://i.imgur.com/qlESlS1.png)
You can also add `-o2` to optimize the final binary size but increase compilation time, and add `-pipe` to use more ram in compilation but increase compilation speed
1. open appropriate MSYS2 terminal and do `pacman -S mingw-w64-<arch>-boost mingw-w64-<arch>-qt5 mingw-w64-<arch>-rapidjson` where `<arch>` is `x86_64` or `i686`
2. go into the project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && mingw32-make`

110
README.md
View file

@ -4,107 +4,27 @@ Chatterino 2
Chatterino 2 is the second installment of the Twitch chat client series "Chatterino". For now you can check out Chatterino 1 at [https://chatterino.com](https://chatterino.com).
## Code style
The code is normally formated using clang format in Qt Creator. [.clang-format](https://github.com/fourtf/chatterino2/blob/master/.clang-format) contains the style file for clang format.
To setup automatic code formating with QT Creator, see [this guide](https://gist.github.com/pajlada/0296454198eb8f8789fd6fe7ea660c5b).
## Building
Before building run `git submodule update --init --recursive` to get required submodules.
### Windows
#### Using Qt Creator
##### Visual Studio 2017
1. Install Visual Studio 2017 and select "Desktop development with C++" and "Universal Windows Platform development.
[Building on Windows](../master/BUILDING_ON_WINDOWS.md)
##### Boost
1. Visual Studio 2017 64-bit: https://dl.bintray.com/boostorg/release/1.66.0/binaries/boost_1_66_0-msvc-14.1-64.exe
2. When prompted, install boost to C:\local\boost
3. When the installation is finished, go to C:\local\boost and rename the "lib64-msvc-14.1" folder to "lib"
[Building on Linux](../master/BUILDING_ON_LINUX.md)
##### OpenSSL
###### For our websocket library, we need OpenSSL 1.1
1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0h.exe
2. When prompted, install openssl to C:\local\openssl
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory"
###### For Qt SSL, we need OpenSSL 1.0
1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2o.exe
2. When prompted, install it anywhere
3. When prompted, copy the OpenSSL DLLS to "The OpenSSL binaries (/bin) directory"
4. Copy the OpenSSL 1.0 files from its /bin folder to C:/local/bin (You will need to create the folder)
5. Then copy the OpenSSL 1.1 files from its /bin folder to C:/local/bin (Overwrite any duplicate files)
6. Add C:/local/bin to your path folder (Follow guide here if you don't know how to do it: https://www.computerhope.com/issues/ch000549.htm#windows8 )
[Building on Mac](../master/BUILDING_ON_MAC.md)
##### Qt
1. Download Qt: https://www.qt.io/download
2. Select "Open source" at the bottom of this page
3. Then select "Download"
###### When prompted which components to install:
1. Under the latest Qt version:
- Select MSVC 2017 64-bit (or MSVC 2015 64-bit if you still use Visual Studio 2015)
- Optionally, enable Qt WebEngine
2. Under Tools:
- Select Qt Creator, and Qt Creator CDB Debugger Support
## Code style
The code is formated using clang format in Qt Creator. [.clang-format](https://github.com/fourtf/chatterino2/blob/master/.clang-format) contains the style file for clang format.
To setup automatic code formating with QT Creator, see [this guide](https://gist.github.com/pajlada/0296454198eb8f8789fd6fe7ea660c5b).
### Windows (Using MSYS2)
Note: This guide is currently out of date and will not work as is.
Note: This build will have some features missing from the build.
### Get it automated with QT Creator + Beautifier + Clang Format
1. Download LLVM: http://releases.llvm.org/5.0.1/LLVM-5.0.1-win64.exe
2. During the installation, make sure to add it to your path
3. In QT Creator, select `Help` > `About Plugins` > `C++` > `Beautifier` to enable the plugin
4. Restart QT Creator
5. Select `Tools` > `Options` > `Beautifier`
6. Under `General` select `Tool: ClangFormat` and enable `Automatic Formatting on File Save`
7. Under `Clang Format` select `Use predefined style: File` and `Fallback style: None`
Building using MSYS2 can be quite easier process. Check out MSYS2 at [msys2.org](http://www.msys2.org/).
Be sure to add `-j <number of threads>` as a make argument so it will use all your cpu cores to build. [example setup](https://i.imgur.com/qlESlS1.png)
You can also add `-o2` to optimize the final binary size but increase compilation time, and add `-pipe` to use more ram in compilation but increase compilation speed
1. open appropriate MSYS2 terminal and do `pacman -S mingw-w64-<arch>-boost mingw-w64-<arch>-qt5 mingw-w64-<arch>-rapidjson` where `<arch>` is `x86_64` or `i686`
2. go into the project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && mingw32-make`
###
### Linux
#### Ubuntu 16.04.2 LTS
*most likely works the same for other Debian-like distros*
1. install QT Creator `sudo apt-get install qtcreator qtmultimedia5-dev`
2. install boost-dev `sudo apt-get install libboost-dev`
3. open `chatterino.pro` with QT Creator and build
#### Ubuntu 18.04
*most likely works the same for other Debian-like distros*
1. Install dependencies (and the C++ IDE Qt Creator) `sudo apt install qtcreator qtmultimedia5-dev libqt5svg5-dev libboost-dev`
2. Install rapidjson to `/usr/local/` like this: From the Chatterino2 root folder: `sudo cp -r lib/rapidjson/include/rapidjson /usr/local/include`. If you want to install it to another place, you have to make sure it's in the chatterino.pro include path
3. open `chatterino.pro` with QT Creator and build
#### Arch Linux
install [chatterino2-git](https://aur.archlinux.org/packages/chatterino2-git/) from the aur or build manually as follows:
1. `sudo pacman -S qt5-base qt5-multimedia qt5-svg gst-plugins-ugly gst-plugins-good boost rapidjson`
2. go into project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && make`
### Mac OSX
1. install Xcode and Xcode Command Line Utilites
2. start Xcode, settings -> Locations, activate your Command Line Tools
3. install Qt Creator
4. install brew https://brew.sh/
5. `brew install boost openssl rapidjson`
6. build the project using Qt Creator
If the Project does not build at this point, you might need to add additional Paths/Libs, because brew does not install openssl and boost in the common path. You can get their path using
`brew info openssl`
`brew info boost`
The lines which you need to add to your project file should look similar to this
```
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
INCLUDEPATH += "/usr/local/Cellar/boost/1.67.0_1/include"
LIBS += -L"/usr/local/Cellar/boost/1.67.0_1/lib"
```
Test 1
Qt creator should now format the documents when saving it.

View file

@ -1,19 +0,0 @@
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

View file

@ -1,102 +0,0 @@
const ignoredPages = {
"settings": true,
"payments": true,
"inventory": true,
"messages": true,
"subscriptions": true,
"friends": true,
"directory": true,
};
const appName = "com.chatterino.chatterino";
let port = null;
/// Connect to port
function connectPort() {
port = chrome.runtime.connectNative("com.chatterino.chatterino");
console.log("port connected");
port.onMessage.addListener(function (msg) {
console.log(msg);
});
port.onDisconnect.addListener(function () {
console.log("port disconnected");
port = null;
});
}
function getPort() {
if (port) {
return port;
} else {
// TODO: add cooldown
connectPort();
return port;
}
}
/// Tab listeners
chrome.tabs.onActivated.addListener((activeInfo) => {
chrome.tabs.get(activeInfo.tabId, (tab) => {
if (!tab)
return;
if (!tab.url)
return;
matchUrl(tab.url, tab);
});
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (!tab.highlighted)
return;
matchUrl(changeInfo.url, tab);
});
/// Misc
function matchUrl(url, tab) {
if (!url)
return;
const match = url.match(/^https?:\/\/(www\.)?twitch.tv\/([a-zA-Z0-9]+)\/?$/);
let channelName;
console.log(tab);
if (match && (channelName = match[2], !ignoredPages[channelName])) {
console.log("channelName " + channelName);
console.log("winId " + tab.windowId);
chrome.windows.get(tab.windowId, {}, (window) => {
let yOffset = window.height - tab.height;
let port = getPort();
if (port) {
port.postMessage({
action: "select",
attach: true,
type: "twitch",
name: channelName,
winId: "" + tab.windowId,
yOffset: yOffset
});
}
});
} else {
let port = getPort();
if (port) {
port.postMessage({
action: "detach",
winId: "" + tab.windowId
})
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,21 +0,0 @@
{
"name": "Chatterino",
"version": "1.0",
"description": "xd",
"permissions": [
"tabs", "nativeMessaging"
],
"icons": {
"256": "icon.png"
},
"manifest_version": 2,
"background": {
"scripts": [
"background.js"
],
"persistent": false
},
"browser_action": {
"default_popup": "popup.html"
}
}

View file

@ -1,6 +0,0 @@
<!DOCTYPE html>
<html>
<body>
xd
</body>
</html>

View file

@ -26,6 +26,11 @@ equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
macx:ICON = resources/images/chatterino2.icns
win32:RC_FILE = resources/windows.rc
macx {
LIBS += -L/usr/local/lib
}
# Submodules
include(dependencies/rapidjson.pri)
include(dependencies/settings.pri)
@ -39,10 +44,14 @@ include(dependencies/openssl.pri)
include(dependencies/boost.pri)
# Optional feature: QtWebEngine
exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
message(Using QWebEngine)
QT += webenginewidgets
DEFINES += "USEWEBENGINE"
#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
# message(Using QWebEngine)
# QT += webenginewidgets
# DEFINES += "USEWEBENGINE"
#}
linux {
LIBS += -lrt
}
win32 {
@ -105,7 +114,6 @@ SOURCES += \
src/providers/twitch/twitchmessagebuilder.cpp \
src/providers/twitch/twitchserver.cpp \
src/providers/twitch/pubsub.cpp \
src/singletons/accountmanager.cpp \
src/singletons/commandmanager.cpp \
src/singletons/emotemanager.cpp \
src/singletons/fontmanager.cpp \
@ -185,7 +193,6 @@ SOURCES += \
src/widgets/attachedwindow.cpp \
src/widgets/settingspages/externaltoolspage.cpp \
src/widgets/helper/comboboxitemdelegate.cpp \
src/util/signalvectormodel.cpp \
src/controllers/commands/command.cpp \
src/controllers/commands/commandmodel.cpp \
src/controllers/commands/commandcontroller.cpp \
@ -197,7 +204,15 @@ SOURCES += \
src/controllers/accounts/accountcontroller.cpp \
src/controllers/accounts/accountmodel.cpp \
src/controllers/accounts/account.cpp \
src/widgets/helper/splitoverlay.cpp
src/widgets/helper/splitoverlay.cpp \
src/widgets/helper/dropoverlay.cpp \
src/widgets/helper/splitnode.cpp \
src/widgets/notificationpopup.cpp \
src/controllers/taggedusers/taggeduserscontroller.cpp \
src/controllers/taggedusers/taggeduser.cpp \
src/controllers/taggedusers/taggedusersmodel.cpp \
src/util/emotemap.cpp \
src/providers/irc/ircconnection2.cpp
HEADERS += \
src/precompiled_header.hpp \
@ -226,7 +241,6 @@ HEADERS += \
src/providers/twitch/twitchmessagebuilder.hpp \
src/providers/twitch/twitchserver.hpp \
src/providers/twitch/pubsub.hpp \
src/singletons/accountmanager.hpp \
src/singletons/commandmanager.hpp \
src/singletons/emotemanager.hpp \
src/singletons/fontmanager.hpp \
@ -343,7 +357,17 @@ HEADERS += \
src/controllers/accounts/accountmodel.hpp \
src/controllers/accounts/account.hpp \
src/util/sharedptrelementless.hpp \
src/widgets/helper/splitoverlay.hpp
src/widgets/helper/splitoverlay.hpp \
src/widgets/helper/dropoverlay.hpp \
src/widgets/helper/splitnode.hpp \
src/widgets/notificationpopup.hpp \
src/controllers/taggedusers/taggeduserscontroller.hpp \
src/controllers/taggedusers/taggeduser.hpp \
src/providerid.hpp \
src/controllers/taggedusers/taggedusersmodel.hpp \
src/util/qstringhash.hpp \
src/util/mutexvalue.hpp \
src/providers/irc/ircconnection2.hpp
RESOURCES += \
resources/resources.qrc

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 812 B

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 B

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 361 B

View file

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,23 @@
Copyright (c) 2012 - 2016, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,26 @@
Copyright (C) 2008-2016 The Communi Project
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,125 @@
LICENSE ISSUES
==============
The OpenSSL toolkit stays under a double license, i.e. both the conditions of
the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts.
OpenSSL License
---------------
/* ====================================================================
* Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
Original SSLeay License
-----------------------
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
* All rights reserved.
*
* This package is an SSL implementation written
* by Eric Young (eay@cryptsoft.com).
* The implementation was written so as to conform with Netscapes SSL.
*
* This library is free for commercial and non-commercial use as long as
* the following conditions are aheared to. The following conditions
* apply to all code found in this distribution, be it the RC4, RSA,
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
* included with this distribution is covered by the same copyright terms
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
*
* Copyright remains Eric Young's, and as such any Copyright notices in
* the code are not to be removed.
* If this package is used in a product, Eric Young should be given attribution
* as the author of the parts of the library used.
* This can be in the form of a textual message at program startup or
* in documentation (online or textual) provided with the package.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* "This product includes cryptographic software written by
* Eric Young (eay@cryptsoft.com)"
* The word 'cryptographic' can be left out if the rouines from the library
* being used are not cryptographic related :-).
* 4. If you include any Windows specific code (or a derivative thereof) from
* the apps directory (application code) you must include an acknowledgement:
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
*
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The licence and distribution terms for any publically available version or
* derivative of this code cannot be changed. i.e. this code cannot simply be
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 pajlada
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 pajlada
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View file

@ -0,0 +1,57 @@
Tencent is pleased to support the open source community by making RapidJSON available.
Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License.
If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license.
A copy of the MIT License is included in this file.
Other dependencies and licenses:
Open Source Software Licensed Under the BSD License:
--------------------------------------------------------------------
The msinttypes r29
Copyright (c) 2006-2013 Alexander Chemeris
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Open Source Software Licensed Under the JSON License:
--------------------------------------------------------------------
json.org
Copyright (c) 2002 JSON.org
All Rights Reserved.
JSON_checker
Copyright (c) 2002 JSON.org
All Rights Reserved.
Terms of the JSON License:
---------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Terms of the MIT License:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,145 @@
Main Library:
Copyright (c) 2014, Peter Thorson. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the WebSocket++ Project nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Bundled Libraries:
****** Base 64 Library (base64/base64.hpp) ******
base64.hpp is a repackaging of the base64.cpp and base64.h files into a
single header suitable for use as a header only library. This conversion was
done by Peter Thorson (webmaster@zaphoyd.com) in 2012. All modifications to
the code are redistributed under the same license as the original, which is
listed below.
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
****** SHA1 Library (sha1/sha1.hpp) ******
sha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the shallsha1
library (http://code.google.com/p/smallsha1/) into a single header suitable for
use as a header only library. This conversion was done by Peter Thorson
(webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed
under the same license as the original, which is listed below.
Copyright (c) 2011, Micael Hildenborg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Micael Hildenborg nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
****** MD5 Library (common/md5.hpp) ******
md5.hpp is a reformulation of the md5.h and md5.c code from
http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to
function as a component of a header only library. This conversion was done by
Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The
changes are released under the same license as the original (listed below)
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
****** UTF8 Validation logic (utf8_validation.hpp) ******
utf8_validation.hpp is adapted from code originally written by Bjoern Hoehrmann
<bjoern@hoehrmann.de>. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for
details.
The original license:
Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -51,6 +51,15 @@
<file>images/split/splitright.png</file>
<file>images/split/splitup.png</file>
<file>images/split/splitmove.png</file>
<file>licenses/boost_boost.txt</file>
<file>licenses/fmt_bsd2.txt</file>
<file>licenses/libcommuni_BSD3.txt</file>
<file>licenses/openssl.txt</file>
<file>licenses/pajlada_settings.txt</file>
<file>licenses/pajlada_signals.txt</file>
<file>licenses/qt_lgpl-3.0.txt</file>
<file>licenses/rapidjson.txt</file>
<file>licenses/websocketpp.txt</file>
</qresource>
<qresource prefix="/qt/etc">
<file>qt.conf</file>

View file

@ -1,11 +1,12 @@
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/commands/commandcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "controllers/ignores/ignorecontroller.hpp"
#include "controllers/taggedusers/taggeduserscontroller.hpp"
#include "providers/twitch/pubsub.hpp"
#include "providers/twitch/twitchserver.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/fontmanager.hpp"
#include "singletons/loggingmanager.hpp"
@ -19,12 +20,6 @@
#include <atomic>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#endif
using namespace chatterino::singletons;
namespace chatterino {
@ -34,7 +29,7 @@ namespace {
bool isBigEndian()
{
int test = 1;
char *p = (char *)&test;
char *p = reinterpret_cast<char *>(&test);
return p[0] == 0;
}
@ -61,6 +56,7 @@ void Application::construct()
isAppConstructed = true;
// 1. Instantiate all classes
this->settings = new singletons::SettingManager;
this->paths = new singletons::PathManager(this->argc, this->argv);
this->themes = new singletons::ThemeManager;
this->windows = new singletons::WindowManager;
@ -68,9 +64,9 @@ void Application::construct()
this->commands = new controllers::commands::CommandController;
this->highlights = new controllers::highlights::HighlightController;
this->ignores = new controllers::ignores::IgnoreController;
this->accounts = new singletons::AccountManager;
this->taggedUsers = new controllers::taggedusers::TaggedUsersController;
this->accounts = new controllers::accounts::AccountController;
this->emotes = new singletons::EmoteManager;
this->settings = new singletons::SettingManager;
this->fonts = new singletons::FontManager;
this->resources = new singletons::ResourceManager;
@ -92,12 +88,13 @@ void Application::initialize()
// 2. Initialize/load classes
this->settings->initialize();
this->windows->initialize();
this->nativeMessaging->registerHost();
this->settings->load();
this->commands->load();
this->logging->initialize();
this->windows->initialize();
this->resources->initialize();
@ -109,7 +106,6 @@ void Application::initialize()
this->accounts->load();
this->twitch.server->initialize();
this->logging->initialize();
// XXX
this->settings->updateWordTypeMask();
@ -182,8 +178,9 @@ void Application::initialize()
}
auto msg = messages::Message::createTimeoutMessage(action);
msg->flags |= messages::Message::PubSub;
util::postToThread([chan, msg] { chan->addMessage(msg); });
util::postToThread([chan, msg] { chan->addOrReplaceTimeout(msg); });
});
this->twitch.pubsub->sig.moderation.userUnbanned.connect([&](const auto &action) {
@ -205,10 +202,10 @@ void Application::initialize()
// TODO(pajlada): Unlisten to all authed topics instead of only moderation topics
// this->twitch.pubsub->UnlistenAllAuthedTopics();
this->twitch.pubsub->listenToWhispers(this->accounts->Twitch.getCurrent()); //
this->twitch.pubsub->listenToWhispers(this->accounts->twitch.getCurrent()); //
};
this->accounts->Twitch.currentUserChanged.connect(RequestModerationActions);
this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
RequestModerationActions();
}
@ -231,52 +228,6 @@ void Application::save()
this->commands->save();
}
void Application::runNativeMessagingHost()
{
auto app = getApp();
app->nativeMessaging = new singletons::NativeMessagingManager;
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
#if 0
bool bigEndian = isBigEndian();
#endif
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
char *b = (char *)malloc(size + 1);
std::cin.read(b, size);
*(b + size) = '\0';
app->nativeMessaging->sendToGuiProcess(QByteArray(b, size));
free(b);
}
}
Application *getApp()
{
assert(staticApp != nullptr);

View file

@ -26,6 +26,12 @@ class HighlightController;
namespace ignores {
class IgnoreController;
}
namespace taggedusers {
class TaggedUsersController;
}
namespace accounts {
class AccountController;
}
} // namespace controllers
namespace singletons {
@ -67,7 +73,8 @@ public:
controllers::commands::CommandController *commands = nullptr;
controllers::highlights::HighlightController *highlights = nullptr;
controllers::ignores::IgnoreController *ignores = nullptr;
singletons::AccountManager *accounts = nullptr;
controllers::taggedusers::TaggedUsersController *taggedUsers = nullptr;
controllers::accounts::AccountController *accounts = nullptr;
singletons::EmoteManager *emotes = nullptr;
singletons::NativeMessagingManager *nativeMessaging = nullptr;
singletons::SettingManager *settings = nullptr;

View file

@ -45,6 +45,11 @@ Channel::Type Channel::getType() const
return this->type;
}
bool Channel::isTwitchChannel() const
{
return this->type >= Twitch && this->type < TwitchEnd;
}
bool Channel::isEmpty() const
{
return this->name.isEmpty();
@ -60,37 +65,68 @@ void Channel::addMessage(MessagePtr message)
auto app = getApp();
MessagePtr deleted;
bool isTimeout = (message->flags & Message::Timeout) != 0;
if (!isTimeout) {
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a setting
this->addRecentChatter(message);
}
}
app->logging->addMessage(this->name, message);
if (isTimeout) {
if (this->messages.pushBack(message, deleted)) {
this->messageRemovedFromStart.invoke(deleted);
}
this->messageAppended.invoke(message);
}
void Channel::addOrReplaceTimeout(messages::MessagePtr message)
{
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
bool addMessage = true;
int snapshotLength = snapshot.getLength();
int end = std::max(0, snapshotLength - 20);
bool addMessage = true;
QTime minimumTime = QTime::currentTime().addSecs(-5);
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
assert(message->banAction != nullptr);
MessagePtr replacement(
Message::createTimeoutMessage(*(message->banAction), s->count + 1));
this->replaceMessage(s, replacement);
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
this->replaceMessage(s, message);
addMessage = false;
break;
}
if (!message->flags.HasFlag(Message::PubSub) && s->flags.HasFlag(Message::PubSub)) {
addMessage = false;
break;
}
int count = s->count + 1;
messages::MessagePtr replacement(Message::createSystemMessage(
message->searchText + QString("(") + QString::number(count) + " times)"));
replacement->timeoutUser = message->timeoutUser;
replacement->count = count;
replacement->flags = message->flags;
this->replaceMessage(s, replacement);
return;
}
}
@ -103,19 +139,12 @@ void Channel::addMessage(MessagePtr message)
}
}
if (addMessage) {
this->addMessage(message);
}
// XXX: Might need the following line
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
if (!addMessage) {
return;
}
}
if (this->messages.pushBack(message, deleted)) {
this->messageRemovedFromStart.invoke(deleted);
}
this->messageAppended.invoke(message);
}
void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages)

View file

@ -29,6 +29,7 @@ public:
TwitchWhispers,
TwitchWatching,
TwitchMentions,
TwitchEnd,
};
explicit Channel(const QString &_name, Type type);
@ -43,11 +44,13 @@ public:
pajlada::Signals::NoArgSignal destroyed;
Type getType() const;
bool isTwitchChannel() const;
virtual bool isEmpty() const;
messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot();
void addMessage(messages::MessagePtr message);
void addMessagesAtStart(std::vector<messages::MessagePtr> &messages);
void addOrReplaceTimeout(messages::MessagePtr message);
void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement);
virtual void addRecentChatter(const std::shared_ptr<messages::Message> &message);

View file

@ -3,6 +3,7 @@
#include "debug/log.hpp"
#include <QString>
#include <QWidget>
#include <boost/preprocessor.hpp>
#include <string>
@ -20,4 +21,8 @@ inline QString qS(const std::string &string)
return QString::fromStdString(string);
}
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
} // namespace chatterino

View file

@ -1,11 +1,23 @@
#include "account.hpp"
#include <tuple>
namespace chatterino {
namespace controllers {
namespace accounts {
Account::Account(const QString &category)
Account::Account(ProviderId _providerId)
: providerId(_providerId)
{
static QString twitch("Twitch");
this->category = [&]() {
switch (_providerId) {
case ProviderId::Twitch:
return twitch;
}
return QString("Unknown ProviderId");
}();
}
const QString &Account::getCategory() const
@ -13,16 +25,17 @@ const QString &Account::getCategory() const
return this->category;
}
ProviderId Account::getProviderId() const
{
return this->providerId;
}
bool Account::operator<(const Account &other) const
{
if (this->category < other.category) {
return true;
} else if (this->category == other.category) {
if (this->toString() < other.toString()) {
return true;
}
}
return false;
QString a = this->toString();
QString b = other.toString();
return std::tie(this->category, a) < std::tie(other.category, b);
}
} // namespace accounts

View file

@ -1,5 +1,7 @@
#pragma once
#include "providerid.hpp"
#include <QString>
namespace chatterino {
@ -9,14 +11,17 @@ namespace accounts {
class Account
{
public:
Account(const QString &category);
Account(ProviderId providerId);
virtual ~Account() = default;
virtual QString toString() const = 0;
const QString &getCategory() const;
ProviderId getProviderId() const;
bool operator<(const Account &other) const;
private:
ProviderId providerId;
QString category;
};

View file

@ -8,13 +8,42 @@ namespace accounts {
AccountController::AccountController()
{
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
this->accounts.insertItem(std::dynamic_pointer_cast<Account>(args.item));
});
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
if (args.caller != this) {
auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
this->accounts.removeItem(it - accs.begin());
}
});
this->accounts.itemRemoved.connect([this](const auto &args) {
switch (args.item->getProviderId()) {
case ProviderId::Twitch: {
auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
this->twitch.accounts.removeItem(it - accs.begin(), this);
} break;
}
});
}
void AccountController::load()
{
this->twitch.load();
}
AccountModel *AccountController::createModel(QObject *parent)
{
AccountModel *model = new AccountModel(parent);
//(util::BaseSignalVector<stdAccount> *)
model->init(&this->accounts);
return model;
}

View file

@ -3,6 +3,7 @@
#include <QObject>
#include "controllers/accounts/account.hpp"
#include "providers/twitch/twitchaccountmanager.hpp"
#include "util/sharedptrelementless.hpp"
#include "util/signalvector2.hpp"
@ -19,6 +20,10 @@ public:
AccountModel *createModel(QObject *parent);
void load();
providers::twitch::TwitchAccountManager twitch;
private:
util::SortedSignalVector<std::shared_ptr<Account>, util::SharedPtrElementLess<Account>>
accounts;

View file

@ -1,5 +1,7 @@
#include "accountmodel.hpp"
#include "util/standarditemhelper.hpp"
namespace chatterino {
namespace controllers {
namespace accounts {
@ -10,16 +12,49 @@ AccountModel::AccountModel(QObject *parent)
}
// turn a vector item into a model row
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &row)
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &,
const std::shared_ptr<Account> &original)
{
return nullptr;
return original;
}
// turns a row in the model into a vector item
void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row)
{
row[0]->setData(item->toString(), Qt::DisplayRole);
util::setStringItem(row[0], item->toString(), false);
row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole);
}
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int proposedIndex)
{
if (this->categoryCount[item->getCategory()]++ == 0) {
auto row = this->createRow();
util::setStringItem(row[0], item->getCategory(), false, false);
row[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
this->insertCustomRow(std::move(row), proposedIndex);
return proposedIndex + 1;
}
return proposedIndex;
}
void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int index)
{
auto it = this->categoryCount.find(item->getCategory());
assert(it != this->categoryCount.end());
if (it->second <= 1) {
this->categoryCount.erase(it);
this->removeCustomRow(index - 1);
} else {
it->second--;
}
}
} // namespace accounts

View file

@ -1,8 +1,11 @@
#pragma once
#include "controllers/accounts/account.hpp"
#include "util/qstringhash.hpp"
#include "util/signalvectormodel.hpp"
#include <unordered_map>
namespace chatterino {
namespace controllers {
namespace accounts {
@ -16,13 +19,23 @@ public:
protected:
// turn a vector item into a model row
virtual std::shared_ptr<Account> getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual std::shared_ptr<Account> getItemFromRow(
std::vector<QStandardItem *> &row, const std::shared_ptr<Account> &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row) override;
virtual int beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int proposedIndex) override;
virtual void afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int index) override;
friend class AccountController;
private:
std::unordered_map<QString, int> categoryCount;
};
} // namespace accounts

View file

@ -1,12 +1,12 @@
#include "commandcontroller.hpp"
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/commands/command.hpp"
#include "controllers/commands/commandmodel.hpp"
#include "messages/messagebuilder.hpp"
#include "providers/twitch/twitchchannel.hpp"
#include "providers/twitch/twitchserver.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/pathmanager.hpp"
#include "util/signalvector2.hpp"
@ -110,7 +110,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
return "";
} else if (commandName == "/uptime") {
const auto &streamStatus = twitchChannel->GetStreamStatus();
const auto &streamStatus = twitchChannel->getStreamStatus();
QString messageText =
streamStatus.live ? streamStatus.uptime : "Channel is not live.";
@ -121,7 +121,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
} else if (commandName == "/ignore" && words.size() >= 2) {
auto app = getApp();
auto user = app->accounts->Twitch.getCurrent();
auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1);
if (user->isAnon()) {
@ -138,7 +138,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
} else if (commandName == "/unignore" && words.size() >= 2) {
auto app = getApp();
auto user = app->accounts->Twitch.getCurrent();
auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1);
if (user->isAnon()) {
@ -161,7 +161,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
messages::MessageBuilder b;
b.emplace<messages::TextElement>(app->accounts->Twitch.getCurrent()->getUserName(),
b.emplace<messages::TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
messages::MessageElement::Text);
b.emplace<messages::TextElement>("->", messages::MessageElement::Text);
b.emplace<messages::TextElement>(words[1], messages::MessageElement::Text);
@ -169,7 +169,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
QString rest = "";
for (int i = 2; i < words.length(); i++) {
rest += words[i];
rest += words[i] + " ";
}
b.emplace<messages::TextElement>(rest, messages::MessageElement::Text);

View file

@ -11,7 +11,7 @@ CommandModel::CommandModel(QObject *parent)
}
// turn a vector item into a model row
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row)
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row, const Command &original)
{
return Command(row[0]->data(Qt::EditRole).toString(), row[1]->data(Qt::EditRole).toString());
}

View file

@ -17,7 +17,8 @@ class CommandModel : public util::SignalVectorModel<Command>
protected:
// turn a vector item into a model row
virtual Command getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual Command getItemFromRow(std::vector<QStandardItem *> &row,
const Command &command) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override;

View file

@ -2,6 +2,7 @@
#include "application.hpp"
#include "controllers/highlights/highlightmodel.hpp"
#include "widgets/notificationpopup.hpp"
namespace chatterino {
namespace controllers {
@ -21,7 +22,6 @@ void HighlightController::initialize()
}
this->phrases.delayedItemsChanged.connect([this] { //
int xd = this->phrases.getVector().size();
this->highlightsSetting.setValue(this->phrases.getVector());
});
}
@ -34,6 +34,15 @@ HighlightModel *HighlightController::createModel(QObject *parent)
return model;
}
void HighlightController::addHighlight(const messages::MessagePtr &msg)
{
// static widgets::NotificationPopup popup;
// popup.updatePosition();
// popup.addMessage(msg);
// popup.show();
}
} // namespace highlights
} // namespace controllers
} // namespace chatterino

View file

@ -1,6 +1,7 @@
#pragma once
#include "controllers/highlights/highlightphrase.hpp"
#include "messages/message.hpp"
#include "singletons/settingsmanager.hpp"
#include "util/signalvector2.hpp"
@ -21,6 +22,8 @@ public:
HighlightModel *createModel(QObject *parent);
void addHighlight(const messages::MessagePtr &msg);
private:
bool initialized = false;

View file

@ -15,7 +15,8 @@ HighlightModel::HighlightModel(QObject *parent)
}
// turn a vector item into a model row
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row)
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original)
{
// key, alert, sound, regex

View file

@ -17,7 +17,8 @@ class HighlightModel : public util::SignalVectorModel<HighlightPhrase>
protected:
// turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightPhrase &item,

View file

@ -15,7 +15,8 @@ IgnoreModel::IgnoreModel(QObject *parent)
}
// turn a vector item into a model row
IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row)
IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row,
const IgnorePhrase &original)
{
// key, regex

View file

@ -17,7 +17,8 @@ class IgnoreModel : public util::SignalVectorModel<IgnorePhrase>
protected:
// turn a vector item into a model row
virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row,
const IgnorePhrase &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const IgnorePhrase &item,

View file

@ -0,0 +1,24 @@
#include "taggeduser.hpp"
#include <tuple>
namespace chatterino {
namespace controllers {
namespace taggedusers {
TaggedUser::TaggedUser(ProviderId _provider, const QString &_name, const QString &_id)
: provider(_provider)
, name(_name)
, id(_id)
{
}
bool TaggedUser::operator<(const TaggedUser &other) const
{
return std::tie(this->provider, this->name, this->id) <
std::tie(other.provider, other.name, other.id);
}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,24 @@
#pragma once
#include <QString>
#include <providerid.hpp>
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUser
{
public:
TaggedUser(ProviderId provider, const QString &name, const QString &id);
bool operator<(const TaggedUser &other) const;
ProviderId provider;
QString name;
QString id;
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,23 @@
#include "taggeduserscontroller.hpp"
#include "controllers/taggedusers/taggedusersmodel.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
TaggedUsersController::TaggedUsersController()
{
}
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
{
TaggedUsersModel *model = new TaggedUsersModel(parent);
model->init(&this->users);
return model;
}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,24 @@
#pragma once
#include "controllers/taggedusers/taggeduser.hpp"
#include "util/signalvector2.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUsersModel;
class TaggedUsersController
{
public:
TaggedUsersController();
util::SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
TaggedUsersModel *createModel(QObject *parent = nullptr);
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,66 @@
#include "taggedusersmodel.hpp"
#include "application.hpp"
#include "util/standarditemhelper.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
// commandmodel
TaggedUsersModel::TaggedUsersModel(QObject *parent)
: util::SignalVectorModel<TaggedUser>(1, parent)
{
}
// turn a vector item into a model row
TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original)
{
return original;
}
// turns a row in the model into a vector item
void TaggedUsersModel::getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row)
{
util::setStringItem(row[0], item.name);
}
void TaggedUsersModel::afterInit()
{
// std::vector<QStandardItem *> row = this->createRow();
// util::setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true,
// false); row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// util::setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true,
// false); util::setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
// true, false); row[3]->setFlags(0); this->insertCustomRow(row, 0);
}
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
// const QVariant &value, int role)
//{
// switch (column) {
// case 0: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
// }
// } break;
// case 1: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
// }
// } break;
// case 2: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightSound.setValue(value.toBool());
// }
// } break;
// case 3: {
// // empty element
// } break;
// }
//}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,34 @@
#pragma once
#include "controllers/taggedusers/taggeduser.hpp"
#include "util/signalvectormodel.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUsersController;
class TaggedUsersModel : public util::SignalVectorModel<TaggedUser>
{
explicit TaggedUsersModel(QObject *parent);
protected:
// turn a vector item into a model row
virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row) override;
virtual void afterInit() override;
// virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
// const QVariant &value, int role) override;
friend class TaggedUsersController;
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -1,6 +1,7 @@
#include "application.hpp"
#include "singletons/nativemessagingmanager.hpp"
#include "singletons/pathmanager.hpp"
#include "singletons/updatemanager.hpp"
#include "util/networkmanager.hpp"
#include "widgets/lastruncrashdialog.hpp"
@ -9,6 +10,7 @@
#include <QFile>
#include <QLibrary>
#include <QStringList>
#include <QStyleFactory>
#ifdef USEWINSDK
#include "util/nativeeventhelper.hpp"
@ -17,7 +19,15 @@
#include <fstream>
#include <iostream>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#endif
int runGui(int argc, char *argv[]);
void runNativeMessagingHost();
void installCustomPalette();
int main(int argc, char *argv[])
{
@ -30,7 +40,7 @@ int main(int argc, char *argv[])
// TODO: can be any argument
if (args.size() > 0 && args[0].startsWith("chrome-extension://")) {
chatterino::Application::runNativeMessagingHost();
runNativeMessagingHost();
return 0;
}
@ -47,14 +57,16 @@ int runGui(int argc, char *argv[])
// QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true);
QApplication a(argc, argv);
// Install native event handler for hidpi on windows
#ifdef USEWINSDK
a.installNativeEventFilter(new chatterino::util::DpiNativeEventFilter);
#endif
QApplication::setStyle(QStyleFactory::create("Fusion"));
installCustomPalette();
// Initialize NetworkManager
chatterino::util::NetworkManager::init();
// Check for upates
chatterino::singletons::UpdateManager::getInstance().checkForUpdates();
// Initialize application
chatterino::Application::instantiate(argc, argv);
auto app = chatterino::getApp();
@ -68,7 +80,14 @@ int runGui(int argc, char *argv[])
if (QFile::exists(runningPath)) {
#ifndef DISABLE_CRASH_DIALOG
chatterino::widgets::LastRunCrashDialog dialog;
dialog.exec();
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
}
#endif
} else {
QFile runningFile(runningPath);
@ -98,3 +117,86 @@ int runGui(int argc, char *argv[])
_exit(0);
}
void runNativeMessagingHost()
{
auto *nm = new chatterino::singletons::NativeMessagingManager;
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
#if 0
bool bigEndian = isBigEndian();
#endif
std::atomic<bool> ping(false);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&ping] {
if (!ping.exchange(false)) {
_exit(0);
}
});
timer.setInterval(11000);
timer.start();
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
std::unique_ptr<char[]> b(new char[size + 1]);
std::cin.read(b.get(), size);
*(b.get() + size) = '\0';
nm->sendToGuiProcess(QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
}
}
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
QPalette darkPalette = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
qApp->setPalette(darkPalette);
}

View file

@ -147,10 +147,10 @@ void Image::loadImage()
loadedEventQueued = true;
QTimer::singleShot(500, [] {
getApp()->emotes->incGeneration();
getApp()->windows->incGeneration();
auto app = getApp();
app->windows->layoutVisibleChatWidgets();
app->windows->layoutChannelViews();
loadedEventQueued = false;
});
}
@ -240,7 +240,8 @@ int Image::getWidth() const
int Image::getScaledWidth() const
{
return static_cast<int>(this->getWidth() * this->scale);
return static_cast<int>((float)this->getWidth() * this->scale *
getApp()->settings->emoteScale.getValue());
}
int Image::getHeight() const
@ -253,7 +254,8 @@ int Image::getHeight() const
int Image::getScaledHeight() const
{
return static_cast<int>(this->getHeight() * this->scale);
return static_cast<int>((float)this->getHeight() * this->scale *
getApp()->settings->emoteScale.getValue());
}
} // namespace messages

View file

@ -3,6 +3,8 @@
#include "application.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/settingsmanager.hpp"
#include "singletons/windowmanager.hpp"
#include "util/benchmark.hpp"
#include <QApplication>
#include <QDebug>
@ -20,9 +22,9 @@ namespace chatterino {
namespace messages {
namespace layouts {
MessageLayout::MessageLayout(MessagePtr _message)
: message(_message)
, buffer(nullptr)
MessageLayout::MessageLayout(MessagePtr message)
: message_(message)
, buffer_(nullptr)
{
util::DebugCount::increase("message layout");
}
@ -34,119 +36,116 @@ MessageLayout::~MessageLayout()
Message *MessageLayout::getMessage()
{
return this->message.get();
return this->message_.get();
}
// Height
int MessageLayout::getHeight() const
{
return container.getHeight();
return container_.getHeight();
}
// Layout
// return true if redraw is required
bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
{
// BenchmarkGuard benchmark("MessageLayout::layout()");
auto app = getApp();
bool layoutRequired = false;
// check if width changed
bool widthChanged = width != this->currentLayoutWidth;
bool widthChanged = width != this->currentLayoutWidth_;
layoutRequired |= widthChanged;
this->currentLayoutWidth = width;
this->currentLayoutWidth_ = width;
// check if emotes changed
bool imagesChanged = this->emoteGeneration != app->emotes->getGeneration();
layoutRequired |= imagesChanged;
this->emoteGeneration = app->emotes->getGeneration();
// check if text changed
bool textChanged = this->fontGeneration != app->fonts->getGeneration();
layoutRequired |= textChanged;
this->fontGeneration = app->fonts->getGeneration();
// check if layout state changed
if (this->layoutState_ != app->windows->getGeneration()) {
layoutRequired = true;
this->flags |= RequiresBufferUpdate;
this->layoutState_ = app->windows->getGeneration();
}
// check if work mask changed
bool wordMaskChanged = this->currentWordFlags != flags; // app->settings->getWordTypeMask();
layoutRequired |= wordMaskChanged;
this->currentWordFlags = flags; // app->settings->getWordTypeMask();
layoutRequired |= this->currentWordFlags_ != flags;
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
// check if timestamp format changed
bool timestampFormatChanged = this->timestampFormat != app->settings->timestampFormat;
layoutRequired |= timestampFormatChanged;
// check if layout was requested manually
layoutRequired |= bool(this->flags & RequiresLayout);
this->flags &= decltype(RequiresLayout)(~RequiresLayout);
// check if dpi changed
bool scaleChanged = this->scale != scale;
layoutRequired |= scaleChanged;
this->scale = scale;
imagesChanged |= scaleChanged;
textChanged |= scaleChanged;
layoutRequired |= this->scale_ != scale;
this->scale_ = scale;
// update word sizes if needed
if (imagesChanged) {
// this->container.updateImages();
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (textChanged) {
// this->container.updateText();
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (widthChanged || wordMaskChanged) {
this->deleteBuffer();
}
// return if no layout is required
if (!layoutRequired) {
return false;
}
int oldHeight = this->container_.getHeight();
this->actuallyLayout(width, flags);
if (widthChanged || this->container_.getHeight() != oldHeight) {
this->deleteBuffer();
}
this->invalidateBuffer();
return true;
}
void MessageLayout::actuallyLayout(int width, MessageElement::Flags flags)
void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
{
this->container.begin(width, this->scale, this->message->flags.value);
auto messageFlags = this->message_->flags.value;
for (const std::unique_ptr<MessageElement> &element : this->message->getElements()) {
element->addToContainer(this->container, flags);
if (this->flags & MessageLayout::Expanded ||
(_flags & MessageElement::ModeratorTools &&
!(this->message_->flags & Message::MessageFlags::Disabled))) {
messageFlags = Message::MessageFlags(messageFlags & ~Message::MessageFlags::Collapsed);
}
if (this->height != this->container.getHeight()) {
this->container_.begin(width, this->scale_, messageFlags);
for (const std::unique_ptr<MessageElement> &element : this->message_->getElements()) {
element->addToContainer(this->container_, _flags);
}
if (this->height_ != this->container_.getHeight()) {
this->deleteBuffer();
}
this->container.end();
this->height = this->container.getHeight();
this->container_.end();
this->height_ = this->container_.getHeight();
// collapsed state
this->flags &= ~Flags::Collapsed;
if (this->container_.isCollapsed()) {
this->flags |= Flags::Collapsed;
}
}
// Painting
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused)
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
Selection &selection, bool isLastReadMessage, bool isWindowFocused)
{
auto app = getApp();
QPixmap *pixmap = this->buffer.get();
QPixmap *pixmap = this->buffer_.get();
// create new buffer if required
if (!pixmap) {
#ifdef Q_OS_MACOS
pixmap =
new QPixmap((int)(this->container.getWidth() * painter.device()->devicePixelRatioF()),
(int)(this->container.getHeight() * painter.device()->devicePixelRatioF()));
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_.getHeight() * painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
pixmap = new QPixmap(this->container.getWidth(), std::max(16, this->container.getHeight()));
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
#endif
this->buffer = std::shared_ptr<QPixmap>(pixmap);
this->bufferValid = false;
this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
this->bufferValid_ = false;
util::DebugCount::increase("message drawing buffers");
}
if (!this->bufferValid || !selection.isEmpty()) {
if (!this->bufferValid_ || !selection.isEmpty()) {
this->updateBuffer(pixmap, messageIndex, selection);
}
@ -155,21 +154,21 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
// draw gif emotes
this->container.paintAnimatedElements(painter, y);
this->container_.paintAnimatedElements(painter, y);
// draw disabled
if (this->message->flags.HasFlag(Message::Disabled)) {
if (this->message_->flags.HasFlag(Message::Disabled)) {
painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled);
}
// draw selection
if (!selection.isEmpty()) {
this->container.paintSelection(painter, messageIndex, selection, y);
this->container_.paintSelection(painter, messageIndex, selection, y);
}
// draw message seperation line
if (app->settings->seperateMessages.getValue()) {
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1,
painter.fillRect(0, y, this->container_.getWidth(), 1,
app->themes->splits.messageSeperator);
}
@ -180,14 +179,13 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
QBrush brush(color, Qt::VerPattern);
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1,
brush);
painter.fillRect(0, y + this->container_.getHeight() - 1, pixmap->width(), 1, brush);
}
this->bufferValid = true;
this->bufferValid_ = true;
}
void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &selection)
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
{
auto app = getApp();
@ -197,8 +195,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
// draw background
QColor backgroundColor;
if (this->message->flags & Message::Highlighted) {
if (this->message_->flags & Message::Highlighted) {
backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags & Message::Subscription) {
backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (app->settings->alternateMessageBackground.getValue() &&
this->flags & MessageLayout::AlternateBackground) {
backgroundColor = app->themes->messages.backgrounds.alternate;
@ -208,7 +208,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
painter.fillRect(buffer->rect(), backgroundColor);
// draw message
this->container.paintElements(painter);
this->container_.paintElements(painter);
#ifdef FOURTF
// debug
@ -226,15 +226,15 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
void MessageLayout::invalidateBuffer()
{
this->bufferValid = false;
this->bufferValid_ = false;
}
void MessageLayout::deleteBuffer()
{
if (this->buffer != nullptr) {
if (this->buffer_ != nullptr) {
util::DebugCount::decrease("message drawing buffers");
this->buffer = nullptr;
this->buffer_ = nullptr;
}
}
@ -243,7 +243,7 @@ void MessageLayout::deleteCache()
this->deleteBuffer();
#ifdef XD
this->container.clear();
this->container_.clear();
#endif
}
@ -256,22 +256,22 @@ void MessageLayout::deleteCache()
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
{
// go through all words and return the first one that contains the point.
return this->container.getElementAt(point);
return this->container_.getElementAt(point);
}
int MessageLayout::getLastCharacterIndex() const
{
return this->container.getLastCharacterIndex();
return this->container_.getLastCharacterIndex();
}
int MessageLayout::getSelectionIndex(QPoint position)
{
return this->container.getSelectionIndex(position);
return this->container_.getSelectionIndex(position);
}
void MessageLayout::addSelectionText(QString &str, int from, int to)
{
this->container.addSelectionText(str, from, to);
this->container_.addSelectionText(str, from, to);
}
} // namespace layouts

View file

@ -22,10 +22,12 @@ public:
enum Flags : uint8_t {
RequiresBufferUpdate = 1 << 1,
RequiresLayout = 1 << 2,
AlternateBackground = 1 << 3
AlternateBackground = 1 << 3,
Collapsed = 1 << 4,
Expanded = 1 << 5,
};
MessageLayout(MessagePtr message);
MessageLayout(MessagePtr message_);
~MessageLayout();
Message *getMessage();
@ -37,10 +39,10 @@ public:
util::FlagsEnum<Flags> flags;
// Layout
bool layout(int width, float scale, MessageElement::Flags flags);
bool layout(int width, float scale_, MessageElement::Flags flags);
// Painting
void paint(QPainter &painter, int y, int messageIndex, Selection &selection,
void paint(QPainter &painter, int width, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused);
void invalidateBuffer();
void deleteBuffer();
@ -50,30 +52,28 @@ public:
const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const;
int getSelectionIndex(QPoint position);
void addSelectionText(QString &str, int from, int to);
void addSelectionText(QString &str, int from = 0, int to = INT_MAX);
// Misc
bool isDisabled() const;
private:
// variables
MessagePtr message;
MessageLayoutContainer container;
std::shared_ptr<QPixmap> buffer = nullptr;
bool bufferValid = false;
MessagePtr message_;
MessageLayoutContainer container_;
std::shared_ptr<QPixmap> buffer_ = nullptr;
bool bufferValid_ = false;
int height = 0;
int height_ = 0;
int currentLayoutWidth = -1;
int fontGeneration = -1;
int emoteGeneration = -1;
QString timestampFormat;
float scale = -1;
unsigned int bufferUpdatedCount = 0;
int currentLayoutWidth_ = -1;
int layoutState_ = -1;
float scale_ = -1;
unsigned int bufferUpdatedCount_ = 0;
MessageElement::Flags currentWordFlags = MessageElement::None;
MessageElement::Flags currentWordFlags_ = MessageElement::None;
int collapsedHeight = 32;
int collapsedHeight_ = 32;
// methods
void actuallyLayout(int width, MessageElement::Flags flags);

View file

@ -9,6 +9,7 @@
#include <QPainter>
#define COMPACT_EMOTES_OFFSET 6
#define MAX_UNCOLLAPSED_LINES (getApp()->settings->collpseMessagesMinLines.getValue())
namespace chatterino {
namespace messages {
@ -36,6 +37,12 @@ void MessageLayoutContainer::begin(int _width, float _scale, Message::MessageFla
this->width = _width;
this->scale = _scale;
this->flags = _flags;
auto mediumFontMetrics = getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, _scale);
this->textLineHeight = mediumFontMetrics.height();
this->spaceWidth = mediumFontMetrics.width(' ');
this->dotdotdotWidth = mediumFontMetrics.width("...");
this->_canAddMessages = true;
this->_isCollapsed = false;
}
void MessageLayoutContainer::clear()
@ -68,12 +75,12 @@ void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element
bool MessageLayoutContainer::canAddElements()
{
return !(this->flags & Message::MessageFlags::Collapsed && line >= 3);
return this->_canAddMessages;
}
void MessageLayoutContainer::_addElement(MessageLayoutElement *element)
void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool forceAdd)
{
if (!this->canAddElements()) {
if (!this->canAddElements() && !forceAdd) {
delete element;
return;
}
@ -129,6 +136,11 @@ void MessageLayoutContainer::breakLine()
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
}
// if (element->getCreator().getFlags() & MessageElement::Badges) {
if (element->getRect().height() < this->textLineHeight) {
yExtra -= (this->textLineHeight - element->getRect().height()) / 2;
}
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
element->getRect().y() + this->lineHeight + yExtra));
}
@ -146,6 +158,12 @@ void MessageLayoutContainer::breakLine()
this->lineStart = this->elements.size();
// this->currentX = (int)(this->scale * 8);
if (this->canCollapse() && line + 1 >= MAX_UNCOLLAPSED_LINES) {
this->_canAddMessages = false;
return;
}
this->currentX = 0;
this->currentY += this->lineHeight;
this->height = this->currentY + (this->margin.bottom * this->scale);
@ -160,15 +178,32 @@ bool MessageLayoutContainer::atStartOfLine()
bool MessageLayoutContainer::fitsInLine(int _width)
{
return this->currentX + _width <= this->width - this->margin.left - this->margin.right;
return this->currentX + _width <=
(this->width - this->margin.left - this->margin.right -
(this->line + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth : 0));
}
void MessageLayoutContainer::end()
{
if (!this->canAddElements()) {
static TextElement dotdotdot("...", MessageElement::Collapsed, MessageColor::Link);
static QString dotdotdotText("...");
auto *element = new TextLayoutElement(
dotdotdot, dotdotdotText, QSize(this->dotdotdotWidth, this->textLineHeight),
QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale);
// getApp()->themes->messages.textColors.system
this->_addElement(element, true);
this->_isCollapsed = true;
}
if (!this->atStartOfLine()) {
this->breakLine();
}
this->height += this->lineHeight;
if (this->lines.size() != 0) {
this->lines[0].rect.setTop(-100000);
this->lines.back().rect.setBottom(100000);
@ -177,6 +212,17 @@ void MessageLayoutContainer::end()
}
}
bool MessageLayoutContainer::canCollapse()
{
return getApp()->settings->collpseMessagesMinLines.getValue() > 0 &&
this->flags & Message::MessageFlags::Collapsed;
}
bool MessageLayoutContainer::isCollapsed()
{
return this->_isCollapsed;
}
MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
{
for (std::unique_ptr<MessageLayoutElement> &element : this->elements) {
@ -429,8 +475,6 @@ void MessageLayoutContainer::addSelectionText(QString &str, int from, int to)
for (std::unique_ptr<MessageLayoutElement> &ele : this->elements) {
int c = ele->getSelectionIndexCount();
qDebug() << c;
if (first) {
if (index + c > from) {
ele->addCopyTextToString(str, from - index, to - index);

View file

@ -76,6 +76,8 @@ struct MessageLayoutContainer {
int getLastCharacterIndex() const;
void addSelectionText(QString &str, int from, int to);
bool isCollapsed();
private:
struct Line {
int startIndex;
@ -86,7 +88,7 @@ private:
};
// helpers
void _addElement(MessageLayoutElement *element);
void _addElement(MessageLayoutElement *element, bool forceAdd = false);
// variables
float scale = 1.f;
@ -100,6 +102,12 @@ private:
size_t lineStart = 0;
int lineHeight = 0;
int spaceWidth = 4;
int textLineHeight = 0;
int dotdotdotWidth = 0;
bool _canAddMessages = true;
bool _isCollapsed = false;
bool canCollapse();
std::vector<std::unique_ptr<MessageLayoutElement>> elements;
std::vector<Line> lines;

View file

@ -188,7 +188,7 @@ int TextLayoutElement::getMouseOverIndex(const QPoint &abs)
auto app = getApp();
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale);
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
int x = this->getRect().left();
@ -209,7 +209,7 @@ int TextLayoutElement::getXFromIndex(int index)
{
auto app = getApp();
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale);
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
if (index <= 0) {
return this->getRect().left();

View file

@ -21,6 +21,8 @@ SBHighlight Message::getScrollBarHighlight() const
{
if (this->flags & Message::Highlighted) {
return SBHighlight(SBHighlight::Highlight);
} else if (this->flags & Message::Subscription) {
return SBHighlight(SBHighlight::Subscription);
}
return SBHighlight();
}
@ -32,7 +34,19 @@ MessagePtr Message::createSystemMessage(const QString &text)
message->addElement(new TimestampElement(QTime::currentTime()));
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
message->flags.EnableFlag(MessageFlags::System);
message->flags |= MessageFlags::System;
message->flags |= MessageFlags::DoNotTriggerNotification;
message->searchText = text;
return message;
}
MessagePtr Message::createMessage(const QString &text)
{
MessagePtr message(new Message);
message->addElement(new TimestampElement(QTime::currentTime()));
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::Text));
message->searchText = text;
return message;
@ -63,7 +77,7 @@ MessagePtr Message::createTimeoutMessage(const QString &username, const QString
if (reason.length() > 0) {
text.append(": \"");
text.append(util::ParseTagString(reason));
text.append(util::parseTagString(reason));
text.append("\"");
}
text.append(".");
@ -89,7 +103,6 @@ MessagePtr Message::createTimeoutMessage(const providers::twitch::BanAction &act
msg->timeoutUser = action.target.name;
msg->count = count;
msg->banAction.reset(new providers::twitch::BanAction(action));
QString text;

View file

@ -18,6 +18,7 @@ namespace messages {
struct Message {
Message()
: parseTime(QTime::currentTime())
{
util::DebugCount::increase("messages");
}
@ -39,6 +40,8 @@ struct Message {
Collapsed = (1 << 7),
DisconnectedMessage = (1 << 8),
Untimeout = (1 << 9),
PubSub = (1 << 10),
Subscription = (1 << 11),
};
util::FlagsEnum<MessageFlags> flags;
@ -50,7 +53,6 @@ struct Message {
QString localizedName;
QString timeoutUser;
std::unique_ptr<providers::twitch::BanAction> banAction;
uint32_t count = 1;
// Messages should not be added after the message is done initializing.
@ -65,6 +67,7 @@ private:
public:
static std::shared_ptr<Message> createSystemMessage(const QString &text);
static std::shared_ptr<Message> createMessage(const QString &text);
static std::shared_ptr<Message> createTimeoutMessage(const QString &username,
const QString &durationInSeconds,

View file

@ -70,8 +70,8 @@ ImageElement::ImageElement(Image *_image, MessageElement::Flags flags)
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{
if (_flags & this->getFlags()) {
QSize size(this->image->getWidth() * this->image->getScale() * container.getScale(),
this->image->getHeight() * this->image->getScale() * container.getScale());
QSize size(this->image->getScaledWidth() * container.getScale(),
this->image->getScaledHeight() * container.getScale());
container.addElement(
(new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink()));
@ -97,19 +97,10 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
return;
}
int quality = getApp()->settings->preferredEmoteQuality;
Image *_image = this->data.getImage(container.getScale());
Image *_image;
if (quality == 3 && this->data.image3x != nullptr) {
_image = this->data.image3x;
} else if (quality >= 2 && this->data.image2x != nullptr) {
_image = this->data.image2x;
} else {
_image = this->data.image1x;
}
QSize size((int)(container.getScale() * _image->getScaledWidth()),
(int)(container.getScale() * _image->getScaledHeight()));
QSize size(int(container.getScale() * _image->getScaledWidth()),
int(container.getScale() * _image->getScaledHeight()));
container.addElement(
(new ImageLayoutElement(*this, _image, size))->setLink(this->getLink()));
@ -139,7 +130,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
auto app = getApp();
if (_flags & this->getFlags()) {
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, container.getScale());
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, container.getScale());
for (Word &word : this->words) {
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
@ -242,7 +233,7 @@ TextElement *TimestampElement::formatTime(const QTime &time)
QString format = locale.toString(time, getApp()->settings->timestampFormat);
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::Medium);
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::ChatMedium);
}
// TWITCH MODERATION

View file

@ -158,7 +158,7 @@ class TextElement : public MessageElement
public:
TextElement(const QString &text, MessageElement::Flags flags,
const MessageColor &color = MessageColor::Text,
FontStyle style = FontStyle::Medium);
FontStyle style = FontStyle::ChatMedium);
~TextElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
@ -169,7 +169,6 @@ public:
// b) which size it wants
class EmoteElement : public MessageElement
{
const util::EmoteData data;
std::unique_ptr<TextElement> textElement;
public:
@ -177,6 +176,8 @@ public:
~EmoteElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
const util::EmoteData data;
};
// contains a text, formated depending on the preferences

View file

@ -7,6 +7,7 @@ struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
};
} // namespace messages

View file

@ -42,6 +42,11 @@ struct SelectionItem {
{
return this->messageIndex == b.messageIndex && this->charIndex == b.charIndex;
}
bool operator!=(const SelectionItem &b) const
{
return this->operator==(b);
}
};
struct Selection {

5
src/providerid.hpp Normal file
View file

@ -0,0 +1,5 @@
#pragma once
namespace chatterino {
enum class ProviderId { Twitch };
}

View file

@ -4,6 +4,8 @@
#include "messages/limitedqueuesnapshot.hpp"
#include "messages/message.hpp"
#include <QCoreApplication>
using namespace chatterino::messages;
namespace chatterino {
@ -13,14 +15,14 @@ namespace irc {
AbstractIrcServer::AbstractIrcServer()
{
// Initialize the connections
this->writeConnection.reset(new Communi::IrcConnection);
this->writeConnection.reset(new IrcConnection);
this->writeConnection->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(this->writeConnection.get(), &Communi::IrcConnection::messageReceived,
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
// Listen to read connection message signals
this->readConnection.reset(new Communi::IrcConnection);
this->readConnection.reset(new IrcConnection);
this->readConnection->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::messageReceived,
@ -31,9 +33,13 @@ AbstractIrcServer::AbstractIrcServer()
[this] { this->onConnected(); });
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::disconnected,
[this] { this->onDisconnected(); });
// listen to reconnect request
this->readConnection->reconnectRequested.connect([this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] { this->connect(); });
}
Communi::IrcConnection *AbstractIrcServer::getReadConnection() const
IrcConnection *AbstractIrcServer::getReadConnection() const
{
return this->readConnection.get();
}
@ -231,7 +237,7 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
{
auto fakeMessage = Communi::IrcMessage::fromData(data.toUtf8(), this->readConnection.get());
this->privateMessageReceived(qobject_cast<Communi::IrcPrivateMessage *>(fakeMessage));
this->messageReceived(fakeMessage);
}
void AbstractIrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)

View file

@ -2,9 +2,9 @@
#include "channel.hpp"
#include <IrcConnection>
#include <IrcMessage>
#include <pajlada/signals/signal.hpp>
#include <providers/irc/ircconnection2.hpp>
#include <functional>
#include <mutex>
@ -19,7 +19,7 @@ public:
virtual ~AbstractIrcServer() = default;
// connection
Communi::IrcConnection *getReadConnection() const;
IrcConnection *getReadConnection() const;
void connect();
void disconnect();
@ -33,7 +33,7 @@ public:
// signals
pajlada::Signals::NoArgSignal connected;
pajlada::Signals::NoArgSignal disconnected;
pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
void addFakeMessage(const QString &data);
@ -43,8 +43,7 @@ public:
protected:
AbstractIrcServer();
virtual void initializeConnection(Communi::IrcConnection *connection, bool isRead,
bool isWrite) = 0;
virtual void initializeConnection(IrcConnection *connection, bool isRead, bool isWrite) = 0;
virtual std::shared_ptr<Channel> createChannel(const QString &channelName) = 0;
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
@ -64,10 +63,12 @@ protected:
private:
void initConnection();
std::unique_ptr<Communi::IrcConnection> writeConnection = nullptr;
std::unique_ptr<Communi::IrcConnection> readConnection = nullptr;
std::unique_ptr<IrcConnection> writeConnection = nullptr;
std::unique_ptr<IrcConnection> readConnection = nullptr;
std::mutex connectionMutex;
QTimer pingTimer;
};
} // namespace irc

View file

@ -0,0 +1,36 @@
#include "ircconnection2.hpp"
namespace chatterino {
namespace providers {
namespace irc {
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
this->pingTimer_.setInterval(5000);
this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (!this->recentlyReceivedMessage_.load()) {
this->sendRaw("PING");
this->reconnectTimer_.start();
}
this->recentlyReceivedMessage_ = false;
});
this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout,
[this] { reconnectRequested.invoke(); });
QObject::connect(this, &Communi::IrcConnection::messageReceived, [this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive()) {
this->reconnectTimer_.stop();
}
});
}
} // namespace irc
} // namespace providers
} // namespace chatterino

View file

@ -0,0 +1,27 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
namespace chatterino {
namespace providers {
namespace irc {
class IrcConnection : public Communi::IrcConnection
{
public:
IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested;
private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
};
} // namespace irc
} // namespace providers
} // namespace chatterino

View file

@ -1,6 +1,7 @@
#include "ircmessagehandler.hpp"
#include "application.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "debug/log.hpp"
#include "messages/limitedqueue.hpp"
#include "messages/message.hpp"
@ -10,6 +11,9 @@
#include "providers/twitch/twitchserver.hpp"
#include "singletons/resourcemanager.hpp"
#include "singletons/windowmanager.hpp"
#include "util/irchelpers.hpp"
#include <IrcMessage>
using namespace chatterino::singletons;
using namespace chatterino::messages;
@ -24,116 +28,145 @@ IrcMessageHandler &IrcMessageHandler::getInstance()
return instance;
}
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server)
{
this->addMessage(message, message->target(), message->content(), server, false);
}
void IrcMessageHandler::addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchServer &server, bool isSub)
{
QString channelName;
if (!trimChannelName(target, channelName)) {
return;
}
auto chan = server.getChannelOrEmpty(channelName);
if (chan->isEmpty()) {
return;
}
messages::MessageParseArgs args;
if (isSub) {
args.trimSubscriberUsername = true;
}
TwitchMessageBuilder builder(chan.get(), message, content, args);
if (isSub || !builder.isIgnored()) {
messages::MessagePtr msg = builder.build();
if (isSub) {
msg->flags |= messages::Message::Subscription;
msg->flags &= ~messages::Message::Highlighted;
} else {
if (msg->flags & messages::Message::Highlighted) {
server.mentionsChannel->addMessage(msg);
getApp()->highlights->addHighlight(msg);
}
}
chan->addMessage(msg);
}
}
void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
{
const auto &tags = message->tags();
auto iterator = tags.find("room-id");
if (iterator != tags.end()) {
auto roomID = iterator.value().toString();
QStringList words = QString(message->toData()).split("#");
// ensure the format is valid
if (words.length() < 2) {
return;
}
auto app = getApp();
QString channelName = words.at(1);
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) {
// get twitch channel
QString chanName;
if (!trimChannelName(message->parameter(0), chanName)) {
return;
}
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
TwitchChannel *twitchChannel = dynamic_cast<twitch::TwitchChannel *>(chan.get());
if (twitchChannel) {
// room-id
decltype(tags.find("xD")) it;
if ((it = tags.find("room-id")) != tags.end()) {
auto roomID = it.value().toString();
if (auto twitchChannel = dynamic_cast<twitch::TwitchChannel *>(channel.get())) {
// set the room id of the channel
twitchChannel->setRoomID(roomID);
}
app->resources->loadChannelData(roomID);
}
// Room modes
TwitchChannel::RoomModes roomModes = twitchChannel->getRoomModes();
if ((it = tags.find("emote-only")) != tags.end()) {
roomModes.emoteOnly = it.value() == "1";
}
if ((it = tags.find("subs-only")) != tags.end()) {
roomModes.submode = it.value() == "1";
}
if ((it = tags.find("slow")) != tags.end()) {
roomModes.slowMode = it.value().toInt();
}
if ((it = tags.find("r9k")) != tags.end()) {
roomModes.r9k = it.value() == "1";
}
if ((it = tags.find("broadcaster-lang")) != tags.end()) {
roomModes.broadcasterLang = it.value().toString();
}
twitchChannel->setRoomModes(roomModes);
}
}
void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
{
// check parameter count
if (message->parameters().length() < 1) {
return;
// // check parameter count
// if (message->parameters().length() < 1) {
// return;
// }
}
// QString chanName;
// if (!TrimChannelName(message->parameter(0), chanName)) {
// return;
// }
QString chanName;
if (!trimChannelName(message->parameter(0), chanName)) {
return;
}
// auto app = getApp();
auto app = getApp();
// // get channel
// auto chan = app->twitch.server->getChannelOrEmpty(chanName);
// get channel
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
// if (chan->isEmpty()) {
// debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found",
// chanName);
// return;
// }
if (chan->isEmpty()) {
debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found",
chanName);
return;
}
// // check if the chat has been cleared by a moderator
// if (message->parameters().length() == 1) {
// chan->addMessage(Message::createSystemMessage("Chat has been cleared by a
// moderator."));
// check if the chat has been cleared by a moderator
if (message->parameters().length() == 1) {
chan->addMessage(Message::createSystemMessage("Chat has been cleared by a moderator."));
// return;
// }
return;
}
// // get username, duration and message of the timed out user
// QString username = message->parameter(1);
// QString durationInSeconds, reason;
// QVariant v = message->tag("ban-duration");
// if (v.isValid()) {
// durationInSeconds = v.toString();
// }
// get username, duration and message of the timed out user
QString username = message->parameter(1);
QString durationInSeconds, reason;
QVariant v = message->tag("ban-duration");
if (v.isValid()) {
durationInSeconds = v.toString();
}
// v = message->tag("ban-reason");
// if (v.isValid()) {
// reason = v.toString();
// }
v = message->tag("ban-reason");
if (v.isValid()) {
reason = v.toString();
}
// // add the notice that the user has been timed out
// LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
// bool addMessage = true;
// int snapshotLength = snapshot.getLength();
auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds, reason, false);
chan->addOrReplaceTimeout(timeoutMsg);
// for (int i = std::max(0, snapshotLength - 20); i < snapshotLength; i++) {
// auto &s = snapshot[i];
// if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == username) {
// MessagePtr replacement(
// Message::createTimeoutMessage(username, durationInSeconds, reason, true));
// chan->replaceMessage(s, replacement);
// addMessage = false;
// break;
// }
// }
// if (addMessage) {
// chan->addMessage(Message::createTimeoutMessage(username, durationInSeconds, reason,
// false));
// }
// // disable the messages from the user
// for (int i = 0; i < snapshotLength; i++) {
// auto &s = snapshot[i];
// if (!(s->flags & Message::Timeout) && s->loginName == username) {
// s->flags.EnableFlag(Message::Disabled);
// }
// }
// // refresh all
// app->windows->repaintVisibleChatWidgets(chan.get());
// refresh all
app->windows->repaintVisibleChatWidgets(chan.get());
}
void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
@ -144,7 +177,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
auto app = getApp();
QString channelName;
if (!TrimChannelName(message->parameter(0), channelName)) {
if (!trimChannelName(message->parameter(0), channelName)) {
return;
}
@ -180,6 +213,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
app->twitch.server->mentionsChannel->addMessage(_message);
}
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
c->addMessage(_message);
if (app->settings->inlineWhispers) {
@ -190,9 +225,51 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
}
}
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server)
{
// do nothing
auto data = message->toData();
auto tags = message->tags();
auto parameters = message->parameters();
auto target = parameters[0];
QString msgType = tags.value("msg-id", "").toString();
QString content;
if (parameters.size() >= 2) {
content = parameters[1];
}
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
// Sub-specific message. I think it's only allowed for "resub" messages atm
if (!content.isEmpty()) {
this->addMessage(message, target, content, server, true);
}
}
auto it = tags.find("system-msg");
if (it != tags.end()) {
auto newMessage =
messages::Message::createSystemMessage(util::parseTagString(it.value().toString()));
newMessage->flags |= messages::Message::Subscription;
QString channelName;
if (message->parameters().size() < 1) {
return;
}
if (!trimChannelName(message->parameter(0), channelName)) {
return;
}
auto chan = server.getChannelOrEmpty(channelName);
if (!chan->isEmpty()) {
chan->addMessage(newMessage);
}
}
}
void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
@ -231,7 +308,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
// auto channel = app->twitch.server->getChannelOrEmpty(channelName);
// if (channel->isEmpty()) {
// debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager",
// debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel
// manager",
// channelName);
// return;
// }
@ -257,6 +335,26 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
this->handleNoticeMessage(message);
}
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
{
auto app = getApp();
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
twitchChannel->addJoinedUser(message->nick());
}
}
void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
{
auto app = getApp();
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
twitchChannel->addPartedUser(message->nick());
}
}
} // namespace twitch
} // namespace providers
} // namespace chatterino

View file

@ -6,6 +6,8 @@ namespace chatterino {
namespace providers {
namespace twitch {
class TwitchServer;
class IrcMessageHandler
{
IrcMessageHandler() = default;
@ -13,14 +15,23 @@ class IrcMessageHandler
public:
static IrcMessageHandler &getInstance();
void handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server);
void handleModeMessage(Communi::IrcMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target, const QString &content,
TwitchServer &server, bool isResub);
};
} // namespace twitch

View file

@ -3,7 +3,6 @@
#include "debug/log.hpp"
#include "providers/twitch/pubsubactions.hpp"
#include "providers/twitch/pubsubhelpers.hpp"
#include "singletons/accountmanager.hpp"
#include "util/rapidjson-helpers.hpp"
#include <rapidjson/error/en.h>

View file

@ -1,7 +1,6 @@
#include "providers/twitch/pubsubhelpers.hpp"
#include "providers/twitch/pubsubactions.hpp"
#include "singletons/accountmanager.hpp"
#include "util/rapidjson-helpers.hpp"
namespace chatterino {

View file

@ -12,7 +12,7 @@ namespace twitch {
TwitchAccount::TwitchAccount(const QString &_username, const QString &_oauthToken,
const QString &_oauthClient, const QString &_userID)
: controllers::accounts::Account("Twitch")
: controllers::accounts::Account(ProviderId::Twitch)
, oauthClient(_oauthClient)
, oauthToken(_oauthToken)
, userName(_username)

View file

@ -55,7 +55,6 @@ public:
bool isAnon() const;
void loadIgnores();
void ignore(const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished);
void ignoreByID(const QString &targetUserID, const QString &targetName,

View file

@ -15,6 +15,9 @@ TwitchAccountManager::TwitchAccountManager()
auto currentUser = this->getCurrent();
currentUser->loadIgnores();
});
this->accounts.itemRemoved.connect(
[this](const auto &acc) { this->removeUser(acc.item.get()); });
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
@ -32,7 +35,7 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
std::lock_guard<std::mutex> lock(this->mutex);
for (const auto &user : this->users) {
for (const auto &user : this->accounts.getVector()) {
userNames.push_back(user->getUserName());
}
@ -44,7 +47,7 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
{
std::lock_guard<std::mutex> lock(this->mutex);
for (const auto &user : this->users) {
for (const auto &user : this->accounts.getVector()) {
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) {
return user;
}
@ -134,25 +137,16 @@ void TwitchAccountManager::load()
});
}
bool TwitchAccountManager::removeUser(const QString &username)
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
if (!this->userExists(username)) {
return false;
}
const auto &accs = this->accounts.getVector();
this->mutex.lock();
this->users.erase(std::remove_if(this->users.begin(), this->users.end(), [username](auto user) {
if (user->getUserName() == username) {
std::string userID(user->getUserId().toStdString());
assert(!userID.empty());
std::string userID(account->getUserId().toStdString());
if (!userID.empty()) {
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
return true;
}
return false;
}));
this->mutex.unlock();
if (username == qS(this->currentUsername.getValue())) {
if (account->getUserName() == qS(this->currentUsername.getValue())) {
// The user that was removed is the current user, log into the anonymous user
this->currentUsername = "";
}
@ -187,9 +181,9 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
std::lock_guard<std::mutex> lock(this->mutex);
// std::lock_guard<std::mutex> lock(this->mutex);
this->users.push_back(newUser);
this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded;
}

View file

@ -15,10 +15,11 @@
//
namespace chatterino {
namespace singletons {
class AccountManager;
} // namespace singletons
namespace controllers {
namespace accounts {
class AccountController;
}
} // namespace controllers
namespace providers {
namespace twitch {
@ -46,8 +47,6 @@ public:
void reloadUsers();
void load();
bool removeUser(const QString &username);
pajlada::Settings::Setting<std::string> currentUsername = {"/accounts/current", ""};
pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated;
@ -63,14 +62,14 @@ private:
UserAdded,
};
AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser;
std::shared_ptr<TwitchAccount> anonymousUser;
std::vector<std::shared_ptr<TwitchAccount>> users;
mutable std::mutex mutex;
friend class chatterino::singletons::AccountManager;
friend class chatterino::controllers::accounts::AccountController;
};
} // namespace twitch

View file

@ -5,7 +5,6 @@
#include "messages/message.hpp"
#include "providers/twitch/pubsub.hpp"
#include "providers/twitch/twitchmessagebuilder.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp"
#include "singletons/settingsmanager.hpp"
@ -45,7 +44,8 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->refreshLiveStatus(); //
});
this->managedConnect(app->accounts->Twitch.currentUserChanged, [this]() { this->setMod(false); });
this->managedConnect(app->accounts->twitch.currentUserChanged,
[this]() { this->setMod(false); });
auto refreshPubSubState = [=]() {
if (!this->hasModRights()) {
@ -56,7 +56,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
return;
}
auto account = app->accounts->Twitch.getCurrent();
auto account = app->accounts->twitch.getCurrent();
if (account && !account->getUserId().isEmpty()) {
app->twitch.pubsub->listenToChannelModerationActions(this->roomID, account);
}
@ -64,7 +64,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->userStateChanged.connect(refreshPubSubState);
this->roomIDchanged.connect(refreshPubSubState);
this->managedConnect(app->accounts->Twitch.currentUserChanged, refreshPubSubState);
this->managedConnect(app->accounts->twitch.currentUserChanged, refreshPubSubState);
refreshPubSubState();
this->fetchMessages.connect([this] {
@ -85,7 +85,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
};
auto doRefreshChatters = [=]() {
const auto streamStatus = this->GetStreamStatus();
const auto streamStatus = this->getStreamStatus();
if (app->settings->onlyFetchChattersForSmallerStreamers) {
if (streamStatus.live && streamStatus.viewerCount > app->settings->smallStreamerLimit) {
@ -102,6 +102,10 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->chattersListTimer = new QTimer;
QObject::connect(this->chattersListTimer, &QTimer::timeout, doRefreshChatters);
this->chattersListTimer->start(5 * 60 * 1000);
// for (int i = 0; i < 1000; i++) {
// this->addMessage(messages::Message::createSystemMessage("asdf"));
// }
}
TwitchChannel::~TwitchChannel()
@ -186,7 +190,7 @@ bool TwitchChannel::isBroadcaster()
{
auto app = getApp();
return this->name == app->accounts->Twitch.getCurrent()->getUserName();
return this->name == app->accounts->twitch.getCurrent()->getUserName();
}
bool TwitchChannel::hasModRights()
@ -206,6 +210,93 @@ void TwitchChannel::addRecentChatter(const std::shared_ptr<messages::Message> &m
this->completionModel.addUser(message->displayName);
}
void TwitchChannel::addJoinedUser(const QString &user)
{
auto *app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!app->settings->showJoins.getValue()) {
return;
}
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
joinedUsers << user;
if (!this->joinedUsersMergeQueued) {
this->joinedUsersMergeQueued = true;
QTimer::singleShot(500, &this->object, [this] {
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
auto message = messages::Message::createSystemMessage("Users joined: " +
this->joinedUsers.join(", "));
message->flags |= messages::Message::Collapsed;
this->addMessage(message);
this->joinedUsers.clear();
this->joinedUsersMergeQueued = false;
});
}
}
void TwitchChannel::addPartedUser(const QString &user)
{
auto *app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!app->settings->showJoins.getValue()) {
return;
}
std::lock_guard<std::mutex> guard(this->partedUserMutex);
partedUsers << user;
if (!this->partedUsersMergeQueued) {
this->partedUsersMergeQueued = true;
QTimer::singleShot(500, &this->object, [this] {
std::lock_guard<std::mutex> guard(this->partedUserMutex);
auto message = messages::Message::createSystemMessage("Users parted: " +
this->partedUsers.join(", "));
message->flags |= messages::Message::Collapsed;
this->addMessage(message);
this->partedUsers.clear();
this->partedUsersMergeQueued = false;
});
}
}
TwitchChannel::RoomModes TwitchChannel::getRoomModes()
{
std::lock_guard<std::mutex> lock(this->roomModeMutex);
return this->roomModes;
}
void TwitchChannel::setRoomModes(const RoomModes &_roomModes)
{
{
std::lock_guard<std::mutex> lock(this->roomModeMutex);
this->roomModes = _roomModes;
}
this->roomModesChanged.invoke();
}
bool TwitchChannel::isLive() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus.live;
}
TwitchChannel::StreamStatus TwitchChannel::getStreamStatus() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus;
}
void TwitchChannel::setLive(bool newLiveStatus)
{
bool gotNewLiveStatus = false;
@ -289,6 +380,11 @@ void TwitchChannel::refreshLiveStatus()
QString::number(diff / 3600) + "h " + QString::number(diff % 3600 / 60) + "m";
channel->streamStatus.rerun = false;
if (stream.HasMember("stream_type")) {
channel->streamStatus.streamType = stream["stream_type"].GetString();
} else {
channel->streamStatus.streamType = QString();
}
if (stream.HasMember("broadcast_platform")) {
const auto &broadcastPlatformValue = stream["broadcast_platform"];

View file

@ -7,6 +7,7 @@
#include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp"
#include "util/concurrentmap.hpp"
#include "util/mutexvalue.hpp"
#include <pajlada/signals/signalholder.hpp>
@ -31,6 +32,7 @@ public:
QString title;
QString game;
QString uptime;
QString streamType;
};
struct UserState {
@ -38,6 +40,15 @@ public:
bool broadcaster;
};
struct RoomModes {
bool submode = false;
bool r9k = false;
bool emoteOnly = false;
// int folowerOnly = 0;
int slowMode = 0;
QString broadcasterLang;
};
~TwitchChannel() final;
void reloadChannelEmotes();
@ -52,6 +63,8 @@ public:
bool hasModRights();
void addRecentChatter(const std::shared_ptr<messages::Message> &message) final;
void addJoinedUser(const QString &user);
void addPartedUser(const QString &user);
const std::shared_ptr<chatterino::util::EmoteMap> bttvChannelEmotes;
const std::shared_ptr<chatterino::util::EmoteMap> ffzChannelEmotes;
@ -66,25 +79,21 @@ public:
pajlada::Signals::NoArgBoltSignal fetchMessages;
pajlada::Signals::NoArgSignal userStateChanged;
pajlada::Signals::NoArgSignal roomModesChanged;
QString roomID;
StreamStatus GetStreamStatus() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus;
}
RoomModes getRoomModes();
void setRoomModes(const RoomModes &roomModes);
StreamStatus getStreamStatus() const;
struct NameOptions {
QString displayName;
QString localizedName;
};
bool IsLive() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus.live;
}
bool isLive() const;
private:
explicit TwitchChannel(const QString &channelName, Communi::IrcConnection *readConnection);
@ -103,6 +112,16 @@ private:
bool mod;
QByteArray messageSuffix;
QString lastSentMessage;
RoomModes roomModes;
std::mutex roomModeMutex;
QObject object;
std::mutex joinedUserMutex;
QStringList joinedUsers;
bool joinedUsersMergeQueued = false;
std::mutex partedUserMutex;
QStringList partedUsers;
bool partedUsersMergeQueued = false;
Communi::IrcConnection *readConnection;

View file

@ -5,7 +5,7 @@ namespace chatterino {
namespace providers {
namespace twitch {
bool TrimChannelName(const QString &channelName, QString &outChannelName)
bool trimChannelName(const QString &channelName, QString &outChannelName)
{
if (channelName.length() < 3) {
debug::Log("channel name length below 3");

View file

@ -6,7 +6,7 @@ namespace chatterino {
namespace providers {
namespace twitch {
bool TrimChannelName(const QString &channelName, QString &outChannelName);
bool trimChannelName(const QString &channelName, QString &outChannelName);
} // namespace twitch
} // namespace providers

View file

@ -1,11 +1,11 @@
#include "providers/twitch/twitchmessagebuilder.hpp"
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "controllers/ignores/ignorecontroller.hpp"
#include "debug/log.hpp"
#include "providers/twitch/twitchchannel.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp"
#include "singletons/resourcemanager.hpp"
@ -68,7 +68,18 @@ bool TwitchMessageBuilder::isIgnored() const
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user : app->accounts->Twitch.getCurrent()->getIgnores()) {
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) {
debug::Log("Blocking message because it's from blocked user {}", user.name);
return true;
}
}
}
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) {
debug::Log("Blocking message because it's from blocked user {}", user.name);
return true;
@ -86,12 +97,14 @@ MessagePtr TwitchMessageBuilder::build()
// PARSING
this->parseUsername();
#ifdef XD
if (this->originalMessage.length() > 100) {
//#ifdef XD
// if (this->originalMessage.length() > 100) {
// this->message->flags |= Message::Collapsed;
// this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed,
// MessageElement::Collapsed);
// }
//#endif
this->message->flags |= Message::Collapsed;
this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed, MessageElement::Collapsed);
}
#endif
// PARSING
this->parseMessageID();
@ -141,15 +154,8 @@ MessagePtr TwitchMessageBuilder::build()
this->appendTwitchEmote(ircMessage, emote, twitchEmotes);
}
struct {
bool operator()(const std::pair<long, util::EmoteData> &lhs,
const std::pair<long, util::EmoteData> &rhs)
{
return lhs.first < rhs.first;
}
} customLess;
std::sort(twitchEmotes.begin(), twitchEmotes.end(), customLess);
std::sort(twitchEmotes.begin(), twitchEmotes.end(),
[](const auto &a, const auto &b) { return a.first < b.first; });
}
auto currentTwitchEmote = twitchEmotes.begin();
@ -282,10 +288,16 @@ void TwitchMessageBuilder::parseUsername()
// username
this->userName = this->ircMessage->nick();
if (this->userName.isEmpty()) {
if (this->userName.isEmpty() || this->args.trimSubscriberUsername) {
this->userName = this->tags.value(QLatin1String("login")).toString();
}
// display name
// auto displayNameVariant = this->tags.value("display-name");
// if (displayNameVariant.isValid()) {
// this->userName = displayNameVariant.toString() + " (" + this->userName + ")";
// }
this->message->loginName = this->userName;
}
@ -350,14 +362,14 @@ void TwitchMessageBuilder::appendUsername()
} else if (this->args.isReceivedWhisper) {
// Sender username
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
FontStyle::MediumBold)
FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, this->userName});
auto currentUser = app->accounts->Twitch.getCurrent();
auto currentUser = app->accounts->twitch.getCurrent();
// Separator
this->emplace<TextElement>("->", MessageElement::Text,
app->themes->messages.textColors.system, FontStyle::Medium);
app->themes->messages.textColors.system, FontStyle::ChatMedium);
QColor selfColor = currentUser->color;
if (!selfColor.isValid()) {
@ -366,14 +378,14 @@ void TwitchMessageBuilder::appendUsername()
// Your own username
this->emplace<TextElement>(currentUser->getUserName() + ":", MessageElement::Text,
selfColor, FontStyle::MediumBold);
selfColor, FontStyle::ChatMediumBold);
} else {
if (!this->action) {
usernameText += ":";
}
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
FontStyle::MediumBold)
FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, this->userName});
}
}
@ -385,7 +397,7 @@ void TwitchMessageBuilder::parseHighlights()
auto app = getApp();
auto currentUser = app->accounts->Twitch.getCurrent();
auto currentUser = app->accounts->twitch.getCurrent();
QString currentUsername = currentUser->getUserName();
@ -571,11 +583,13 @@ void TwitchMessageBuilder::appendTwitchBadges()
QString cheerAmountQS = badge.mid(5);
std::string versionKey = cheerAmountQS.toStdString();
QString tooltip = QString("Twitch cheer ") + cheerAmountQS;
// Try to fetch channel-specific bit badge
try {
const auto &badge = channelResources.badgeSets.at("bits").versions.at(versionKey);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
->setTooltip(tooltip);
continue;
} catch (const std::out_of_range &) {
// Channel does not contain a special bit badge for this version
@ -584,7 +598,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
// Use default bit badge
try {
const auto &badge = app->resources->badgeSets.at("bits").versions.at(versionKey);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
->setTooltip(tooltip);
} catch (const std::out_of_range &) {
debug::Log("No default bit badge for version {} found", versionKey);
continue;

View file

@ -1,16 +1,18 @@
#include "twitchserver.hpp"
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "providers/twitch/ircmessagehandler.hpp"
#include "providers/twitch/twitchaccount.hpp"
#include "providers/twitch/twitchhelpers.hpp"
#include "providers/twitch/twitchmessagebuilder.hpp"
#include "singletons/accountmanager.hpp"
#include "util/posttothread.hpp"
#include <IrcCommand>
#include <cassert>
using namespace Communi;
// using namespace Communi;
using namespace chatterino::singletons;
namespace chatterino {
@ -27,13 +29,14 @@ TwitchServer::TwitchServer()
void TwitchServer::initialize()
{
getApp()->accounts->Twitch.currentUserChanged.connect(
getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { util::postToThread([this] { this->connect(); }); });
}
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead, bool isWrite)
void TwitchServer::initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
bool isWrite)
{
std::shared_ptr<TwitchAccount> account = getApp()->accounts->Twitch.getCurrent();
std::shared_ptr<TwitchAccount> account = getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName();
@ -56,9 +59,9 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
// this->refreshIgnoredUsers(username, oauthClient, oauthToken);
}
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/membership"));
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/commands"));
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/tags"));
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/membership"));
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands"));
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags"));
connection->setHost("irc.chat.twitch.tv");
connection->setPort(6667);
@ -74,69 +77,53 @@ std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
return std::shared_ptr<Channel>(channel);
}
void TwitchServer::privateMessageReceived(IrcPrivateMessage *message)
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
{
QString channelName;
if (!TrimChannelName(message->target(), channelName)) {
return;
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
}
this->onPrivateMessage.invoke(message);
auto chan = this->getChannelOrEmpty(channelName);
if (chan->isEmpty()) {
return;
}
messages::MessageParseArgs args;
TwitchMessageBuilder builder(chan.get(), message, args);
if (!builder.isIgnored()) {
messages::MessagePtr _message = builder.build();
if (_message->flags & messages::Message::Highlighted) {
this->mentionsChannel->addMessage(_message);
}
chan->addMessage(_message);
}
}
void TwitchServer::messageReceived(IrcMessage *message)
void TwitchServer::messageReceived(Communi::IrcMessage *message)
{
// this->readConnection
if (message->type() == IrcMessage::Type::Private) {
if (message->type() == Communi::IrcMessage::Type::Private) {
// We already have a handler for private messages
return;
}
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
if (command == "ROOMSTATE") {
IrcMessageHandler::getInstance().handleRoomStateMessage(message);
handler.handleRoomStateMessage(message);
} else if (command == "CLEARCHAT") {
IrcMessageHandler::getInstance().handleClearChatMessage(message);
handler.handleClearChatMessage(message);
} else if (command == "USERSTATE") {
IrcMessageHandler::getInstance().handleUserStateMessage(message);
handler.handleUserStateMessage(message);
} else if (command == "WHISPER") {
IrcMessageHandler::getInstance().handleWhisperMessage(message);
handler.handleWhisperMessage(message);
} else if (command == "USERNOTICE") {
IrcMessageHandler::getInstance().handleUserNoticeMessage(message);
handler.handleUserNoticeMessage(message, *this);
} else if (command == "MODE") {
IrcMessageHandler::getInstance().handleModeMessage(message);
handler.handleModeMessage(message);
} else if (command == "NOTICE") {
IrcMessageHandler::getInstance().handleNoticeMessage(
static_cast<IrcNoticeMessage *>(message));
handler.handleNoticeMessage(static_cast<Communi::IrcNoticeMessage *>(message));
} else if (command == "JOIN") {
handler.handleJoinMessage(message);
} else if (command == "PART") {
handler.handlePartMessage(message);
}
}
void TwitchServer::writeConnectionMessageReceived(IrcMessage *message)
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
{
switch (message->type()) {
case IrcMessage::Type::Notice: {
case Communi::IrcMessage::Type::Notice: {
IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage(
static_cast<IrcNoticeMessage *>(message));
static_cast<Communi::IrcNoticeMessage *>(message));
} break;
default:;
}
}

View file

@ -3,6 +3,7 @@
#include "providers/irc/abstractircserver.hpp"
#include "providers/twitch/twitchaccount.hpp"
#include "providers/twitch/twitchchannel.hpp"
#include "util/mutexvalue.hpp"
#include <memory>
@ -22,12 +23,17 @@ public:
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
util::MutexValue<QString> lastUserThatWhisperedMe;
// QString getLastWhisperedPerson() const;
// void setLastWhisperedPerson(const QString &person);
const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel;
protected:
void initializeConnection(Communi::IrcConnection *connection, bool isRead,
void initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
bool isWrite) override;
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
@ -38,6 +44,10 @@ protected:
std::shared_ptr<Channel> getCustomChannel(const QString &channelname) override;
QString cleanChannelName(const QString &dirtyChannelName) override;
private:
// mutable std::mutex lastWhisperedPersonMutex;
// QString lastWhisperedPerson;
};
} // namespace twitch

View file

@ -1,12 +0,0 @@
#include "singletons/accountmanager.hpp"
namespace chatterino {
namespace singletons {
void AccountManager::load()
{
this->Twitch.load();
}
} // namespace singletons
} // namespace chatterino

View file

@ -1,21 +0,0 @@
#pragma once
#include "providers/twitch/twitchaccountmanager.hpp"
namespace chatterino {
namespace singletons {
class AccountManager
{
public:
AccountManager() = default;
~AccountManager() = delete;
void load();
providers::twitch::TwitchAccountManager Twitch;
};
} // namespace singletons
} // namespace chatterino

View file

@ -86,8 +86,8 @@ EmoteManager::EmoteManager()
void EmoteManager::initialize()
{
getApp()->accounts->Twitch.currentUserChanged.connect([this] {
auto currentUser = getApp()->accounts->Twitch.getCurrent();
getApp()->accounts->twitch.currentUserChanged.connect([this] {
auto currentUser = getApp()->accounts->twitch.getCurrent();
assert(currentUser);
this->refreshTwitchEmotes(currentUser);
});
@ -130,13 +130,23 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
QString code = emoteObject.value("code").toString();
// emoteObject.value("imageType").toString();
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&] {
util::EmoteData emoteData;
QString link = linkTemplate;
link.detach();
emoteData.image1x = new Image(link.replace("{{id}}", id).replace("{{image}}", "1x"),
1, code, code + "<br />Channel BTTV Emote");
link = linkTemplate;
link.detach();
emoteData.image2x = new Image(link.replace("{{id}}", id).replace("{{image}}", "2x"),
0.5, code, code + "<br />Channel BTTV Emote");
link = linkTemplate;
link.detach();
emoteData.image3x = new Image(link.replace("{{id}}", id).replace("{{image}}", "3x"),
0.25, code, code + "<br />Channel BTTV Emote");
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
link = link.replace("{{id}}", id).replace("{{image}}", "1x");
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&code, &link] {
return util::EmoteData(new Image(link, 1, code, code + "<br/>Channel BTTV Emote"));
return emoteData;
});
this->bttvChannelEmotes.insert(code, emote);
@ -182,9 +192,11 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName,
QJsonObject urls = emoteObject.value("urls").toObject();
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [&code, &urls] {
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [id, &code, &urls] {
util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
return emoteData;
});
@ -436,6 +448,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
[=, &emoteData](const QJsonObject &root) {
emoteData.emoteSets.clear();
emoteData.emoteCodes.clear();
auto emoticonSets = root.value("emoticon_sets").toObject();
for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) {
std::string emoteSetString = it.key().toStdString();
@ -443,7 +456,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
for (QJsonValue emoteValue : emoteSetList) {
QJsonObject emoticon = emoteValue.toObject();
std::string id = emoticon["id"].toString().toStdString();
std::string id = QString::number(emoticon["id"].toInt()).toStdString();
std::string code = emoticon["code"].toString().toStdString();
emoteData.emoteSets[emoteSetString].push_back({id, code});
emoteData.emoteCodes.push_back(code);
@ -483,6 +496,7 @@ void EmoteManager::loadBTTVEmotes()
code + "<br />Global BTTV Emote");
emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, code,
code + "<br />Global BTTV Emote");
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
this->bttvGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString());
@ -510,10 +524,13 @@ void EmoteManager::loadFFZEmotes()
QJsonObject object = emote.toObject();
QString code = object.value("name").toString();
int id = object.value("id").toInt();
QJsonObject urls = object.value("urls").toObject();
util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Global FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
this->ffzGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString());
@ -530,6 +547,20 @@ util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteNa
{
QString _emoteName = emoteName;
_emoteName.replace("<", "&lt;");
_emoteName.replace(">", "&gt;");
static QMap<QString, QString> emoteNameReplacements{
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("}, {"\\&lt\\;3", "&lt;3"},
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"},
{"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
};
auto it = emoteNameReplacements.find(_emoteName);
if (it != emoteNameReplacements.end()) {
_emoteName = it.value();
}
return _twitchEmoteFromCache.getOrAdd(id, [&emoteName, &_emoteName, &id] {
util::EmoteData newEmoteData;

View file

@ -46,16 +46,6 @@ public:
util::EmoteData getTwitchEmoteById(long int id, const QString &emoteName);
int getGeneration()
{
return _generation;
}
void incGeneration()
{
_generation++;
}
pajlada::Signals::NoArgSignal &getGifUpdateSignal();
// Bit badge/emotes?
@ -145,8 +135,6 @@ private:
pajlada::Signals::NoArgSignal gifUpdateTimerSignal;
QTimer gifUpdateTimer;
bool gifUpdateTimerInitiated = false;
int _generation = 0;
};
} // namespace singletons

View file

@ -3,6 +3,10 @@
#include <QDebug>
#include <QtGlobal>
#include "application.hpp"
#include "util/assertinguithread.hpp"
#include "windowmanager.hpp"
#ifdef Q_OS_WIN32
#define DEFAULT_FONT_FAMILY "Segoe UI"
#define DEFAULT_FONT_SIZE 10
@ -20,86 +24,111 @@ namespace chatterino {
namespace singletons {
FontManager::FontManager()
: currentFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
, currentFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
// , currentFont(this->currentFontFamily.getValue().c_str(), currentFontSize.getValue())
: chatFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
, chatFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
{
qDebug() << "init FontManager";
this->currentFontFamily.connect([this](const std::string &newValue, auto) {
this->incGeneration();
// this->currentFont.setFamily(newValue.c_str());
this->currentFontByScale.clear();
this->chatFontFamily.connect([this](const std::string &, auto) {
util::assertInGuiThread();
if (getApp()->windows) {
getApp()->windows->incGeneration();
}
for (auto &map : this->fontsByType) {
map.clear();
}
this->fontChanged.invoke();
});
this->currentFontSize.connect([this](const int &newValue, auto) {
this->incGeneration();
// this->currentFont.setSize(newValue);
this->currentFontByScale.clear();
this->chatFontSize.connect([this](const int &, auto) {
util::assertInGuiThread();
if (getApp()->windows) {
getApp()->windows->incGeneration();
}
for (auto &map : this->fontsByType) {
map.clear();
}
this->fontChanged.invoke();
});
this->fontsByType.resize(size_t(EndType));
}
QFont &FontManager::getFont(FontManager::Type type, float scale)
QFont FontManager::getFont(FontManager::Type type, float scale)
{
// return this->currentFont.getFont(type);
return this->getCurrentFont(scale).getFont(type);
return this->getOrCreateFontData(type, scale).font;
}
QFontMetrics &FontManager::getFontMetrics(FontManager::Type type, float scale)
QFontMetrics FontManager::getFontMetrics(FontManager::Type type, float scale)
{
// return this->currentFont.getFontMetrics(type);
return this->getCurrentFont(scale).getFontMetrics(type);
return this->getOrCreateFontData(type, scale).metrics;
}
FontManager::FontData &FontManager::Font::getFontData(FontManager::Type type)
FontManager::FontData &FontManager::getOrCreateFontData(Type type, float scale)
{
switch (type) {
case Tiny:
return this->tiny;
case Small:
return this->small;
case MediumSmall:
return this->mediumSmall;
case Medium:
return this->medium;
case MediumBold:
return this->mediumBold;
case MediumItalic:
return this->mediumItalic;
case Large:
return this->large;
case VeryLarge:
return this->veryLarge;
default:
qDebug() << "Unknown font type:" << type << ", defaulting to medium";
return this->medium;
}
}
util::assertInGuiThread();
QFont &FontManager::Font::getFont(Type type)
{
return this->getFontData(type).font;
}
assert(type >= 0 && type < EndType);
QFontMetrics &FontManager::Font::getFontMetrics(Type type)
{
return this->getFontData(type).metrics;
}
auto &map = this->fontsByType[size_t(type)];
// find element
auto it = map.find(scale);
if (it != map.end()) {
// return if found
FontManager::Font &FontManager::getCurrentFont(float scale)
{
for (auto it = this->currentFontByScale.begin(); it != this->currentFontByScale.end(); it++) {
if (it->first == scale) {
return it->second;
}
}
this->currentFontByScale.push_back(
std::make_pair(scale, Font(this->currentFontFamily.getValue().c_str(),
this->currentFontSize.getValue() * scale)));
return this->currentFontByScale.back().second;
// emplace new element
auto result = map.emplace(scale, this->createFontData(type, scale));
assert(result.second);
return result.first->second;
}
FontManager::FontData FontManager::createFontData(Type type, float scale)
{
// check if it's a chat (scale the setting)
if (type >= ChatStart && type <= ChatEnd) {
static std::unordered_map<Type, ChatFontData> sizeScale{
{ChatSmall, {0.6f, false, QFont::Normal}},
{ChatMediumSmall, {0.8f, false, QFont::Normal}},
{ChatMedium, {1, false, QFont::Normal}},
{ChatMediumBold, {1, false, QFont::Medium}},
{ChatMediumItalic, {1, true, QFont::Normal}},
{ChatLarge, {1.2f, false, QFont::Normal}},
{ChatVeryLarge, {1.4f, false, QFont::Normal}},
};
auto data = sizeScale[type];
return FontData(QFont(QString::fromStdString(this->chatFontFamily.getValue()),
int(this->chatFontSize.getValue() * data.scale * scale), data.weight,
data.italic));
}
// normal Ui font (use pt size)
{
#ifdef Q_OS_MAC
constexpr float multiplier = 0.8f;
#else
constexpr float multiplier = 1.f;
#endif
static std::unordered_map<Type, UiFontData> defaultSize{
{Tiny, {8, "Monospace", false, QFont::Normal}},
{UiMedium, {int(12 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
{UiTabs, {int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
};
UiFontData &data = defaultSize[type];
QFont font(data.name, int(data.size * scale), data.weight, data.italic);
return FontData(font);
}
}
} // namespace singletons

View file

@ -3,138 +3,79 @@
#include <QFont>
#include <QFontDatabase>
#include <QFontMetrics>
#include <array>
#include <boost/noncopyable.hpp>
#include <pajlada/settings/setting.hpp>
#include <pajlada/signals/signal.hpp>
#include <unordered_map>
namespace chatterino {
namespace singletons {
class FontManager
class FontManager : boost::noncopyable
{
public:
FontManager();
FontManager(const FontManager &) = delete;
FontManager(FontManager &&) = delete;
~FontManager() = delete;
// font data gets set in createFontData(...)
enum Type : uint8_t {
Tiny,
Small,
MediumSmall,
Medium,
MediumBold,
MediumItalic,
Large,
VeryLarge,
ChatSmall,
ChatMediumSmall,
ChatMedium,
ChatMediumBold,
ChatMediumItalic,
ChatLarge,
ChatVeryLarge,
UiMedium,
UiTabs,
// don't remove this value
EndType,
// make sure to update these values accordingly!
ChatStart = ChatSmall,
ChatEnd = ChatVeryLarge,
};
QFont &getFont(Type type, float scale);
QFontMetrics &getFontMetrics(Type type, float scale);
QFont getFont(Type type, float scale);
QFontMetrics getFontMetrics(Type type, float scale);
int getGeneration() const
{
return this->generation;
}
void incGeneration()
{
this->generation++;
}
pajlada::Settings::Setting<std::string> currentFontFamily;
pajlada::Settings::Setting<int> currentFontSize;
pajlada::Settings::Setting<std::string> chatFontFamily;
pajlada::Settings::Setting<int> chatFontSize;
pajlada::Signals::NoArgSignal fontChanged;
private:
struct FontData {
FontData(QFont &&_font)
FontData(const QFont &_font)
: font(_font)
, metrics(this->font)
, metrics(_font)
{
}
QFont font;
QFontMetrics metrics;
const QFont font;
const QFontMetrics metrics;
};
struct Font {
Font() = delete;
Font(const char *fontFamilyName, int mediumSize)
: tiny(QFont("Monospace", 8))
, small(QFont(fontFamilyName, mediumSize - 4))
, mediumSmall(QFont(fontFamilyName, mediumSize - 2))
, medium(QFont(fontFamilyName, mediumSize))
, mediumBold(QFont(fontFamilyName, mediumSize, QFont::DemiBold))
, mediumItalic(QFont(fontFamilyName, mediumSize, -1, true))
, large(QFont(fontFamilyName, mediumSize))
, veryLarge(QFont(fontFamilyName, mediumSize))
{
tiny.font.setStyleHint(QFont::TypeWriter);
}
void setFamily(const char *newFamily)
{
this->small.font.setFamily(newFamily);
this->mediumSmall.font.setFamily(newFamily);
this->medium.font.setFamily(newFamily);
this->mediumBold.font.setFamily(newFamily);
this->mediumItalic.font.setFamily(newFamily);
this->large.font.setFamily(newFamily);
this->veryLarge.font.setFamily(newFamily);
this->updateMetrics();
}
void setSize(int newMediumSize)
{
this->small.font.setPointSize(newMediumSize - 4);
this->mediumSmall.font.setPointSize(newMediumSize - 2);
this->medium.font.setPointSize(newMediumSize);
this->mediumBold.font.setPointSize(newMediumSize);
this->mediumItalic.font.setPointSize(newMediumSize);
this->large.font.setPointSize(newMediumSize + 2);
this->veryLarge.font.setPointSize(newMediumSize + 4);
this->updateMetrics();
}
void updateMetrics()
{
this->small.metrics = QFontMetrics(this->small.font);
this->mediumSmall.metrics = QFontMetrics(this->mediumSmall.font);
this->medium.metrics = QFontMetrics(this->medium.font);
this->mediumBold.metrics = QFontMetrics(this->mediumBold.font);
this->mediumItalic.metrics = QFontMetrics(this->mediumItalic.font);
this->large.metrics = QFontMetrics(this->large.font);
this->veryLarge.metrics = QFontMetrics(this->veryLarge.font);
}
FontData &getFontData(Type type);
QFont &getFont(Type type);
QFontMetrics &getFontMetrics(Type type);
FontData tiny;
FontData small;
FontData mediumSmall;
FontData medium;
FontData mediumBold;
FontData mediumItalic;
FontData large;
FontData veryLarge;
struct ChatFontData {
float scale;
bool italic;
QFont::Weight weight;
};
Font &getCurrentFont(float scale);
struct UiFontData {
float size;
const char *name;
bool italic;
QFont::Weight weight;
};
// Future plans:
// Could have multiple fonts in here, such as "Menu font", "Application font", "Chat font"
FontData &getOrCreateFontData(Type type, float scale);
FontData createFontData(Type type, float scale);
std::list<std::pair<float, Font>> currentFontByScale;
int generation = 0;
std::vector<std::unordered_map<float, FontData>> fontsByType;
};
} // namespace singletons

View file

@ -31,6 +31,7 @@ namespace ipc = boost::interprocess;
namespace chatterino {
namespace singletons {
// fourtf: don't add this class to the application class
NativeMessagingManager::NativeMessagingManager()
{
qDebug() << "init NativeMessagingManager";
@ -50,6 +51,10 @@ void NativeMessagingManager::registerHost()
{
auto app = getApp();
if (app->paths->isPortable()) {
return;
}
// create manifest
QJsonDocument document;
QJsonObject root_obj;
@ -113,13 +118,14 @@ void NativeMessagingManager::ReceiverThread::run()
while (true) {
try {
char *buf = (char *)malloc(MESSAGE_SIZE);
std::unique_ptr<char> buf(static_cast<char *>(malloc(MESSAGE_SIZE)));
ipc::message_queue::size_type retSize;
unsigned int priority;
messageQueue.receive(buf, MESSAGE_SIZE, retSize, priority);
messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority);
QJsonDocument document = QJsonDocument::fromJson(QByteArray(buf, retSize));
QJsonDocument document =
QJsonDocument::fromJson(QByteArray::fromRawData(buf.get(), retSize));
this->handleMessage(document.object());
} catch (ipc::interprocess_exception &ex) {
@ -143,26 +149,37 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
QString _type = root.value("type").toString();
bool attach = root.value("attach").toBool();
QString name = root.value("name").toString();
QString winId = root.value("winId").toString();
int yOffset = root.value("yOffset").toInt(-1);
if (_type.isNull() || name.isNull() || winId.isNull()) {
#ifdef USEWINSDK
widgets::AttachedWindow::GetArgs args;
args.winId = root.value("winId").toString();
args.yOffset = root.value("yOffset").toInt(-1);
args.width = root.value("size").toObject().value("width").toInt(-1);
args.height = root.value("size").toObject().value("height").toInt(-1);
if (_type.isNull() || args.winId.isNull()) {
qDebug() << "NM type, name or winId missing";
attach = false;
return;
}
#endif
if (_type == "twitch") {
util::postToThread([name, attach, winId, yOffset, app] {
util::postToThread([=] {
if (!name.isEmpty()) {
app->twitch.server->watchingChannel.update(
app->twitch.server->getOrAddChannel(name));
}
if (attach) {
#ifdef USEWINSDK
auto *window =
widgets::AttachedWindow::get(::GetForegroundWindow(), winId, yOffset);
if (args.height != -1) {
auto *window = widgets::AttachedWindow::get(::GetForegroundWindow(), args);
if (!name.isEmpty()) {
window->setChannel(app->twitch.server->getOrAddChannel(name));
window->show();
}
}
// window->show();
#endif
}
});
@ -179,12 +196,15 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
}
#ifdef USEWINSDK
util::postToThread([winId] { widgets::AttachedWindow::detach(winId); });
util::postToThread([winId] {
qDebug() << "NW detach";
widgets::AttachedWindow::detach(winId);
});
#endif
} else {
qDebug() << "NM unknown action " + action;
}
}
} // namespace singletons
} // namespace singletons
} // namespace chatterino

View file

@ -8,6 +8,7 @@ namespace singletons {
class NativeMessagingManager
{
public:
// fourtf: don't add this class to the application class
NativeMessagingManager();
~NativeMessagingManager() = delete;

View file

@ -19,21 +19,21 @@ PathManager::PathManager(int argc, char **argv)
.replace("/", "x");
// Options
bool portable = false;
this->portable = false;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "portable") == 0) {
portable = true;
this->portable = true;
}
}
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/portable")) {
portable = true;
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/this->portable")) {
this->portable = true;
}
// Root path = %APPDATA%/Chatterino or the folder that the executable resides in
QString rootPath;
if (portable) {
if (this->portable) {
rootPath.append(QCoreApplication::applicationDirPath());
} else {
// Get settings path
@ -91,5 +91,10 @@ bool PathManager::createFolder(const QString &folderPath)
return QDir().mkpath(folderPath);
}
bool PathManager::isPortable()
{
return this->portable;
}
} // namespace singletons
} // namespace chatterino

View file

@ -28,6 +28,10 @@ public:
QString appPathHash;
bool createFolder(const QString &folderPath);
bool isPortable();
private:
bool portable;
};
} // namespace singletons

View file

@ -2,6 +2,7 @@
#include "util/emotemap.hpp"
#include <QIcon>
#include <QRegularExpression>
#include <map>

View file

@ -39,8 +39,18 @@ void SettingManager::initialize()
this->timestampFormat.connect([](auto, auto) {
auto app = getApp();
app->windows->layoutVisibleChatWidgets();
app->windows->layoutChannelViews();
});
this->emoteScale.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->timestampFormat.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->alternateMessageBackground.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->seperateMessages.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->collpseMessagesMinLines.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
}
MessageElement::Flags SettingManager::getWordFlags()

View file

@ -40,6 +40,8 @@ public:
BoolSetting hideEmptyInput = {"/appearance/hideEmptyInputBox", false};
BoolSetting showMessageLength = {"/appearance/messages/showMessageLength", false};
BoolSetting seperateMessages = {"/appearance/messages/separateMessages", false};
// BoolSetting collapseLongMessages = {"/appearance/messages/collapseLongMessages", false};
IntSetting collpseMessagesMinLines = {"/appearance/messages/collapseMessagesMinLines", 0};
BoolSetting alternateMessageBackground = {"/appearance/messages/alternateMessageBackground",
false};
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
@ -54,6 +56,8 @@ public:
/// Behaviour
BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", true};
BoolSetting mentionUsersWithAt = {"/behaviour/mentionUsersWithAt", false};
BoolSetting showJoins = {"/behaviour/showJoins", false};
BoolSetting showParts = {"/behaviour/showParts", false};
FloatSetting mouseScrollMultiplier = {"/behaviour/mouseScrollMultiplier", 1.0};
// Auto-completion
@ -75,10 +79,10 @@ public:
BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true};
FloatSetting emoteScale = {"/emotes/scale", 1.f};
// 0 = Smallest size
// 1 = One size above 0 (usually size of 0 * 2)
// 2 = One size above 1 (usually size of 1 * 2)
// etc...
// 0 = No preference
// 1 = 1x
// 2 = 2x
// 3 = 3x
IntSetting preferredEmoteQuality = {"/emotes/preferredEmoteQuality", 0};
/// Links

View file

@ -52,7 +52,6 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
isLight = multiplier > 0;
bool lightWin = isLight;
QColor none(0, 0, 0, 0);
QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
@ -69,7 +68,7 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
#ifdef Q_OS_LINUX
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
#else
this->window.background = lightWin ? "#fff" : "#444";
this->window.background = lightWin ? "#fff" : "#111";
#endif
QColor fg = this->window.text = lightWin ? "#000" : "#eee";
@ -89,27 +88,52 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
/// TABS
if (lightWin) {
this->tabs.regular = {fg, {bg, QColor("#ccc"), bg}};
this->tabs.regular = {QColor("#444"),
{QColor("#fff"), QColor("#fff"), QColor("#fff")},
{QColor("#fff"), QColor("#fff"), QColor("#fff")}};
this->tabs.newMessage = {
fg,
{QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}};
this->tabs.highlighted = {fg, {QColor("#ccc"), QColor("#ccc"), QColor("#bbb")}};
this->tabs.selected = {QColor("#fff"),
{QColor("#777"), QColor("#777"), QColor("#888")}};
} else {
this->tabs.regular = {fg, {bg, QColor("#555"), bg}};
this->tabs.newMessage = {
fg,
{QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
QBrush(blendColors(themeColorNoSat, "#666", 0.7), Qt::FDiagPattern)}};
this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"), QColor("#666")}};
fg, {bg, QColor("#ccc"), bg}, {QColor("#aaa"), QColor("#aaa"), QColor("#aaa")}};
this->tabs.highlighted = {fg,
{bg, QColor("#ccc"), bg},
{QColor("#b60505"), QColor("#b60505"), QColor("#b60505")}};
this->tabs.selected = {QColor("#000"),
{QColor("#999"), QColor("#999"), QColor("#888")}};
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
} else {
this->tabs.regular = {QColor("#aaa"),
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#444"), QColor("#444"), QColor("#444")}};
this->tabs.newMessage = {fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#888"), QColor("#888"), QColor("#888")}};
this->tabs.highlighted = {fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#ee6166"), QColor("#ee6166"), QColor("#ee6166")}};
this->tabs.selected = {QColor("#fff"),
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
}
// scrollbar
this->scrollbars.highlights.highlight = QColor("#ee6166");
this->scrollbars.highlights.subscription = QColor("#C466FF");
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}};
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#666", 0.7),
// Qt::FDiagPattern)}};
// this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"),
// QColor("#666")}};
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
}
@ -119,7 +143,12 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->splits.messageSeperator = isLight ? QColor(127, 127, 127) : QColor(60, 60, 60);
this->splits.background = getColor(0, sat, 1);
this->splits.dropPreview = QColor(0, 148, 255, 0x30);
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0x70);
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
this->splits.dropTargetRect = QColor(0, 148, 255, 0x00);
this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00);
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
// this->splits.border
// this->splits.borderFocused
@ -143,7 +172,10 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->messages.backgrounds.regular = splits.background;
this->messages.backgrounds.alternate = getColor(0, sat, 0.93);
this->messages.backgrounds.highlighted =
blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
blendColors(themeColor, this->messages.backgrounds.regular, 0.6);
this->messages.backgrounds.subscription =
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
// this->messages.backgrounds.resub
// this->messages.backgrounds.whisper
this->messages.disabled = getColor(0, sat, 1, 0.6);
@ -151,7 +183,9 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
// this->messages.seperatorInner =
// Scrollbar
this->scrollbars.background = getColor(0, sat, 0.94);
this->scrollbars.background = QColor(0, 0, 0, 0);
// this->scrollbars.background = splits.background;
// this->scrollbars.background.setAlphaF(qreal(0.2));
this->scrollbars.thumb = getColor(0, sat, 0.80);
this->scrollbars.thumbSelected = getColor(0, sat, 0.7);
@ -163,13 +197,13 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->messages.selection = isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
this->updated.invoke();
}
} // namespace singletons
QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qreal ratio)
{
int r = color1.red() * (1 - ratio) + color2.red() * ratio;
int g = color1.green() * (1 - ratio) + color2.green() * ratio;
int b = color1.blue() * (1 - ratio) + color2.blue() * ratio;
int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
return QColor(r, g, b, 255);
}
@ -177,22 +211,22 @@ QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qre
void ThemeManager::normalizeColor(QColor &color)
{
if (this->isLight) {
if (color.lightnessF() > 0.5f) {
color.setHslF(color.hueF(), color.saturationF(), 0.5f);
if (color.lightnessF() > 0.5) {
color.setHslF(color.hueF(), color.saturationF(), 0.5);
}
if (color.lightnessF() > 0.4f && color.hueF() > 0.1 && color.hueF() < 0.33333) {
if (color.lightnessF() > 0.4 && color.hueF() > 0.1 && color.hueF() < 0.33333) {
color.setHslF(
color.hueF(), color.saturationF(),
color.lightnessF() - sin((color.hueF() - 0.1) / (0.3333 - 0.1) * 3.14159) *
color.saturationF() * 0.2);
}
} else {
if (color.lightnessF() < 0.5f) {
color.setHslF(color.hueF(), color.saturationF(), 0.5f);
if (color.lightnessF() < 0.5) {
color.setHslF(color.hueF(), color.saturationF(), 0.5);
}
if (color.lightnessF() < 0.6f && color.hueF() > 0.54444 && color.hueF() < 0.83333) {
if (color.lightnessF() < 0.6 && color.hueF() > 0.54444 && color.hueF() < 0.83333) {
color.setHslF(
color.hueF(), color.saturationF(),
color.lightnessF() + sin((color.hueF() - 0.54444) / (0.8333 - 0.54444) * 3.14159) *

View file

@ -25,11 +25,16 @@ public:
struct TabColors {
QColor text;
struct Backgrounds {
struct {
QBrush regular;
QBrush hover;
QBrush unfocused;
} backgrounds;
struct {
QColor regular;
QColor hover;
QColor unfocused;
} line;
};
/// WINDOW
@ -43,9 +48,9 @@ public:
/// TABS
struct {
TabColors regular;
TabColors selected;
TabColors highlighted;
TabColors newMessage;
TabColors highlighted;
TabColors selected;
QColor border;
QColor bottomLine;
} tabs;
@ -58,6 +63,10 @@ public:
QColor borderFocused;
QColor dropPreview;
QColor dropPreviewBorder;
QColor dropTargetRect;
QColor dropTargetRectBorder;
QColor resizeHandle;
QColor resizeHandleBackground;
struct {
QColor border;
@ -89,7 +98,7 @@ public:
QColor regular;
QColor alternate;
QColor highlighted;
// QColor resub;
QColor subscription;
// QColor whisper;
} backgrounds;
@ -104,8 +113,10 @@ public:
QColor background;
QColor thumb;
QColor thumbSelected;
// const int highlightsCount = 3;
// QColor highlights[3];
struct {
QColor highlight;
QColor subscription;
} highlights;
} scrollbars;
/// TOOLTIP

View file

@ -7,25 +7,31 @@ namespace chatterino {
namespace singletons {
UpdateManager::UpdateManager()
: currentVersion(CHATTERINO_VERSION)
: currentVersion_(CHATTERINO_VERSION)
{
qDebug() << "init UpdateManager";
}
UpdateManager &UpdateManager::getInstance()
{
// fourtf: don't add this class to the application class
static UpdateManager instance;
return instance;
}
const QString &UpdateManager::getCurrentVersion() const
{
return this->getCurrentVersion();
return currentVersion_;
}
const QString &UpdateManager::getOnlineVersion() const
{
return this->getOnlineVersion();
return onlineVersion_;
}
void UpdateManager::installUpdates()
{
}
void UpdateManager::checkForUpdates()
@ -33,17 +39,38 @@ void UpdateManager::checkForUpdates()
QString url = "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/stable";
util::NetworkRequest req(url);
req.setTimeout(20000);
req.setTimeout(30000);
req.getJSON([this](QJsonObject &object) {
QJsonValue version_val = object.value("version");
if (!version_val.isString()) {
this->setStatus_(Error);
qDebug() << "error updating";
return;
}
this->onlineVersion = version_val.toString();
this->onlineVersionUpdated.invoke();
this->onlineVersion_ = version_val.toString();
if (this->currentVersion_ != this->onlineVersion_) {
this->setStatus_(UpdateAvailable);
} else {
this->setStatus_(NoUpdateAvailable);
}
});
this->setStatus_(Searching);
req.execute();
}
UpdateManager::UpdateStatus UpdateManager::getStatus() const
{
return this->status_;
}
void UpdateManager::setStatus_(UpdateStatus status)
{
if (this->status_ != status) {
this->status_ = status;
this->statusUpdated.invoke(status);
}
}
} // namespace singletons

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