Compare commits

...

7 Commits
1.0.0 ... 1.0.1

Author SHA1 Message Date
ccbd223692 fix desktop icon 2021-12-30 13:03:13 -05:00
c613a1c958 add .desktop file 2021-12-30 11:53:17 -05:00
a8b215b560 fix ebuild link 2021-12-30 09:35:52 -05:00
a0f043d790 fix flatpak build link 2021-12-29 15:03:08 -05:00
113956ccc8 Enhance the dependencies list, and add information to build a .deb (#6) 2021-12-29 15:00:47 -05:00
f25a9bc103 Completely remove Google Analytics library 2021-12-29 10:37:09 -05:00
af76cf59b6 Move todo to wiki and update README
Moved "todo" section to wiki

Reqorded packages section.

Changed swirl overlay URL to my Gitea instance

Added Flatpak and Debian package info

Serve packages over packages.polymc.org, and added SHA256 sums
2021-12-29 10:12:17 -05:00
23 changed files with 57 additions and 1071 deletions

View File

@ -26,11 +26,24 @@ Getting the project to build and run on Linux is easy if you use any modern and
## Build dependencies
* A C++ compiler capable of building C++11 code.
* Qt 5.6+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution, as long as it has a new enough version.
* cmake 3.1 or newer
* zlib (for example, `zlib1g-dev`)
* Java JDK 8 (for example, `openjdk-8-jdk`)
* GL headers (for example, `libgl1-mesa-dev`)
* Qt Development tools 5.6 or newer (`qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5` on Debian-based system)
* cmake 3.1 or newer (`cmake` on Debian-based system)
* zlib (`zlib1g-dev` on Debian-based system)
* Java JDK (`openjdk-17-jdk`on Debian-based system)
* GL headers (`libgl1-mesa-dev` on Debian-based system)
### Building a .deb
You need to install the build dependencies first
```
git clone https://github.com/PolyMC/PolyMC.git
git submodule init && git submodule update
cd packages/debian
./makedeb.sh
```
If everything works correctly, the .deb will be next to the build script, in `PolyMC/packages/debian`
### Building from command line
You need a source folder, a build folder and an install folder.

View File

@ -78,9 +78,6 @@ set(Launcher_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE
# Imgur API Client ID
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
# Google analytics ID
set(Launcher_ANALYTICS_ID "" CACHE STRING "ID you can get from Google analytics")
# MSA Client ID
set(Launcher_MSA_CLIENT_ID "17b47edd-c884-4997-926d-9e7f9a6b4647" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
@ -203,20 +200,23 @@ elseif(Launcher_LAYOUT_REAL STREQUAL "lin-nodeps")
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION ${BUNDLE_DEST_DIR} RENAME ${Launcher_Name})
elseif(Launcher_LAYOUT_REAL STREQUAL "lin-system")
set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary")
set(Launcher_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
set(Launcher_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
set(Launcher_SHARE_DEST_DIR "share/polymc" CACHE STRING "Path to the shard data directory")
set(JARS_DEST_DIR "${Launcher_SHARE_DEST_DIR}/jars")
set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary")
set(Launcher_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
set(Launcher_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
set(Launcher_SHARE_DEST_DIR "share/polymc" CACHE STRING "Path to the shared data directory")
set(JARS_DEST_DIR "${Launcher_SHARE_DEST_DIR}/jars")
set(Launcher_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
set(BINARY_DEST_DIR ${Launcher_BINARY_DEST_DIR})
set(LIBRARY_DEST_DIR ${Launcher_LIBRARY_DEST_DIR})
set(BINARY_DEST_DIR ${Launcher_BINARY_DEST_DIR})
set(LIBRARY_DEST_DIR ${Launcher_LIBRARY_DEST_DIR})
MESSAGE(STATUS "Compiling for linux system with ${Launcher_SHARE_DEST_DIR} and LAUNCHER_LINUX_DATADIR")
SET(Launcher_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DLAUNCHER_LINUX_DATADIR")
MESSAGE(STATUS "Compiling for linux system with ${Launcher_SHARE_DEST_DIR} and LAUNCHER_LINUX_DATADIR")
SET(Launcher_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DLAUNCHER_LINUX_DATADIR")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
install(FILES ${Launcher_Desktop} DESTINATION ${Launcher_DESKTOP_DEST_DIR})
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
elseif(Launcher_LAYOUT_REAL STREQUAL "win-bundle")
set(BINARY_DEST_DIR ".")
@ -250,7 +250,6 @@ set(NBT_NAME Launcher_nbt++)
set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
add_subdirectory(libraries/libnbtplusplus)
add_subdirectory(libraries/ganalytics) # google analytics library
add_subdirectory(libraries/systeminfo) # system information library
add_subdirectory(libraries/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
@ -260,7 +259,7 @@ add_subdirectory(libraries/quazip) # zip manipulation library
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # google analytics library
add_subdirectory(libraries/classparser) # class parser library
add_subdirectory(libraries/optional-bare)
add_subdirectory(libraries/tomlc99) # toml parser
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much

View File

@ -9,28 +9,17 @@ PolyMC is a custom launcher for Minecraft that focuses on predictability, long t
This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. The PolyMC community felt that the maintainer was not acting in the spirit of Free Software so this fork was made. Read "[Why was this fork made?](https://github.com/PolyMC/PolyMC/wiki/FAQ)" on the wiki for more details.
## todo
- [ ] Get a permanent name + icon.
- [ ] Style the logo for different icon styles.
- [ ] Packaging for Linux--Any help packaging for your favorite distro is appreciated!
- [ ] Packaging for MacOS/Windows
- [ ] Stop relying on MultiMC-Hosted metadata services
- [ ] Remove references to MultiMC
- [ ] Change up packaging, remove the install script junk
- [ ] AppImage, Flatpak, .deb, ebuild, and AUR packages
- [ ] Meson
- [x] Long-term solution for the MSA client ID issue
- [x] Figure out a way to switch to GPL.
## Packages
Experimental packages are available for Linux (non-portable) and Windows (portable). Both versions are confirmed to work but the Windows version needs more testing--please volunteer if you use Windows!
Several source build packages are available, along with experimental pre-built generic packages.
- An [AUR package](https://aur.archlinux.org/packages/polymc-git/) is also available.
- A Gentoo ebuild is available in the [swirl](https://github.com/binex-dsk/ebuilds) overlay, available as `games-action/polymc`. Check the README for instructions on how to add the overlay.
- A full list of generic, pre-built packages is available [here](https://jenkins.polymc.org/job/PolyMC/lastSuccessfulBuild/artifact/).
- An [AUR package](https://aur.archlinux.org/packages/polymc-git/) is available.
- A Gentoo ebuild is available in the [swirl](https://git.swurl.xyz/swirl/ebuilds) overlay, named `games-action/polymc`. Check the README for instructions on how to add the overlay.
- A Flatpak is available. Instructions on building it can be found in [packages/flatpak/BUILDING.md](packages/flatpak/BUILDING.md).
- Generic, prebuilt packages (archived by version) can be found [here](https://packages.polymc.org/) ([latest](https://packages.polymc.org/latest))
- Last build status: https://jenkins.polymc.org/job/PolyMC/lastBuild/
- [Linux (AMD64) System](https://jenkins.polymc.org/job/PolyMC/lastSuccessfulBuild/artifact/lin64-system.tar.zst) - this is a generic system package intended to be used as a base for making distro-specific packages.
- [Windows (32-bit)](https://jenkins.polymc.org/job/PolyMC/lastSuccessfulBuild/artifact/win32.zip) - this is a portable package, you can extract it anywhere and run it. This package needs testing.
- [Linux (AMD64) System](https://packages.polymc.org/latest/lin64-system/lin64-system.tar.zst) ([SHA256](https://packages.polymc.org/latest/lin64-system/lin64-system.tar.zst.sha256)) - this is a generic system package intended to be used as a base for making distro-specific packages.
- [Windows (32-bit)](https://packages.polymc.org/latest/win32/win32.zip) ([SHA256](https://packages.polymc.org/latest/win32/win32.zip.sha256)) - this is a portable package, you can extract it anywhere and run it. This package needs testing.
- [Debian (AMD64)](https://packages.polymc.org/latest/deb/polymc-amd64.deb) ([SHA256](https://packages.polymc.org/latest/deb/polymc-amd64.deb.sha256)) - this is intended to be installed with `dpkg -i`. Alternatively, you may build the `.deb` yourself, by going to `packages/debian` and running `./makedeb.sh`.
- MacOS currently does not have any packages. We are still working on setting up MacOS packaging.
## Development

View File

@ -24,7 +24,6 @@ Config::Config()
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
ANALYTICS_ID = "@Launcher_ANALYTICS_ID@";
NOTIFICATION_URL = "@Launcher_NOTIFICATION_URL@";
FULL_VERSION_STR = "@Launcher_VERSION_MAJOR@.@Launcher_VERSION_MINOR@.@Launcher_VERSION_BUILD@";

View File

@ -46,9 +46,6 @@ public:
QString USER_AGENT_UNCACHED;
/// Google analytics ID
QString ANALYTICS_ID;
/// URL for notifications
QString NOTIFICATION_URL;
@ -79,7 +76,7 @@ public:
* Client ID you can get from Imgur when you register an application
*/
QString IMGUR_CLIENT_ID;
/**
* Client ID you can get from Microsoft Identity Platform when you register an application
*/
@ -115,3 +112,4 @@ public:
};
extern const Config BuildConfig;

View File

@ -26,7 +26,6 @@
#include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h"
#include "ui/setupwizard/AnalyticsWizardPage.h"
#include "ui/dialogs/CustomMessageBox.h"
@ -73,7 +72,6 @@
#include <DesktopServices.h>
#include <LocalPeer.h>
#include <ganalytics.h>
#include <sys.h>
@ -719,14 +717,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// paste.ee API key
m_settings->registerSetting("PasteEEAPIKey", "multimc");
if(!BuildConfig.ANALYTICS_ID.isEmpty())
{
// Analytics
m_settings->registerSetting("Analytics", true);
m_settings->registerSetting("AnalyticsSeen", 0);
m_settings->registerSetting("AnalyticsClientID", QString());
}
// Init page provider
{
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
@ -908,46 +898,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Application theme set.";
}
// Initialize analytics
[this]()
{
const int analyticsVersion = 2;
if(BuildConfig.ANALYTICS_ID.isEmpty())
{
return;
}
auto analyticsSetting = m_settings->getSetting("Analytics");
connect(analyticsSetting.get(), &Setting::SettingChanged, this, &Application::analyticsSettingChanged);
QString clientID = m_settings->get("AnalyticsClientID").toString();
if(clientID.isEmpty())
{
clientID = QUuid::createUuid().toString();
clientID.remove(QLatin1Char('{'));
clientID.remove(QLatin1Char('}'));
m_settings->set("AnalyticsClientID", clientID);
}
m_analytics = new GAnalytics(BuildConfig.ANALYTICS_ID, clientID, analyticsVersion, this);
m_analytics->setLogLevel(GAnalytics::Debug);
m_analytics->setAnonymizeIPs(true);
// FIXME: the ganalytics library has no idea about our fancy shared pointers...
m_analytics->setNetworkAccessManager(network().get());
if(m_settings->get("AnalyticsSeen").toInt() < m_analytics->version())
{
qDebug() << "Analytics info not seen by user yet (or old version).";
return;
}
if(!m_settings->get("Analytics").toBool())
{
qDebug() << "Analytics disabled by user.";
return;
}
m_analytics->enable();
qDebug() << "<> Initialized analytics with tid" << BuildConfig.ANALYTICS_ID;
}();
if(createSetupWizard())
{
return;
@ -974,29 +924,13 @@ bool Application::createSetupWizard()
}
return false;
}();
bool analyticsRequired = [&]()
{
if(BuildConfig.ANALYTICS_ID.isEmpty())
{
return false;
}
if (!settings()->get("Analytics").toBool())
{
return false;
}
if (settings()->get("AnalyticsSeen").toInt() < analytics()->version())
{
return true;
}
return false;
}();
bool languageRequired = [&]()
{
if (settings()->get("Language").toString().isEmpty())
return true;
return false;
}();
bool wizardRequired = javaRequired || analyticsRequired || languageRequired;
bool wizardRequired = javaRequired || languageRequired;
if(wizardRequired)
{
@ -1009,10 +943,6 @@ bool Application::createSetupWizard()
{
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
}
if(analyticsRequired)
{
m_setupWizard->addPage(new AnalyticsWizardPage(m_setupWizard));
}
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show();
return true;
@ -1169,22 +1099,6 @@ void Application::messageReceived(const QByteArray& message)
}
}
void Application::analyticsSettingChanged(const Setting&, QVariant value)
{
if(!m_analytics)
return;
bool enabled = value.toBool();
if(enabled)
{
qDebug() << "Analytics enabled by user.";
}
else
{
qDebug() << "Analytics disabled by user.";
}
m_analytics->enable(enabled);
}
std::shared_ptr<TranslationsModel> Application::translations()
{
return m_translations;
@ -1454,60 +1368,6 @@ MainWindow* Application::showMainWindow(bool minimized)
connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);
m_openWindows++;
}
// FIXME: move this somewhere else...
if(m_analytics)
{
auto windowSize = m_mainWindow->size();
auto sizeString = QString("%1x%2").arg(windowSize.width()).arg(windowSize.height());
qDebug() << "Viewport size" << sizeString;
m_analytics->setViewportSize(sizeString);
/*
* cm1 = java min heap [MB]
* cm2 = java max heap [MB]
* cm3 = system RAM [MB]
*
* cd1 = java version
* cd2 = java architecture
* cd3 = system architecture
* cd4 = CPU architecture
*/
QVariantMap customValues;
int min = m_settings->get("MinMemAlloc").toInt();
int max = m_settings->get("MaxMemAlloc").toInt();
if(min < max)
{
customValues["cm1"] = min;
customValues["cm2"] = max;
}
else
{
customValues["cm1"] = max;
customValues["cm2"] = min;
}
constexpr uint64_t Mega = 1024ull * 1024ull;
int ramSize = int(Sys::getSystemRam() / Mega);
qDebug() << "RAM size is" << ramSize << "MB";
customValues["cm3"] = ramSize;
customValues["cd1"] = m_settings->get("JavaVersion");
customValues["cd2"] = m_settings->get("JavaArchitecture");
customValues["cd3"] = Sys::isSystem64bit() ? "64":"32";
customValues["cd4"] = Sys::isCPU64bit() ? "64":"32";
auto kernelInfo = Sys::getKernelInfo();
customValues["cd5"] = kernelInfo.kernelName;
customValues["cd6"] = kernelInfo.kernelVersion;
auto distInfo = Sys::getDistributionInfo();
if(!distInfo.distributionName.isEmpty())
{
customValues["cd7"] = distInfo.distributionName;
}
if(!distInfo.distributionVersion.isEmpty())
{
customValues["cd8"] = distInfo.distributionVersion;
}
m_analytics->sendScreenView("Main Window", customValues);
}
return m_mainWindow;
}

View File

@ -33,7 +33,6 @@ class BaseDetachedToolFactory;
class TranslationsModel;
class ITheme;
class MCEditTool;
class GAnalytics;
namespace Meta {
class Index;
@ -60,10 +59,6 @@ public:
Application(int &argc, char **argv);
virtual ~Application();
GAnalytics *analytics() const {
return m_analytics;
}
std::shared_ptr<SettingsObject> settings() const {
return m_settings;
}
@ -161,7 +156,6 @@ private slots:
void messageReceived(const QByteArray & message);
void controllerSucceeded();
void controllerFailed(const QString & error);
void analyticsSettingChanged(const Setting &setting, QVariant value);
void setupWizardFinished(int status);
private:
@ -226,7 +220,6 @@ private:
// peer launcher instance connector - used to implement single instance launcher and signalling
LocalPeer * m_peerInstance = nullptr;
GAnalytics * m_analytics = nullptr;
SetupWizard * m_setupWizard = nullptr;
public:
QString m_instanceIdToLaunch;
@ -236,3 +229,4 @@ public:
QUrl m_zipToImport;
std::unique_ptr<QFile> logFile;
};

View File

@ -627,8 +627,6 @@ SET(LAUNCHER_SOURCES
# GUI - setup wizard
ui/setupwizard/SetupWizard.h
ui/setupwizard/SetupWizard.cpp
ui/setupwizard/AnalyticsWizardPage.cpp
ui/setupwizard/AnalyticsWizardPage.h
ui/setupwizard/BaseWizardPage.h
ui/setupwizard/JavaWizardPage.cpp
ui/setupwizard/JavaWizardPage.h
@ -932,7 +930,6 @@ target_link_libraries(Launcher_logic
hoedown
Launcher_rainbow
LocalPeer
ganalytics
)
target_link_libraries(Launcher_logic)

View File

@ -73,11 +73,6 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
{
ui->updateSettingsBox->setHidden(true);
}
// Analytics
if(BuildConfig.ANALYTICS_ID.isEmpty())
{
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->analyticsTab));
}
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
@ -321,12 +316,6 @@ void LauncherPage::applySettings()
s->set("InstSortMode", "Name");
break;
}
// Analytics
if(!BuildConfig.ANALYTICS_ID.isEmpty())
{
s->set("Analytics", ui->analyticsCheck->isChecked());
}
}
void LauncherPage::loadSettings()
{
@ -422,12 +411,6 @@ void LauncherPage::loadSettings()
{
ui->sortByNameBtn->setChecked(true);
}
// Analytics
if(!BuildConfig.ANALYTICS_ID.isEmpty())
{
ui->analyticsCheck->setChecked(s->get("Analytics").toBool());
}
}
void LauncherPage::refreshFontPreview()

View File

@ -485,69 +485,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="analyticsTab">
<attribute name="title">
<string>Analytics</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QGroupBox" name="consoleSettingsBox_2">
<property name="title">
<string>Analytics Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="analyticsCheck">
<property name="text">
<string>Send anonymous usage statistics?</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;
&lt;body&gt;
&lt;p&gt;The launcher sends anonymous usage statistics on every start of the application.&lt;/p&gt;&lt;p&gt;The following data is collected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Launcher version.&lt;/li&gt;
&lt;li&gt;Operating system name, version and architecture.&lt;/li&gt;
&lt;li&gt;CPU architecture (kernel architecture on linux).&lt;/li&gt;
&lt;li&gt;Size of system memory.&lt;/li&gt;
&lt;li&gt;Java version, architecture and memory settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@ -1,63 +0,0 @@
#include "AnalyticsWizardPage.h"
#include <Application.h>
#include <QVBoxLayout>
#include <QTextBrowser>
#include <QCheckBox>
#include <ganalytics.h>
#include <BuildConfig.h>
AnalyticsWizardPage::AnalyticsWizardPage(QWidget *parent)
: BaseWizardPage(parent)
{
setObjectName(QStringLiteral("analyticsPage"));
verticalLayout_3 = new QVBoxLayout(this);
verticalLayout_3->setObjectName(QStringLiteral("verticalLayout_3"));
textBrowser = new QTextBrowser(this);
textBrowser->setObjectName(QStringLiteral("textBrowser"));
textBrowser->setAcceptRichText(false);
textBrowser->setOpenExternalLinks(true);
verticalLayout_3->addWidget(textBrowser);
checkBox = new QCheckBox(this);
checkBox->setObjectName(QStringLiteral("checkBox"));
checkBox->setChecked(true);
verticalLayout_3->addWidget(checkBox);
retranslate();
}
AnalyticsWizardPage::~AnalyticsWizardPage()
{
}
bool AnalyticsWizardPage::validatePage()
{
auto settings = APPLICATION->settings();
auto analytics = APPLICATION->analytics();
auto status = checkBox->isChecked();
settings->set("AnalyticsSeen", analytics->version());
settings->set("Analytics", status);
return true;
}
void AnalyticsWizardPage::retranslate()
{
setTitle(tr("Analytics"));
setSubTitle(tr("We track some anonymous statistics about users."));
textBrowser->setHtml(tr(
"<html><body>"
"<p>The launcher sends anonymous usage statistics on every start of the application. This helps us decide what platforms and issues to focus on.</p>"
"<p>The data is processed by Google Analytics, see their <a href=\"https://support.google.com/analytics/answer/6004245?hl=en\">article on the "
"matter</a>.</p>"
"<p>The following data is collected:</p>"
"<ul><li>A random unique ID of the installation.<br />It is stored in the application settings file.</li>"
"<li>Anonymized (partial) IP address.</li>"
"<li>Launcher version.</li>"
"<li>Operating system name, version and architecture.</li>"
"<li>CPU architecture (kernel architecture on linux).</li>"
"<li>Size of system memory.</li>"
"<li>Java version, architecture and memory settings.</li></ul>"
"<p>If we change the tracked information, you will see this page again.</p></body></html>"));
checkBox->setText(tr("Enable Analytics"));
}

View File

@ -1,25 +0,0 @@
#pragma once
#include "BaseWizardPage.h"
class QVBoxLayout;
class QTextBrowser;
class QCheckBox;
class AnalyticsWizardPage : public BaseWizardPage
{
Q_OBJECT
public:
explicit AnalyticsWizardPage(QWidget *parent = Q_NULLPTR);
virtual ~AnalyticsWizardPage();
bool validatePage() override;
protected:
void retranslate() override;
private:
QVBoxLayout *verticalLayout_3 = nullptr;
QTextBrowser *textBrowser = nullptr;
QCheckBox *checkBox = nullptr;
};

View File

@ -2,12 +2,10 @@
#include "LanguageWizardPage.h"
#include "JavaWizardPage.h"
#include "AnalyticsWizardPage.h"
#include "translations/TranslationsModel.h"
#include <Application.h>
#include <FileSystem.h>
#include <ganalytics.h>
#include <QAbstractButton>
#include <BuildConfig.h>

View File

@ -9,13 +9,6 @@ This library has served as a base for some (much more full-featured and advanced
Copyright belongs to Petr Mrázek, unless explicitly stated otherwise in the source files. Available under the Apache 2.0 license.
## ganalytics
A Google Analytics library for Qt.
BSD licensed, derived from [qt-google-analytics](https://github.com/HSAnet/qt-google-analytics).
Modifications include better handling of IP anonymization (can be enabled) and general improvements of the API (application handles persistence and ID generation instead of the library).
## hoedown
Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté.

View File

@ -1,17 +0,0 @@
project(ganalytics)
find_package(Qt5Core)
find_package(Qt5Gui)
find_package(Qt5Network)
set(ganalytics_SOURCES
src/ganalytics.cpp
src/ganalytics_worker.cpp
src/ganalytics_worker.h
include/ganalytics.h
)
add_library(ganalytics STATIC ${ganalytics_SOURCES})
target_link_libraries(ganalytics Qt5::Core Qt5::Gui Qt5::Network)
target_include_directories(ganalytics PUBLIC include)
target_link_libraries(ganalytics systeminfo)

View File

@ -1,24 +0,0 @@
Copyright (c) 2014-2015, University of Applied Sciences Augsburg
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 University of Applied Sciences Augsburg 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 OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
OODS 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
UT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,34 +0,0 @@
qt-google-analytics
================
Qt5 classes for providing google analytics usage in a Qt/QML application.
## Building
Include ```qt-google-analytics.pri``` in your .pro file.
## Using
Please make sure you have set your application information using ```QApplication::setApplicationName``` and ```QApplication::setApplicationVersion```.
### In C++:
```
GAnalytics tracker("UA-my-id");
tracker.sendScreenView("Main Screen");
```
### In QtQuick:
Register the class on the C++ side using ```qmlRegisterType<GAnalytics>("analytics", 0, 1, "Tracker");```
```
Tracker {
id: tracker
trackingID: "UA-my-id"
}
[...]
tracker.sendScreenView("Main Screen")
```
There is also an example application in the examples folder.
## License
Copyright (c) 2014-2016, University of Applied Sciences Augsburg.
All rights reserved. Distributed under the terms and conditions of the BSD License. See separate LICENSE.txt.

View File

@ -1,67 +0,0 @@
#pragma once
#include <QObject>
#include <QVariantMap>
class QNetworkAccessManager;
class GAnalyticsWorker;
class GAnalytics : public QObject
{
Q_OBJECT
Q_ENUMS(LogLevel)
public:
explicit GAnalytics(const QString &trackingID, const QString &clientID, const int version, QObject *parent = 0);
~GAnalytics();
public:
enum LogLevel
{
Debug,
Info,
Error
};
int version();
void setLogLevel(LogLevel logLevel);
LogLevel logLevel() const;
// Getter and Setters
void setViewportSize(const QString &viewportSize);
QString viewportSize() const;
void setLanguage(const QString &language);
QString language() const;
void setAnonymizeIPs(bool anonymize);
bool anonymizeIPs();
void setSendInterval(int milliseconds);
int sendInterval() const;
void enable(bool state = true);
bool isEnabled();
/// Get or set the network access manager. If none is set, the class creates its own on the first request
void setNetworkAccessManager(QNetworkAccessManager *networkAccessManager);
QNetworkAccessManager *networkAccessManager() const;
public slots:
void sendScreenView(const QString &screenName, const QVariantMap &customValues = QVariantMap());
void sendEvent(const QString &category, const QString &action, const QString &label = QString(), const QVariant &value = QVariant(),
const QVariantMap &customValues = QVariantMap());
void sendException(const QString &exceptionDescription, bool exceptionFatal = true, const QVariantMap &customValues = QVariantMap());
void startSession();
void endSession();
private:
GAnalyticsWorker *d;
friend QDataStream &operator<<(QDataStream &outStream, const GAnalytics &analytics);
friend QDataStream &operator>>(QDataStream &inStream, GAnalytics &analytics);
};
QDataStream &operator<<(QDataStream &outStream, const GAnalytics &analytics);
QDataStream &operator>>(QDataStream &inStream, GAnalytics &analytics);

View File

@ -1,237 +0,0 @@
#include "ganalytics.h"
#include "ganalytics_worker.h"
#include "sys.h"
#include <QDataStream>
#include <QDebug>
#include <QLocale>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QQueue>
#include <QSettings>
#include <QTimer>
#include <QUrlQuery>
#include <QUuid>
GAnalytics::GAnalytics(const QString &trackingID, const QString &clientID, const int version, QObject *parent) : QObject(parent)
{
d = new GAnalyticsWorker(this);
d->m_trackingID = trackingID;
d->m_clientID = clientID;
d->m_version = version;
}
/**
* Destructor of class GAnalytics.
*/
GAnalytics::~GAnalytics()
{
delete d;
}
void GAnalytics::setLogLevel(GAnalytics::LogLevel logLevel)
{
d->m_logLevel = logLevel;
}
GAnalytics::LogLevel GAnalytics::logLevel() const
{
return d->m_logLevel;
}
// SETTER and GETTER
void GAnalytics::setViewportSize(const QString &viewportSize)
{
d->m_viewportSize = viewportSize;
}
QString GAnalytics::viewportSize() const
{
return d->m_viewportSize;
}
void GAnalytics::setLanguage(const QString &language)
{
d->m_language = language;
}
QString GAnalytics::language() const
{
return d->m_language;
}
void GAnalytics::setAnonymizeIPs(bool anonymize)
{
d->m_anonymizeIPs = anonymize;
}
bool GAnalytics::anonymizeIPs()
{
return d->m_anonymizeIPs;
}
void GAnalytics::setSendInterval(int milliseconds)
{
d->m_timer.setInterval(milliseconds);
}
int GAnalytics::sendInterval() const
{
return (d->m_timer.interval());
}
bool GAnalytics::isEnabled()
{
return d->m_isEnabled;
}
void GAnalytics::enable(bool state)
{
d->enable(state);
}
int GAnalytics::version()
{
return d->m_version;
}
void GAnalytics::setNetworkAccessManager(QNetworkAccessManager *networkAccessManager)
{
if (d->networkManager != networkAccessManager)
{
// Delete the old network manager if it was our child
if (d->networkManager && d->networkManager->parent() == this)
{
d->networkManager->deleteLater();
}
d->networkManager = networkAccessManager;
}
}
QNetworkAccessManager *GAnalytics::networkAccessManager() const
{
return d->networkManager;
}
static void appendCustomValues(QUrlQuery &query, const QVariantMap &customValues)
{
for (QVariantMap::const_iterator iter = customValues.begin(); iter != customValues.end(); ++iter)
{
query.addQueryItem(iter.key(), iter.value().toString());
}
}
/**
* Sent screen view is called when the user changed the applications view.
* These action of the user should be noticed and reported. Therefore
* a QUrlQuery is build in this method. It holts all the parameter for
* a http POST. The UrlQuery will be stored in a message Queue.
*/
void GAnalytics::sendScreenView(const QString &screenName, const QVariantMap &customValues)
{
d->logMessage(Info, QString("ScreenView: %1").arg(screenName));
QUrlQuery query = d->buildStandardPostQuery("screenview");
query.addQueryItem("cd", screenName);
query.addQueryItem("an", d->m_appName);
query.addQueryItem("av", d->m_appVersion);
appendCustomValues(query, customValues);
d->enqueQueryWithCurrentTime(query);
}
/**
* This method is called whenever a button was pressed in the application.
* A query for a POST message will be created to report this event. The
* created query will be stored in a message queue.
*/
void GAnalytics::sendEvent(const QString &category, const QString &action, const QString &label, const QVariant &value, const QVariantMap &customValues)
{
QUrlQuery query = d->buildStandardPostQuery("event");
query.addQueryItem("an", d->m_appName);
query.addQueryItem("av", d->m_appVersion);
query.addQueryItem("ec", category);
query.addQueryItem("ea", action);
if (!label.isEmpty())
query.addQueryItem("el", label);
if (value.isValid())
query.addQueryItem("ev", value.toString());
appendCustomValues(query, customValues);
d->enqueQueryWithCurrentTime(query);
}
/**
* Method is called after an exception was raised. It builds a
* query for a POST message. These query will be stored in a
* message queue.
*/
void GAnalytics::sendException(const QString &exceptionDescription, bool exceptionFatal, const QVariantMap &customValues)
{
QUrlQuery query = d->buildStandardPostQuery("exception");
query.addQueryItem("an", d->m_appName);
query.addQueryItem("av", d->m_appVersion);
query.addQueryItem("exd", exceptionDescription);
if (exceptionFatal)
{
query.addQueryItem("exf", "1");
}
else
{
query.addQueryItem("exf", "0");
}
appendCustomValues(query, customValues);
d->enqueQueryWithCurrentTime(query);
}
/**
* Session starts. This event will be sent by a POST message.
* Query is setup in this method and stored in the message
* queue.
*/
void GAnalytics::startSession()
{
QVariantMap customValues;
customValues.insert("sc", "start");
sendEvent("Session", "Start", QString(), QVariant(), customValues);
}
/**
* Session ends. This event will be sent by a POST message.
* Query is setup in this method and stored in the message
* queue.
*/
void GAnalytics::endSession()
{
QVariantMap customValues;
customValues.insert("sc", "end");
sendEvent("Session", "End", QString(), QVariant(), customValues);
}
/**
* Qut stream to persist class GAnalytics.
*/
QDataStream &operator<<(QDataStream &outStream, const GAnalytics &analytics)
{
outStream << analytics.d->persistMessageQueue();
return outStream;
}
/**
* In stream to read GAnalytics from file.
*/
QDataStream &operator>>(QDataStream &inStream, GAnalytics &analytics)
{
QList<QString> dataList;
inStream >> dataList;
analytics.d->readMessagesFromFile(dataList);
return inStream;
}

View File

@ -1,254 +0,0 @@
#include "ganalytics.h"
#include "ganalytics_worker.h"
#include "sys.h"
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QGuiApplication>
#include <QScreen>
const QLatin1String GAnalyticsWorker::dateTimeFormat("yyyy,MM,dd-hh:mm::ss:zzz");
GAnalyticsWorker::GAnalyticsWorker(GAnalytics *parent)
: QObject(parent), q(parent), m_logLevel(GAnalytics::Error)
{
m_appName = QCoreApplication::instance()->applicationName();
m_appVersion = QCoreApplication::instance()->applicationVersion();
m_request.setUrl(QUrl("https://www.google-analytics.com/collect"));
m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
m_request.setHeader(QNetworkRequest::UserAgentHeader, getUserAgent());
m_language = QLocale::system().name().toLower().replace("_", "-");
m_screenResolution = getScreenResolution();
m_timer.setInterval(m_timerInterval);
connect(&m_timer, &QTimer::timeout, this, &GAnalyticsWorker::postMessage);
}
void GAnalyticsWorker::enable(bool state)
{
// state change to the same is not valid.
if(m_isEnabled == state)
{
return;
}
m_isEnabled = state;
if(m_isEnabled)
{
// enable -> start doing things :)
m_timer.start();
}
else
{
// disable -> stop the timer
m_timer.stop();
}
}
void GAnalyticsWorker::logMessage(GAnalytics::LogLevel level, const QString &message)
{
if (m_logLevel > level)
{
return;
}
qDebug() << "[Analytics]" << message;
}
/**
* Build the POST query. Adds all parameter to the query
* which are used in every POST.
* @param type Type of POST message. The event which is to post.
* @return query Most used parameter in a query for a POST.
*/
QUrlQuery GAnalyticsWorker::buildStandardPostQuery(const QString &type)
{
QUrlQuery query;
query.addQueryItem("v", "1");
query.addQueryItem("tid", m_trackingID);
query.addQueryItem("cid", m_clientID);
if (!m_userID.isEmpty())
{
query.addQueryItem("uid", m_userID);
}
query.addQueryItem("t", type);
query.addQueryItem("ul", m_language);
query.addQueryItem("vp", m_viewportSize);
query.addQueryItem("sr", m_screenResolution);
if(m_anonymizeIPs)
{
query.addQueryItem("aip", "1");
}
return query;
}
/**
* Get primary screen resolution.
* @return A QString like "800x600".
*/
QString GAnalyticsWorker::getScreenResolution()
{
QScreen *screen = QGuiApplication::primaryScreen();
QSize size = screen->size();
return QString("%1x%2").arg(size.width()).arg(size.height());
}
/**
* Try to gain information about the system where this application
* is running. It needs to get the name and version of the operating
* system, the language and screen resolution.
* All this information will be send in POST messages.
* @return agent A QString with all the information formatted for a POST message.
*/
QString GAnalyticsWorker::getUserAgent()
{
return QString("%1/%2").arg(m_appName).arg(m_appVersion);
}
/**
* The message queue contains a list of QueryBuffer object.
* QueryBuffer holds a QUrlQuery object and a QDateTime object.
* These both object are freed from the buffer object and
* inserted as QString objects in a QList.
* @return dataList The list with concartinated queue data.
*/
QList<QString> GAnalyticsWorker::persistMessageQueue()
{
QList<QString> dataList;
foreach (QueryBuffer buffer, m_messageQueue)
{
dataList << buffer.postQuery.toString();
dataList << buffer.time.toString(dateTimeFormat);
}
return dataList;
}
/**
* Reads persistent messages from a file.
* Gets all message data as a QList<QString>.
* Two lines in the list build a QueryBuffer object.
*/
void GAnalyticsWorker::readMessagesFromFile(const QList<QString> &dataList)
{
QListIterator<QString> iter(dataList);
while (iter.hasNext())
{
QString queryString = iter.next();
QString dateString = iter.next();
QUrlQuery query;
query.setQuery(queryString);
QDateTime dateTime = QDateTime::fromString(dateString, dateTimeFormat);
QueryBuffer buffer;
buffer.postQuery = query;
buffer.time = dateTime;
m_messageQueue.enqueue(buffer);
}
}
/**
* Takes a QUrlQuery object and wrapp it together with
* a QTime object into a QueryBuffer struct. These struct
* will be stored in the message queue.
*/
void GAnalyticsWorker::enqueQueryWithCurrentTime(const QUrlQuery &query)
{
QueryBuffer buffer;
buffer.postQuery = query;
buffer.time = QDateTime::currentDateTime();
m_messageQueue.enqueue(buffer);
}
/**
* This function is called by a timer interval.
* The function tries to send a messages from the queue.
* If message was successfully send then this function
* will be called back to send next message.
* If message queue contains more than one message then
* the connection will kept open.
* The message POST is asyncroniously when the server
* answered a signal will be emitted.
*/
void GAnalyticsWorker::postMessage()
{
if (m_messageQueue.isEmpty())
{
// queue empty -> try sending later
m_timer.start();
return;
}
else
{
// queue has messages -> stop timer and start sending
m_timer.stop();
}
QString connection = "close";
if (m_messageQueue.count() > 1)
{
connection = "keep-alive";
}
QueryBuffer buffer = m_messageQueue.head();
QDateTime sendTime = QDateTime::currentDateTime();
qint64 timeDiff = buffer.time.msecsTo(sendTime);
if (timeDiff > fourHours)
{
// too old.
m_messageQueue.dequeue();
emit postMessage();
return;
}
buffer.postQuery.addQueryItem("qt", QString::number(timeDiff));
m_request.setRawHeader("Connection", connection.toUtf8());
m_request.setHeader(QNetworkRequest::ContentLengthHeader, buffer.postQuery.toString().length());
logMessage(GAnalytics::Debug, "Query string = " + buffer.postQuery.toString());
// Create a new network access manager if we don't have one yet
if (networkManager == NULL)
{
networkManager = new QNetworkAccessManager(this);
}
QNetworkReply *reply = networkManager->post(m_request, buffer.postQuery.query(QUrl::EncodeUnicode).toUtf8());
connect(reply, SIGNAL(finished()), this, SLOT(postMessageFinished()));
}
/**
* NetworkAccsessManager has finished to POST a message.
* If POST message was successfully send then the message
* query should be removed from queue.
* SIGNAL "postMessage" will be emitted to send next message
* if there is any.
* If message couldn't be send then next try is when the
* timer emits its signal.
*/
void GAnalyticsWorker::postMessageFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
int httpStausCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (httpStausCode < 200 || httpStausCode > 299)
{
logMessage(GAnalytics::Error, QString("Error posting message: %1").arg(reply->errorString()));
// An error ocurred. Try sending later.
m_timer.start();
return;
}
else
{
logMessage(GAnalytics::Debug, "Message sent");
}
m_messageQueue.dequeue();
postMessage();
reply->deleteLater();
}

View File

@ -1,65 +0,0 @@
#pragma once
#include <QUrlQuery>
#include <QDateTime>
#include <QTimer>
#include <QNetworkRequest>
#include <QQueue>
struct QueryBuffer
{
QUrlQuery postQuery;
QDateTime time;
};
class GAnalyticsWorker : public QObject
{
Q_OBJECT
public:
explicit GAnalyticsWorker(GAnalytics *parent = 0);
GAnalytics *q;
QNetworkAccessManager *networkManager = nullptr;
QQueue<QueryBuffer> m_messageQueue;
QTimer m_timer;
QNetworkRequest m_request;
GAnalytics::LogLevel m_logLevel;
QString m_trackingID;
QString m_clientID;
QString m_userID;
QString m_appName;
QString m_appVersion;
QString m_language;
QString m_screenResolution;
QString m_viewportSize;
bool m_anonymizeIPs = false;
bool m_isEnabled = false;
int m_timerInterval = 30000;
int m_version = 0;
const static int fourHours = 4 * 60 * 60 * 1000;
const static QLatin1String dateTimeFormat;
public:
void logMessage(GAnalytics::LogLevel level, const QString &message);
QUrlQuery buildStandardPostQuery(const QString &type);
QString getScreenResolution();
QString getUserAgent();
QList<QString> persistMessageQueue();
void readMessagesFromFile(const QList<QString> &dataList);
void enqueQueryWithCurrentTime(const QUrlQuery &query);
void setIsSending(bool doSend);
void enable(bool state);
public slots:
void postMessage();
void postMessageFinished();
};

View File

@ -8,6 +8,7 @@ set(Launcher_UserAgent "${Launcher_CommonName}/5.0" PARENT_SCOPE)
set(Launcher_ConfigFile "polymc.cfg" PARENT_SCOPE)
set(Launcher_Git "https://github.com/PolyMC/PolyMC" PARENT_SCOPE)
set(Launcher_Desktop "program_info/polymc.desktop" PARENT_SCOPE)
set(Launcher_Branding_ICNS "program_info/Launcher.icns" PARENT_SCOPE)
set(Launcher_Branding_WindowsRC "program_info/launcher.rc" PARENT_SCOPE)
set(Launcher_Branding_LogoQRC "program_info/logo.qrc" PARENT_SCOPE)

View File

@ -0,0 +1,11 @@
[Desktop Entry]
Version=1.0
Name=PolyMC
Comment=A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
Type=Application
Terminal=false
Exec=polymc
StartupNotify=true
Icon=polymc
Categories=Game;
Keywords=game;minecraft;launcher;