From d4b522b6cb5281df02da54cd9e0f6445770e7ec7 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 10:36:26 +0100 Subject: [PATCH 01/45] Add offline mode UI --- launcher/CMakeLists.txt | 3 + launcher/ui/dialogs/OfflineLoginDialog.cpp | 113 +++++++++++++++++++ launcher/ui/dialogs/OfflineLoginDialog.h | 58 ++++++++++ launcher/ui/dialogs/OfflineLoginDialog.ui | 67 +++++++++++ launcher/ui/pages/global/AccountListPage.cpp | 17 +++ launcher/ui/pages/global/AccountListPage.h | 1 + launcher/ui/pages/global/AccountListPage.ui | 6 + 7 files changed, 265 insertions(+) create mode 100644 launcher/ui/dialogs/OfflineLoginDialog.cpp create mode 100644 launcher/ui/dialogs/OfflineLoginDialog.h create mode 100644 launcher/ui/dialogs/OfflineLoginDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b5c52afa2..0052f0e22 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -769,6 +769,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/LoginDialog.h ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.h + ui/dialogs/OfflineLoginDialog.cpp + ui/dialogs/OfflineLoginDialog.h ui/dialogs/NewComponentDialog.cpp ui/dialogs/NewComponentDialog.h ui/dialogs/NewInstanceDialog.cpp @@ -880,6 +882,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/ExportInstanceDialog.ui ui/dialogs/IconPickerDialog.ui ui/dialogs/MSALoginDialog.ui + ui/dialogs/OfflineLoginDialog.ui ui/dialogs/AboutDialog.ui ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp new file mode 100644 index 000000000..f6ecc4e97 --- /dev/null +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -0,0 +1,113 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "OfflineLoginDialog.h" +#include "ui_OfflineLoginDialog.h" + +#include "minecraft/auth/AccountTask.h" + +#include + +OfflineLoginDialog::OfflineLoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog) +{ + ui->setupUi(this); + ui->progressBar->setVisible(false); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +OfflineLoginDialog::~OfflineLoginDialog() +{ + delete ui; +} + +// Stage 1: User interaction +void OfflineLoginDialog::accept() +{ + setUserInputsEnabled(false); + ui->progressBar->setVisible(true); + + // Setup the login task and start it + m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); + m_loginTask = m_account->login("TODO: create offline mode account flow"); + connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed); + connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded); + connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus); + connect(m_loginTask.get(), &Task::progress, this, &OfflineLoginDialog::onTaskProgress); + m_loginTask->start(); +} + +void OfflineLoginDialog::setUserInputsEnabled(bool enable) +{ + ui->userTextBox->setEnabled(enable); + ui->buttonBox->setEnabled(enable); +} + +// Enable the OK button only when the textbox contains something. +void OfflineLoginDialog::on_userTextBox_textEdited(const QString &newText) +{ + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(!newText.isEmpty()); +} + +void OfflineLoginDialog::onTaskFailed(const QString &reason) +{ + // Set message + auto lines = reason.split('\n'); + QString processed; + for(auto line: lines) { + if(line.size()) { + processed += "" + line + "
"; + } + else { + processed += "
"; + } + } + ui->label->setText(processed); + + // Re-enable user-interaction + setUserInputsEnabled(true); + ui->progressBar->setVisible(false); +} + +void OfflineLoginDialog::onTaskSucceeded() +{ + QDialog::accept(); +} + +void OfflineLoginDialog::onTaskStatus(const QString &status) +{ + ui->label->setText(status); +} + +void OfflineLoginDialog::onTaskProgress(qint64 current, qint64 total) +{ + ui->progressBar->setMaximum(total); + ui->progressBar->setValue(current); +} + +// Public interface +MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg) +{ + OfflineLoginDialog dlg(parent); + dlg.ui->label->setText(msg); + if (dlg.exec() == QDialog::Accepted) + { + return dlg.m_account; + } + return 0; +} diff --git a/launcher/ui/dialogs/OfflineLoginDialog.h b/launcher/ui/dialogs/OfflineLoginDialog.h new file mode 100644 index 000000000..ba0c18232 --- /dev/null +++ b/launcher/ui/dialogs/OfflineLoginDialog.h @@ -0,0 +1,58 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "minecraft/auth/MinecraftAccount.h" +#include "tasks/Task.h" + +namespace Ui +{ +class OfflineLoginDialog; +} + +class OfflineLoginDialog : public QDialog +{ + Q_OBJECT + +public: + ~OfflineLoginDialog(); + + static MinecraftAccountPtr newAccount(QWidget *parent, QString message); + +private: + explicit OfflineLoginDialog(QWidget *parent = 0); + + void setUserInputsEnabled(bool enable); + +protected +slots: + void accept(); + + void onTaskFailed(const QString &reason); + void onTaskSucceeded(); + void onTaskStatus(const QString &status); + void onTaskProgress(qint64 current, qint64 total); + + void on_userTextBox_textEdited(const QString &newText); + +private: + Ui::OfflineLoginDialog *ui; + MinecraftAccountPtr m_account; + Task::Ptr m_loginTask; +}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.ui b/launcher/ui/dialogs/OfflineLoginDialog.ui new file mode 100644 index 000000000..d8964a2e4 --- /dev/null +++ b/launcher/ui/dialogs/OfflineLoginDialog.ui @@ -0,0 +1,67 @@ + + + OfflineLoginDialog + + + + 0 + 0 + 400 + 150 + + + + + 0 + 0 + + + + Add Account + + + + + + Message label placeholder. + + + Qt::RichText + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Username + + + + + + + 69 + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index b8da6c754..b9aa7628b 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -24,6 +24,7 @@ #include "net/NetJob.h" #include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/OfflineLoginDialog.h" #include "ui/dialogs/LoginDialog.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/CustomMessageBox.h" @@ -153,6 +154,22 @@ void AccountListPage::on_actionAddMicrosoft_triggered() } } +void AccountListPage::on_actionAddOffline_triggered() +{ + MinecraftAccountPtr account = OfflineLoginDialog::newAccount( + this, + tr("Please enter your desired username to add your offline account.") + ); + + if (account) + { + m_accounts->addAccount(account); + if (m_accounts->count() == 1) { + m_accounts->setDefaultAccount(account); + } + } +} + void AccountListPage::on_actionRemove_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 1c65e7081..841c3fd2d 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -62,6 +62,7 @@ public: public slots: void on_actionAddMojang_triggered(); void on_actionAddMicrosoft_triggered(); + void on_actionAddOffline_triggered(); void on_actionRemove_triggered(); void on_actionRefresh_triggered(); void on_actionSetDefault_triggered(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index 29738c023..d21a92e23 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -54,6 +54,7 @@ + @@ -103,6 +104,11 @@ Add Microsoft + + + Add Offline + + Refresh From a1ff3b1ee34c302ba52d773816207d30badab1eb Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 14:26:02 +0100 Subject: [PATCH 02/45] Add offline mode support --- launcher/CMakeLists.txt | 4 +++ launcher/LaunchController.cpp | 6 ++++ launcher/minecraft/auth/AccountData.cpp | 10 +++++- launcher/minecraft/auth/AccountData.h | 3 +- launcher/minecraft/auth/AccountList.cpp | 2 +- launcher/minecraft/auth/MinecraftAccount.cpp | 31 +++++++++++++++++++ launcher/minecraft/auth/MinecraftAccount.h | 12 +++++++ launcher/minecraft/auth/flows/Offline.cpp | 17 ++++++++++ launcher/minecraft/auth/flows/Offline.h | 22 +++++++++++++ launcher/minecraft/auth/steps/OfflineStep.cpp | 18 +++++++++++ launcher/minecraft/auth/steps/OfflineStep.h | 20 ++++++++++++ launcher/ui/dialogs/OfflineLoginDialog.cpp | 4 +-- 12 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 launcher/minecraft/auth/flows/Offline.cpp create mode 100644 launcher/minecraft/auth/flows/Offline.h create mode 100644 launcher/minecraft/auth/steps/OfflineStep.cpp create mode 100644 launcher/minecraft/auth/steps/OfflineStep.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0052f0e22..df3614475 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -221,7 +221,11 @@ set(MINECRAFT_SOURCES minecraft/auth/flows/Mojang.h minecraft/auth/flows/MSA.cpp minecraft/auth/flows/MSA.h + minecraft/auth/flows/Offline.cpp + minecraft/auth/flows/Offline.h + minecraft/auth/steps/OfflineStep.cpp + minecraft/auth/steps/OfflineStep.h minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.h minecraft/auth/steps/GetSkinStep.cpp diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 7750be1a2..32fc99cb9 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -116,6 +116,12 @@ void LaunchController::login() { m_session->wants_online = m_online; m_accountToUse->fillSession(m_session); + // Launch immediately in true offline mode + if(m_accountToUse->isOffline()) { + launchInstance(); + return; + } + switch(m_accountToUse->accountState()) { case AccountState::Offline: { m_session->wants_online = false; diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 7526c9517..9b84fe1a3 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -314,6 +314,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { type = AccountType::MSA; } else if (typeS == "Mojang") { type = AccountType::Mojang; + } else if (typeS == "Offline") { + type = AccountType::Offline; } else { qWarning() << "Failed to parse account data: type is not recognized."; return false; @@ -363,6 +365,9 @@ QJsonObject AccountData::saveState() const { tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); } + else if (type == AccountType::Offline) { + output["type"] = "Offline"; + } tokenToJSONV3(output, yggdrasilToken, "ygg"); profileToJSONV3(output, minecraftProfile, "profile"); @@ -371,7 +376,7 @@ QJsonObject AccountData::saveState() const { } QString AccountData::userName() const { - if(type != AccountType::Mojang) { + if(type == AccountType::MSA) { return QString(); } return yggdrasilToken.extra["userName"].toString(); @@ -427,6 +432,9 @@ QString AccountData::accountDisplayString() const { case AccountType::Mojang: { return userName(); } + case AccountType::Offline: { + return userName(); + } case AccountType::MSA: { if(xboxApiToken.extra.contains("gtg")) { return xboxApiToken.extra["gtg"].toString(); diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index abf84e43a..606c1ad11 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -38,7 +38,8 @@ struct MinecraftProfile { enum class AccountType { MSA, - Mojang + Mojang, + Offline }; enum class AccountState { diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index ef8b435d1..04470e1c1 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -302,7 +302,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const } case MigrationColumn: { - if(account->isMSA()) { + if(account->isMSA() || account->isOffline()) { return tr("N/A", "Can Migrate?"); } if (account->canMigrate()) { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index ed9e945ee..6592be0fe 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -30,6 +30,7 @@ #include "flows/MSA.h" #include "flows/Mojang.h" +#include "flows/Offline.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); @@ -68,6 +69,23 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() return account; } +MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) +{ + MinecraftAccountPtr account = new MinecraftAccount(); + account->data.type = AccountType::Offline; + account->data.yggdrasilToken.token = "offline"; + account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; + account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); + account->data.yggdrasilToken.extra["userName"] = username; + account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->data.minecraftEntitlement.ownsMinecraft = true; + account->data.minecraftEntitlement.canPlayMinecraft = true; + account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->data.minecraftProfile.name = username; + account->data.minecraftProfile.validity = Katabasis::Validity::Certain; + return account; +} + QJsonObject MinecraftAccount::saveToJson() const { @@ -111,6 +129,16 @@ shared_qobject_ptr MinecraftAccount::loginMSA() { return m_currentTask; } +shared_qobject_ptr MinecraftAccount::loginOffline() { + Q_ASSERT(m_currentTask.get() == nullptr); + + m_currentTask.reset(new OfflineLogin(&data)); + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + emit activityChanged(true); + return m_currentTask; +} + shared_qobject_ptr MinecraftAccount::refresh() { if(m_currentTask) { return m_currentTask; @@ -119,6 +147,9 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } + if(data.type == AccountType::Offline) { + m_currentTask.reset(new OfflineRefresh(&data)); + } else { m_currentTask.reset(new MojangRefresh(&data)); } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 7ab3c7468..6592f9c07 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -73,6 +73,8 @@ public: /* construction */ static MinecraftAccountPtr createBlankMSA(); + static MinecraftAccountPtr createOffline(const QString &username); + static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json); static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); @@ -89,6 +91,8 @@ public: /* manipulation */ shared_qobject_ptr loginMSA(); + shared_qobject_ptr loginOffline(); + shared_qobject_ptr refresh(); shared_qobject_ptr currentTask(); @@ -128,6 +132,10 @@ public: /* queries */ return data.type == AccountType::MSA; } + bool isOffline() const { + return data.type == AccountType::Offline; + } + bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; } @@ -149,6 +157,10 @@ public: /* queries */ return "msa"; } break; + case AccountType::Offline: { + return "offline"; + } + break; default: { return "unknown"; } diff --git a/launcher/minecraft/auth/flows/Offline.cpp b/launcher/minecraft/auth/flows/Offline.cpp new file mode 100644 index 000000000..fc614a8c7 --- /dev/null +++ b/launcher/minecraft/auth/flows/Offline.cpp @@ -0,0 +1,17 @@ +#include "Offline.h" + +#include "minecraft/auth/steps/OfflineStep.h" + +OfflineRefresh::OfflineRefresh( + AccountData *data, + QObject *parent +) : AuthFlow(data, parent) { + m_steps.append(new OfflineStep(m_data)); +} + +OfflineLogin::OfflineLogin( + AccountData *data, + QObject *parent +) : AuthFlow(data, parent) { + m_steps.append(new OfflineStep(m_data)); +} diff --git a/launcher/minecraft/auth/flows/Offline.h b/launcher/minecraft/auth/flows/Offline.h new file mode 100644 index 000000000..5d1f83a46 --- /dev/null +++ b/launcher/minecraft/auth/flows/Offline.h @@ -0,0 +1,22 @@ +#pragma once +#include "AuthFlow.h" + +class OfflineRefresh : public AuthFlow +{ + Q_OBJECT +public: + explicit OfflineRefresh( + AccountData *data, + QObject *parent = 0 + ); +}; + +class OfflineLogin : public AuthFlow +{ + Q_OBJECT +public: + explicit OfflineLogin( + AccountData *data, + QObject *parent = 0 + ); +}; diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp new file mode 100644 index 000000000..9f1fc2666 --- /dev/null +++ b/launcher/minecraft/auth/steps/OfflineStep.cpp @@ -0,0 +1,18 @@ +#include "OfflineStep.h" + +#include "Application.h" + +OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}; +OfflineStep::~OfflineStep() noexcept = default; + +QString OfflineStep::describe() { + return tr("Creating offline account."); +} + +void OfflineStep::rehydrate() { + // NOOP +} + +void OfflineStep::perform() { + emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account.")); +} diff --git a/launcher/minecraft/auth/steps/OfflineStep.h b/launcher/minecraft/auth/steps/OfflineStep.h new file mode 100644 index 000000000..62addb1f7 --- /dev/null +++ b/launcher/minecraft/auth/steps/OfflineStep.h @@ -0,0 +1,20 @@ + +#pragma once +#include + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +#include + +class OfflineStep : public AuthStep { + Q_OBJECT +public: + explicit OfflineStep(AccountData *data); + virtual ~OfflineStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; +}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index f6ecc4e97..0cc922f83 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -42,8 +42,8 @@ void OfflineLoginDialog::accept() ui->progressBar->setVisible(true); // Setup the login task and start it - m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); - m_loginTask = m_account->login("TODO: create offline mode account flow"); + m_account = MinecraftAccount::createOffline(ui->userTextBox->text()); + m_loginTask = m_account->loginOffline(); connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed); connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded); connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus); From 6ecc8c5496cd1fa121b69f770c0664320fd7dc1d Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 14:57:32 +0100 Subject: [PATCH 03/45] Remove unnecessary license header --- launcher/ui/dialogs/OfflineLoginDialog.cpp | 15 --------------- launcher/ui/dialogs/OfflineLoginDialog.h | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index 0cc922f83..345ed40ac 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -1,18 +1,3 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - #include "OfflineLoginDialog.h" #include "ui_OfflineLoginDialog.h" diff --git a/launcher/ui/dialogs/OfflineLoginDialog.h b/launcher/ui/dialogs/OfflineLoginDialog.h index ba0c18232..5e6083792 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.h +++ b/launcher/ui/dialogs/OfflineLoginDialog.h @@ -1,18 +1,3 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - #pragma once #include From 46a3b4de6ebb625b958a69aba85316171d3fa168 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 18:41:33 +0100 Subject: [PATCH 04/45] Remove unnecessary semicolon --- launcher/minecraft/auth/steps/OfflineStep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp index 9f1fc2666..dc092bfd4 100644 --- a/launcher/minecraft/auth/steps/OfflineStep.cpp +++ b/launcher/minecraft/auth/steps/OfflineStep.cpp @@ -2,7 +2,7 @@ #include "Application.h" -OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}; +OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {} OfflineStep::~OfflineStep() noexcept = default; QString OfflineStep::describe() { From 395e2655648dbb80d089077e6a6b2530f4876c63 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Fri, 14 Jan 2022 00:01:05 +0100 Subject: [PATCH 05/45] Add offline mode disclaimer --- launcher/ui/dialogs/OfflineLoginDialog.ui | 4 ++-- launcher/ui/pages/global/AccountListPage.cpp | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/launcher/ui/dialogs/OfflineLoginDialog.ui b/launcher/ui/dialogs/OfflineLoginDialog.ui index d8964a2e4..4577d361b 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.ui +++ b/launcher/ui/dialogs/OfflineLoginDialog.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 150 + 500 + 250 diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index b9aa7628b..1c27d5b73 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -158,7 +158,13 @@ void AccountListPage::on_actionAddOffline_triggered() { MinecraftAccountPtr account = OfflineLoginDialog::newAccount( this, - tr("Please enter your desired username to add your offline account.") + tr("Please enter your desired username to add your offline account.
" + "
" + "It is required by Mojang that you own Minecraft BEFORE you may use offline mode.
" + "The PolyMC organization denounces piracy and takes NO LIABILITY WHATSOEVER
" + "for any illegal activity that may occur in usage of the offline mode feature.
" + "
" + "By continuing you promise that you own a Minecraft account.") ); if (account) From cdaa397dcffb92ae6a9c659047a87d49286dee4f Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Fri, 14 Jan 2022 14:19:31 +0100 Subject: [PATCH 06/45] Reword offline mode disclaimer --- launcher/ui/pages/global/AccountListPage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 1c27d5b73..ad88812ae 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -161,8 +161,8 @@ void AccountListPage::on_actionAddOffline_triggered() tr("Please enter your desired username to add your offline account.
" "
" "It is required by Mojang that you own Minecraft BEFORE you may use offline mode.
" - "The PolyMC organization denounces piracy and takes NO LIABILITY WHATSOEVER
" - "for any illegal activity that may occur in usage of the offline mode feature.
" + "The PolyMC developers denounce piracy and take NO LIABILITY WHATSOEVER for
" + "any illegal activity that may occur in usage of the offline mode feature.
" "
" "By continuing you promise that you own a Minecraft account.") ); From 41dba376a803825fca2dc6b3b812ff8a15a9588e Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 17:33:34 -0500 Subject: [PATCH 07/45] remove 5 from display name Closes: #58 --- program_info/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index d2f23277f..77b971fcd 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -3,8 +3,8 @@ set(Launcher_CommonName "PolyMC") set(Launcher_Copyright "PolyMC Contributors" PARENT_SCOPE) set(Launcher_Domain "github.com/PolyMC" PARENT_SCOPE) set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) -set(Launcher_DisplayName "${Launcher_CommonName} 5" PARENT_SCOPE) -set(Launcher_UserAgent "${Launcher_CommonName}/5.0" PARENT_SCOPE) +set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE) +set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_RELEASE_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "polymc.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PolyMC/PolyMC" PARENT_SCOPE) From b19e3156154ba0dd232a3d165b1759c57e2858f2 Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 17:34:50 -0500 Subject: [PATCH 08/45] Set maximum memory allocated to 4GB by default --- launcher/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index a3e2c44f9..47c9c20e5 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -662,7 +662,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); - m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024); + m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096); m_settings->registerSetting("PermGen", 128); // Java Settings From a62155c1c9e561327cc589fe3da7b6d5a107d58d Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 18:20:06 -0500 Subject: [PATCH 09/45] preliminary stuff for paste.ee removal --- CMakeLists.txt | 3 - buildconfig/BuildConfig.cpp.in | 1 - buildconfig/BuildConfig.h | 5 -- launcher/Application.cpp | 4 +- launcher/CMakeLists.txt | 6 +- .../global/{PasteEEPage.cpp => PastePage.cpp} | 22 ++++---- .../global/{PasteEEPage.h => PastePage.h} | 11 ++-- .../global/{PasteEEPage.ui => PastePage.ui} | 55 ++++++------------- 8 files changed, 40 insertions(+), 67 deletions(-) rename launcher/ui/pages/global/{PasteEEPage.cpp => PastePage.cpp} (81%) rename launcher/ui/pages/global/{PasteEEPage.h => PastePage.h} (88%) rename launcher/ui/pages/global/{PasteEEPage.ui => PastePage.ui} (61%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af0aa712..91119b2f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,9 +68,6 @@ set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notification # The metadata server set(Launcher_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") -# paste.ee API key -set(Launcher_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account") - # Imgur API Client ID set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application") diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index af8845dce..2595f78b2 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -42,7 +42,6 @@ Config::Config() VERSION_STR = "@Launcher_VERSION_STRING@"; NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@"; - PASTE_EE_KEY = "@Launcher_PASTE_EE_API_KEY@"; IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@"; MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@"; META_URL = "@Launcher_META_URL@"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 009fb2bca..d09d5288f 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -67,11 +67,6 @@ public: */ QString NEWS_RSS_URL; - /** - * API key you can get from paste.ee when you register an account - */ - QString PASTE_EE_KEY; - /** * Client ID you can get from Imgur when you register an application */ diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 47c9c20e5..98e3e0fc0 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -14,7 +14,7 @@ #include "ui/pages/global/ProxyPage.h" #include "ui/pages/global/ExternalToolsPage.h" #include "ui/pages/global/AccountListPage.h" -#include "ui/pages/global/PasteEEPage.h" +#include "ui/pages/global/PastePage.h" #include "ui/pages/global/CustomCommandsPage.h" #include "ui/themes/ITheme.h" @@ -728,7 +728,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); } qDebug() << "<> Settings loaded."; } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index df3614475..21859cad2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -711,8 +711,8 @@ SET(LAUNCHER_SOURCES ui/pages/global/LauncherPage.h ui/pages/global/ProxyPage.cpp ui/pages/global/ProxyPage.h - ui/pages/global/PasteEEPage.cpp - ui/pages/global/PasteEEPage.h + ui/pages/global/PastePage.cpp + ui/pages/global/PastePage.h # GUI - platform pages ui/pages/modplatform/VanillaPage.cpp @@ -848,7 +848,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/global/AccountListPage.ui ui/pages/global/JavaPage.ui ui/pages/global/LauncherPage.ui - ui/pages/global/PasteEEPage.ui + ui/pages/global/PastePage.ui ui/pages/global/ProxyPage.ui ui/pages/global/MinecraftPage.ui ui/pages/global/ExternalToolsPage.ui diff --git a/launcher/ui/pages/global/PasteEEPage.cpp b/launcher/ui/pages/global/PastePage.cpp similarity index 81% rename from launcher/ui/pages/global/PasteEEPage.cpp rename to launcher/ui/pages/global/PastePage.cpp index 4b375d9a2..3378a6efc 100644 --- a/launcher/ui/pages/global/PasteEEPage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2021 MultiMC & PolyMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ * limitations under the License. */ -#include "PasteEEPage.h" -#include "ui_PasteEEPage.h" +#include "PastePage.h" +#include "ui_PastePage.h" #include #include @@ -25,22 +25,22 @@ #include "tools/BaseProfiler.h" #include "Application.h" -PasteEEPage::PasteEEPage(QWidget *parent) : +PastePage::PastePage(QWidget *parent) : QWidget(parent), - ui(new Ui::PasteEEPage) + ui(new Ui::PastePage) { ui->setupUi(this); ui->tabWidget->tabBar()->hide();\ - connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited); + connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PastePage::textEdited); loadSettings(); } -PasteEEPage::~PasteEEPage() +PastePage::~PastePage() { delete ui; } -void PasteEEPage::loadSettings() +void PastePage::loadSettings() { auto s = APPLICATION->settings(); QString keyToUse = s->get("PasteEEAPIKey").toString(); @@ -55,7 +55,7 @@ void PasteEEPage::loadSettings() } } -void PasteEEPage::applySettings() +void PastePage::applySettings() { auto s = APPLICATION->settings(); @@ -69,13 +69,13 @@ void PasteEEPage::applySettings() s->set("PasteEEAPIKey", pasteKeyToUse); } -bool PasteEEPage::apply() +bool PastePage::apply() { applySettings(); return true; } -void PasteEEPage::textEdited(const QString& text) +void PastePage::textEdited(const QString& text) { ui->customButton->setChecked(true); } diff --git a/launcher/ui/pages/global/PasteEEPage.h b/launcher/ui/pages/global/PastePage.h similarity index 88% rename from launcher/ui/pages/global/PasteEEPage.h rename to launcher/ui/pages/global/PastePage.h index a1c7d434b..3930d4ec1 100644 --- a/launcher/ui/pages/global/PasteEEPage.h +++ b/launcher/ui/pages/global/PastePage.h @@ -21,16 +21,16 @@ #include namespace Ui { -class PasteEEPage; +class PastePage; } -class PasteEEPage : public QWidget, public BasePage +class PastePage : public QWidget, public BasePage { Q_OBJECT public: - explicit PasteEEPage(QWidget *parent = 0); - ~PasteEEPage(); + explicit PastePage(QWidget *parent = 0); + ~PastePage(); QString displayName() const override { @@ -58,5 +58,6 @@ private slots: void textEdited(const QString &text); private: - Ui::PasteEEPage *ui; + Ui::PastePage *ui; }; + diff --git a/launcher/ui/pages/global/PasteEEPage.ui b/launcher/ui/pages/global/PastePage.ui similarity index 61% rename from launcher/ui/pages/global/PasteEEPage.ui rename to launcher/ui/pages/global/PastePage.ui index 108837817..0bef5a226 100644 --- a/launcher/ui/pages/global/PasteEEPage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -1,7 +1,7 @@ - PasteEEPage - + PastePage + 0 @@ -36,39 +36,9 @@ - paste.ee API key + Pastebin Site - - - - MultiMC key - 12MB &upload limit - - - pasteButtonGroup - - - - - - - &Your own key - 12MB upload limit: - - - pasteButtonGroup - - - - - - - QLineEdit::Password - - - Paste your API key here! - - - @@ -76,10 +46,24 @@ + + + + + 0x0.st + + + + + paste.polymc.org + + + + - <html><head/><body><p><a href="https://paste.ee">paste.ee</a> is used by MultiMC for log uploads. If you have a <a href="https://paste.ee">paste.ee</a> account, you can add your API key here and have your uploaded logs paired with your account.</p></body></html> + <html><head/><body><p>paste.polymc.org is a pastebin managed by PolyMC's lead maintainer. Something something trust</p></body></html> Qt::RichText @@ -116,9 +100,6 @@ tabWidget - multimcButton - customButton - customAPIkeyEdit From a606b47a22443cefc52d865df24c45ff50908f6f Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 18:30:02 -0500 Subject: [PATCH 10/45] pastebin URL app setting --- launcher/Application.cpp | 4 ++-- launcher/ui/pages/global/PastePage.cpp | 13 +++---------- launcher/ui/pages/global/PastePage.ui | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 98e3e0fc0..110b2e6b5 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -714,8 +714,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); - // paste.ee API key - m_settings->registerSetting("PasteEEAPIKey", "multimc"); + // pastebin URL + m_settings->registerSetting("PastebinURL", "0x0.st"); // Init page provider { diff --git a/launcher/ui/pages/global/PastePage.cpp b/launcher/ui/pages/global/PastePage.cpp index 3378a6efc..495e9937f 100644 --- a/launcher/ui/pages/global/PastePage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -43,16 +43,9 @@ PastePage::~PastePage() void PastePage::loadSettings() { auto s = APPLICATION->settings(); - QString keyToUse = s->get("PasteEEAPIKey").toString(); - if(keyToUse == "multimc") - { - ui->multimcButton->setChecked(true); - } - else - { - ui->customButton->setChecked(true); - ui->customAPIkeyEdit->setText(keyToUse); - } + QString pastebin = s->get("PastebinURL"); + int index = ui->urlChoices->findText(pastebin); + ui->urlChoices->setCurrentIndex(index); } void PastePage::applySettings() diff --git a/launcher/ui/pages/global/PastePage.ui b/launcher/ui/pages/global/PastePage.ui index 0bef5a226..784ea3f42 100644 --- a/launcher/ui/pages/global/PastePage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -47,7 +47,7 @@ - + 0x0.st From 6cd4375aff98554c56a0138208d869aca0587656 Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 18:39:25 -0500 Subject: [PATCH 11/45] add arch linux package to readme closes: #53 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d9d414e73..7b67c5882 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Several source build packages are available, along with experimental pre-built g - [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`. - [AppImage (AMD64)](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage) ([SHA256](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage.sha256)) - `chmod +x` must be run on this file before usage. This should work on any distribution. +- [Arch Linux (AMD64)](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst) ([SHA256](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst.sha256) - this is intended to be installed with `pacman -U`. This is an alternative if building the AUR package is not desired. - MacOS currently does not have any packages. We are still working on setting up MacOS packaging. ## Development From ac93c64cd40be038a0f3a71df18686c5e1f955c3 Mon Sep 17 00:00:00 2001 From: Thomas Sirack Date: Fri, 14 Jan 2022 16:59:16 -0700 Subject: [PATCH 12/45] Fix program executable name for shell script --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af0aa712..a19556cbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ endif() ####################################### Program Info ####################################### -set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary") +set(Launcher_APP_BINARY_NAME "PolyMC" CACHE STRING "Name of the Launcher binary") add_subdirectory(program_info) ####################################### Install layout ####################################### From 0bbd0ac0b9b0ba7212ed15d4628577bf92005a18 Mon Sep 17 00:00:00 2001 From: Thomas Sirack Date: Fri, 14 Jan 2022 19:28:10 -0700 Subject: [PATCH 13/45] Change method of shell script fix per suggestion The Launcher.in file is now modified rather than CMakeLists.txt --- CMakeLists.txt | 2 +- launcher/Launcher.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a19556cbd..2af0aa712 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ endif() ####################################### Program Info ####################################### -set(Launcher_APP_BINARY_NAME "PolyMC" CACHE STRING "Name of the Launcher binary") +set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary") add_subdirectory(program_info) ####################################### Install layout ####################################### diff --git a/launcher/Launcher.in b/launcher/Launcher.in index b79b276bc..5e5e2c2bc 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -14,7 +14,7 @@ if [[ $EUID -eq 0 ]]; then fi -LAUNCHER_NAME=@Launcher_Name@ +LAUNCHER_NAME=@Launcher_APP_BINARY_NAME@ LAUNCHER_DIR="$(dirname "$(readlink -f "$0")")" echo "Launcher Dir: ${LAUNCHER_DIR}" From dc129fd8868f1888fcc55136f3cdc2486ae8ab6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 8 Jan 2022 11:14:07 +0100 Subject: [PATCH 14/45] GH-4125 workaround for java printing garbage to stdout on bedrock linux --- launcher/java/JavaChecker.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 80c599cc1..c3132af35 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -61,6 +61,10 @@ void JavaChecker::stdoutReady() QByteArray data = process->readAllStandardOutput(); QString added = QString::fromLocal8Bit(data); added.remove('\r'); + // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux + if (added.contains("/bedrock/strata")) { + return; + } m_stdout += added; } From f78bb90ed9f2658eee9259a472efe47a25eddc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 8 Jan 2022 12:26:16 +0100 Subject: [PATCH 15/45] GH-4125 fix it better --- launcher/java/JavaChecker.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index c3132af35..4557784b0 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -61,10 +61,6 @@ void JavaChecker::stdoutReady() QByteArray data = process->readAllStandardOutput(); QString added = QString::fromLocal8Bit(data); added.remove('\r'); - // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux - if (added.contains("/bedrock/strata")) { - return; - } m_stdout += added; } @@ -107,6 +103,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) for(QString line : lines) { line = line.trimmed(); + // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux + if (line.contains("/bedrock/strata")) { + continue; + } auto parts = line.split('=', QString::SkipEmptyParts); if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) From b95c27ceef1a1742d62ae0a758657fd0beea18c6 Mon Sep 17 00:00:00 2001 From: swirl Date: Sat, 15 Jan 2022 21:25:49 -0500 Subject: [PATCH 16/45] fix readme formatting --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b67c5882..d97ab67c0 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ PolyMC logo


+ PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. 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. @@ -23,7 +24,7 @@ Several source build packages are available, along with experimental pre-built g - [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`. - [AppImage (AMD64)](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage) ([SHA256](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage.sha256)) - `chmod +x` must be run on this file before usage. This should work on any distribution. -- [Arch Linux (AMD64)](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst) ([SHA256](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst.sha256) - this is intended to be installed with `pacman -U`. This is an alternative if building the AUR package is not desired. +- [Arch Linux (AMD64)](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst) ([SHA256](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst.sha256)) - this is intended to be installed with `pacman -U`. This is an alternative if building the AUR package is not desired. - MacOS currently does not have any packages. We are still working on setting up MacOS packaging. ## Development From 8172dcd2d53114df05a23fa58e3832d2011f2e17 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Sun, 16 Jan 2022 07:55:10 +0100 Subject: [PATCH 17/45] Replace PNG README header with SVG Instead of GitHub specific MarkDown based theming this uses CSS in SVG. --- README.md | 3 +-- program_info/polymc-dark.png | Bin 50722 -> 0 bytes program_info/polymc-header.svg | 38 +++++++++++++++++++++++++++++++++ program_info/polymc-light.png | Bin 48991 -> 0 bytes 4 files changed, 39 insertions(+), 2 deletions(-) delete mode 100644 program_info/polymc-dark.png create mode 100644 program_info/polymc-header.svg delete mode 100644 program_info/polymc-light.png diff --git a/README.md b/README.md index d9d414e73..40d4c6356 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@

- PolyMC logo - PolyMC logo + PolyMC logo


PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. diff --git a/program_info/polymc-dark.png b/program_info/polymc-dark.png deleted file mode 100644 index cedf6cef79a83483da55ec682ca6081e860f67e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50722 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 diff --git a/program_info/polymc-header.svg b/program_info/polymc-header.svg new file mode 100644 index 000000000..fb91a54b8 --- /dev/null +++ b/program_info/polymc-header.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/program_info/polymc-light.png b/program_info/polymc-light.png deleted file mode 100644 index d3899a1f22edaf51a4e7bf478496184cb89a8a3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48991 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

(JeYaw?!|+dZWVK+?4EMnes)JR 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

KvZoT&H=J7(=bwbmOz6wuf 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 Date: Sun, 16 Jan 2022 12:02:53 -0500 Subject: [PATCH 18/45] Default to colored icons, update copyright Closes: #74 --- COPYING.md | 4 ++-- launcher/Application.cpp | 2 +- launcher/ui/dialogs/AboutDialog.cpp | 2 +- launcher/ui/pages/global/LauncherPage.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/COPYING.md b/COPYING.md index 4205d441a..1ac6d5cb9 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,7 +1,7 @@ # PolyMC Copyright (C) 2012-2021 MultiMC Contributors - Copyright (C) 2021 PolyMC Contributors + Copyright (C) 2021-2022 PolyMC Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . - + # Launcher (https://github.com/MultiMC/Launcher) Copyright 2012-2021 MultiMC Contributors Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 47c9c20e5..8d1c4d623 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -595,7 +595,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("AutoUpdate", true); // Theming - m_settings->registerSetting("IconTheme", QString("multimc")); + m_settings->registerSetting("IconTheme", QString("pe_colored")); m_settings->registerSetting("ApplicationTheme", QString("system")); // Notifications diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 2ba34f1ac..46d2f4293 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -99,7 +99,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia QString urlText("

%1

"); ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT)); - QString copyText("© 2012-2021 %1"); + QString copyText("© 2021-2022 %1"); ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT)); connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 4d4d4e896..81ecd58f3 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -256,7 +256,7 @@ void LauncherPage::applySettings() s->set("IconTheme", "pe_blue"); break; case 4: - s->set("IconTheme", "pe_colored"); + s->set("IconTheme", "multimc"); break; case 5: s->set("IconTheme", "OSX"); @@ -272,7 +272,7 @@ void LauncherPage::applySettings() break; case 0: default: - s->set("IconTheme", "multimc"); + s->set("IconTheme", "pe_colored"); break; } From 5f9270ed4b97dd647bb49840f76132ddcdc13a1b Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Sun, 16 Jan 2022 23:30:17 +0100 Subject: [PATCH 19/45] Fix MSA account refresh --- launcher/minecraft/auth/MinecraftAccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 6592be0fe..ffc81ed86 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -147,7 +147,7 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } - if(data.type == AccountType::Offline) { + else if(data.type == AccountType::Offline) { m_currentTask.reset(new OfflineRefresh(&data)); } else { From f55297eca94ed3e1912e6d7a13d457f9e4b9010b Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Mon, 17 Jan 2022 03:45:33 +0000 Subject: [PATCH 20/45] Revert "Merge pull request #81 from bexnoss/fix-msa-account-refresh" This reverts commit 0bc8baf1172d6967cdb2993826e3469ecd9aab66, reversing changes made to 81fe41a038ee12ff2ee0200525b39e4ad650bec2. --- launcher/minecraft/auth/MinecraftAccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index ffc81ed86..6592be0fe 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -147,7 +147,7 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } - else if(data.type == AccountType::Offline) { + if(data.type == AccountType::Offline) { m_currentTask.reset(new OfflineRefresh(&data)); } else { From 55597b458ced4d7ad8082ab226617ba48e177ee6 Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Mon, 17 Jan 2022 03:45:47 +0000 Subject: [PATCH 21/45] Revert "Merge pull request #50 from bexnoss/offline-mode" This reverts commit b4f750e7db40352111417ea89a9f375ae8c746ab, reversing changes made to b19e3156154ba0dd232a3d165b1759c57e2858f2. --- launcher/CMakeLists.txt | 7 -- launcher/LaunchController.cpp | 6 -- launcher/minecraft/auth/AccountData.cpp | 10 +- launcher/minecraft/auth/AccountData.h | 3 +- launcher/minecraft/auth/AccountList.cpp | 2 +- launcher/minecraft/auth/MinecraftAccount.cpp | 31 ------ launcher/minecraft/auth/MinecraftAccount.h | 12 --- launcher/minecraft/auth/flows/Offline.cpp | 17 ---- launcher/minecraft/auth/flows/Offline.h | 22 ----- launcher/minecraft/auth/steps/OfflineStep.cpp | 18 ---- launcher/minecraft/auth/steps/OfflineStep.h | 20 ---- launcher/ui/dialogs/OfflineLoginDialog.cpp | 98 ------------------- launcher/ui/dialogs/OfflineLoginDialog.h | 43 -------- launcher/ui/dialogs/OfflineLoginDialog.ui | 67 ------------- launcher/ui/pages/global/AccountListPage.cpp | 23 ----- launcher/ui/pages/global/AccountListPage.h | 1 - launcher/ui/pages/global/AccountListPage.ui | 6 -- 17 files changed, 3 insertions(+), 383 deletions(-) delete mode 100644 launcher/minecraft/auth/flows/Offline.cpp delete mode 100644 launcher/minecraft/auth/flows/Offline.h delete mode 100644 launcher/minecraft/auth/steps/OfflineStep.cpp delete mode 100644 launcher/minecraft/auth/steps/OfflineStep.h delete mode 100644 launcher/ui/dialogs/OfflineLoginDialog.cpp delete mode 100644 launcher/ui/dialogs/OfflineLoginDialog.h delete mode 100644 launcher/ui/dialogs/OfflineLoginDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index df3614475..b5c52afa2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -221,11 +221,7 @@ set(MINECRAFT_SOURCES minecraft/auth/flows/Mojang.h minecraft/auth/flows/MSA.cpp minecraft/auth/flows/MSA.h - minecraft/auth/flows/Offline.cpp - minecraft/auth/flows/Offline.h - minecraft/auth/steps/OfflineStep.cpp - minecraft/auth/steps/OfflineStep.h minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.h minecraft/auth/steps/GetSkinStep.cpp @@ -773,8 +769,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/LoginDialog.h ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.h - ui/dialogs/OfflineLoginDialog.cpp - ui/dialogs/OfflineLoginDialog.h ui/dialogs/NewComponentDialog.cpp ui/dialogs/NewComponentDialog.h ui/dialogs/NewInstanceDialog.cpp @@ -886,7 +880,6 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/ExportInstanceDialog.ui ui/dialogs/IconPickerDialog.ui ui/dialogs/MSALoginDialog.ui - ui/dialogs/OfflineLoginDialog.ui ui/dialogs/AboutDialog.ui ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 32fc99cb9..7750be1a2 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -116,12 +116,6 @@ void LaunchController::login() { m_session->wants_online = m_online; m_accountToUse->fillSession(m_session); - // Launch immediately in true offline mode - if(m_accountToUse->isOffline()) { - launchInstance(); - return; - } - switch(m_accountToUse->accountState()) { case AccountState::Offline: { m_session->wants_online = false; diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 9b84fe1a3..7526c9517 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -314,8 +314,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { type = AccountType::MSA; } else if (typeS == "Mojang") { type = AccountType::Mojang; - } else if (typeS == "Offline") { - type = AccountType::Offline; } else { qWarning() << "Failed to parse account data: type is not recognized."; return false; @@ -365,9 +363,6 @@ QJsonObject AccountData::saveState() const { tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); } - else if (type == AccountType::Offline) { - output["type"] = "Offline"; - } tokenToJSONV3(output, yggdrasilToken, "ygg"); profileToJSONV3(output, minecraftProfile, "profile"); @@ -376,7 +371,7 @@ QJsonObject AccountData::saveState() const { } QString AccountData::userName() const { - if(type == AccountType::MSA) { + if(type != AccountType::Mojang) { return QString(); } return yggdrasilToken.extra["userName"].toString(); @@ -432,9 +427,6 @@ QString AccountData::accountDisplayString() const { case AccountType::Mojang: { return userName(); } - case AccountType::Offline: { - return userName(); - } case AccountType::MSA: { if(xboxApiToken.extra.contains("gtg")) { return xboxApiToken.extra["gtg"].toString(); diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 606c1ad11..abf84e43a 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -38,8 +38,7 @@ struct MinecraftProfile { enum class AccountType { MSA, - Mojang, - Offline + Mojang }; enum class AccountState { diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 04470e1c1..ef8b435d1 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -302,7 +302,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const } case MigrationColumn: { - if(account->isMSA() || account->isOffline()) { + if(account->isMSA()) { return tr("N/A", "Can Migrate?"); } if (account->canMigrate()) { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 6592be0fe..ed9e945ee 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -30,7 +30,6 @@ #include "flows/MSA.h" #include "flows/Mojang.h" -#include "flows/Offline.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); @@ -69,23 +68,6 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() return account; } -MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) -{ - MinecraftAccountPtr account = new MinecraftAccount(); - account->data.type = AccountType::Offline; - account->data.yggdrasilToken.token = "offline"; - account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; - account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); - account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->data.minecraftEntitlement.ownsMinecraft = true; - account->data.minecraftEntitlement.canPlayMinecraft = true; - account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->data.minecraftProfile.name = username; - account->data.minecraftProfile.validity = Katabasis::Validity::Certain; - return account; -} - QJsonObject MinecraftAccount::saveToJson() const { @@ -129,16 +111,6 @@ shared_qobject_ptr MinecraftAccount::loginMSA() { return m_currentTask; } -shared_qobject_ptr MinecraftAccount::loginOffline() { - Q_ASSERT(m_currentTask.get() == nullptr); - - m_currentTask.reset(new OfflineLogin(&data)); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); - emit activityChanged(true); - return m_currentTask; -} - shared_qobject_ptr MinecraftAccount::refresh() { if(m_currentTask) { return m_currentTask; @@ -147,9 +119,6 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } - if(data.type == AccountType::Offline) { - m_currentTask.reset(new OfflineRefresh(&data)); - } else { m_currentTask.reset(new MojangRefresh(&data)); } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 6592f9c07..7ab3c7468 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -73,8 +73,6 @@ public: /* construction */ static MinecraftAccountPtr createBlankMSA(); - static MinecraftAccountPtr createOffline(const QString &username); - static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json); static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); @@ -91,8 +89,6 @@ public: /* manipulation */ shared_qobject_ptr loginMSA(); - shared_qobject_ptr loginOffline(); - shared_qobject_ptr refresh(); shared_qobject_ptr currentTask(); @@ -132,10 +128,6 @@ public: /* queries */ return data.type == AccountType::MSA; } - bool isOffline() const { - return data.type == AccountType::Offline; - } - bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; } @@ -157,10 +149,6 @@ public: /* queries */ return "msa"; } break; - case AccountType::Offline: { - return "offline"; - } - break; default: { return "unknown"; } diff --git a/launcher/minecraft/auth/flows/Offline.cpp b/launcher/minecraft/auth/flows/Offline.cpp deleted file mode 100644 index fc614a8c7..000000000 --- a/launcher/minecraft/auth/flows/Offline.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "Offline.h" - -#include "minecraft/auth/steps/OfflineStep.h" - -OfflineRefresh::OfflineRefresh( - AccountData *data, - QObject *parent -) : AuthFlow(data, parent) { - m_steps.append(new OfflineStep(m_data)); -} - -OfflineLogin::OfflineLogin( - AccountData *data, - QObject *parent -) : AuthFlow(data, parent) { - m_steps.append(new OfflineStep(m_data)); -} diff --git a/launcher/minecraft/auth/flows/Offline.h b/launcher/minecraft/auth/flows/Offline.h deleted file mode 100644 index 5d1f83a46..000000000 --- a/launcher/minecraft/auth/flows/Offline.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "AuthFlow.h" - -class OfflineRefresh : public AuthFlow -{ - Q_OBJECT -public: - explicit OfflineRefresh( - AccountData *data, - QObject *parent = 0 - ); -}; - -class OfflineLogin : public AuthFlow -{ - Q_OBJECT -public: - explicit OfflineLogin( - AccountData *data, - QObject *parent = 0 - ); -}; diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp deleted file mode 100644 index dc092bfd4..000000000 --- a/launcher/minecraft/auth/steps/OfflineStep.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "OfflineStep.h" - -#include "Application.h" - -OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {} -OfflineStep::~OfflineStep() noexcept = default; - -QString OfflineStep::describe() { - return tr("Creating offline account."); -} - -void OfflineStep::rehydrate() { - // NOOP -} - -void OfflineStep::perform() { - emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account.")); -} diff --git a/launcher/minecraft/auth/steps/OfflineStep.h b/launcher/minecraft/auth/steps/OfflineStep.h deleted file mode 100644 index 62addb1f7..000000000 --- a/launcher/minecraft/auth/steps/OfflineStep.h +++ /dev/null @@ -1,20 +0,0 @@ - -#pragma once -#include - -#include "QObjectPtr.h" -#include "minecraft/auth/AuthStep.h" - -#include - -class OfflineStep : public AuthStep { - Q_OBJECT -public: - explicit OfflineStep(AccountData *data); - virtual ~OfflineStep() noexcept; - - void perform() override; - void rehydrate() override; - - QString describe() override; -}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp deleted file mode 100644 index 345ed40ac..000000000 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "OfflineLoginDialog.h" -#include "ui_OfflineLoginDialog.h" - -#include "minecraft/auth/AccountTask.h" - -#include - -OfflineLoginDialog::OfflineLoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog) -{ - ui->setupUi(this); - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -OfflineLoginDialog::~OfflineLoginDialog() -{ - delete ui; -} - -// Stage 1: User interaction -void OfflineLoginDialog::accept() -{ - setUserInputsEnabled(false); - ui->progressBar->setVisible(true); - - // Setup the login task and start it - m_account = MinecraftAccount::createOffline(ui->userTextBox->text()); - m_loginTask = m_account->loginOffline(); - connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed); - connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded); - connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus); - connect(m_loginTask.get(), &Task::progress, this, &OfflineLoginDialog::onTaskProgress); - m_loginTask->start(); -} - -void OfflineLoginDialog::setUserInputsEnabled(bool enable) -{ - ui->userTextBox->setEnabled(enable); - ui->buttonBox->setEnabled(enable); -} - -// Enable the OK button only when the textbox contains something. -void OfflineLoginDialog::on_userTextBox_textEdited(const QString &newText) -{ - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty()); -} - -void OfflineLoginDialog::onTaskFailed(const QString &reason) -{ - // Set message - auto lines = reason.split('\n'); - QString processed; - for(auto line: lines) { - if(line.size()) { - processed += "" + line + "
"; - } - else { - processed += "
"; - } - } - ui->label->setText(processed); - - // Re-enable user-interaction - setUserInputsEnabled(true); - ui->progressBar->setVisible(false); -} - -void OfflineLoginDialog::onTaskSucceeded() -{ - QDialog::accept(); -} - -void OfflineLoginDialog::onTaskStatus(const QString &status) -{ - ui->label->setText(status); -} - -void OfflineLoginDialog::onTaskProgress(qint64 current, qint64 total) -{ - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); -} - -// Public interface -MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg) -{ - OfflineLoginDialog dlg(parent); - dlg.ui->label->setText(msg); - if (dlg.exec() == QDialog::Accepted) - { - return dlg.m_account; - } - return 0; -} diff --git a/launcher/ui/dialogs/OfflineLoginDialog.h b/launcher/ui/dialogs/OfflineLoginDialog.h deleted file mode 100644 index 5e6083792..000000000 --- a/launcher/ui/dialogs/OfflineLoginDialog.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include - -#include "minecraft/auth/MinecraftAccount.h" -#include "tasks/Task.h" - -namespace Ui -{ -class OfflineLoginDialog; -} - -class OfflineLoginDialog : public QDialog -{ - Q_OBJECT - -public: - ~OfflineLoginDialog(); - - static MinecraftAccountPtr newAccount(QWidget *parent, QString message); - -private: - explicit OfflineLoginDialog(QWidget *parent = 0); - - void setUserInputsEnabled(bool enable); - -protected -slots: - void accept(); - - void onTaskFailed(const QString &reason); - void onTaskSucceeded(); - void onTaskStatus(const QString &status); - void onTaskProgress(qint64 current, qint64 total); - - void on_userTextBox_textEdited(const QString &newText); - -private: - Ui::OfflineLoginDialog *ui; - MinecraftAccountPtr m_account; - Task::Ptr m_loginTask; -}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.ui b/launcher/ui/dialogs/OfflineLoginDialog.ui deleted file mode 100644 index 4577d361b..000000000 --- a/launcher/ui/dialogs/OfflineLoginDialog.ui +++ /dev/null @@ -1,67 +0,0 @@ - - - OfflineLoginDialog - - - - 0 - 0 - 500 - 250 - - - - - 0 - 0 - - - - Add Account - - - - - - Message label placeholder. - - - Qt::RichText - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Username - - - - - - - 69 - - - false - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index ad88812ae..b8da6c754 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -24,7 +24,6 @@ #include "net/NetJob.h" #include "ui/dialogs/ProgressDialog.h" -#include "ui/dialogs/OfflineLoginDialog.h" #include "ui/dialogs/LoginDialog.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/CustomMessageBox.h" @@ -154,28 +153,6 @@ void AccountListPage::on_actionAddMicrosoft_triggered() } } -void AccountListPage::on_actionAddOffline_triggered() -{ - MinecraftAccountPtr account = OfflineLoginDialog::newAccount( - this, - tr("Please enter your desired username to add your offline account.
" - "
" - "It is required by Mojang that you own Minecraft BEFORE you may use offline mode.
" - "The PolyMC developers denounce piracy and take NO LIABILITY WHATSOEVER for
" - "any illegal activity that may occur in usage of the offline mode feature.
" - "
" - "By continuing you promise that you own a Minecraft account.") - ); - - if (account) - { - m_accounts->addAccount(account); - if (m_accounts->count() == 1) { - m_accounts->setDefaultAccount(account); - } - } -} - void AccountListPage::on_actionRemove_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 841c3fd2d..1c65e7081 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -62,7 +62,6 @@ public: public slots: void on_actionAddMojang_triggered(); void on_actionAddMicrosoft_triggered(); - void on_actionAddOffline_triggered(); void on_actionRemove_triggered(); void on_actionRefresh_triggered(); void on_actionSetDefault_triggered(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index d21a92e23..29738c023 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -54,7 +54,6 @@ - @@ -104,11 +103,6 @@ Add Microsoft
- - - Add Offline - - Refresh From 236c0166f6b0f7fc2d726d509edd49b017b98b12 Mon Sep 17 00:00:00 2001 From: Philipp David Date: Wed, 12 Jan 2022 17:44:58 +0100 Subject: [PATCH 22/45] Default to system locale language --- launcher/translations/TranslationsModel.cpp | 29 ++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 2e7440079..5533c0826 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -143,6 +143,8 @@ struct TranslationsModel::Private std::unique_ptr m_po_translator; QFileSystemWatcher *watcher; + + bool no_language_set = false; }; TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent) @@ -165,7 +167,26 @@ void TranslationsModel::translationDirChanged(const QString& path) { qDebug() << "Dir changed:" << path; reloadLocalFiles(); - selectLanguage(selectedLanguage()); + + if (d->no_language_set) + { + auto bcp47Name = QLocale::system().name(); + if (!findLanguage(bcp47Name)) + { + bcp47Name = bcp47Name.split('_').front(); + } + selectLanguage(bcp47Name); + if (selectedLanguage() != defaultLangCode) + { + updateLanguage(selectedLanguage()); + APPLICATION->settings()->set("Language", selectedLanguage()); + } + d->no_language_set = false; + } + else + { + selectLanguage(selectedLanguage()); + } } void TranslationsModel::indexReceived() @@ -439,6 +460,12 @@ bool TranslationsModel::selectLanguage(QString key) { QString &langCode = key; auto langPtr = findLanguage(key); + + if (langCode.length() == 0) + { + d->no_language_set = true; + } + if(!langPtr) { qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; From 126e6d13aa59f5c09b4bb3956cbf85ed8ae72fa2 Mon Sep 17 00:00:00 2001 From: Philipp David Date: Thu, 13 Jan 2022 08:29:50 +0100 Subject: [PATCH 23/45] Use isEmpty instead of 0 length check --- launcher/translations/TranslationsModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 5533c0826..bfbe8f58e 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -461,7 +461,7 @@ bool TranslationsModel::selectLanguage(QString key) QString &langCode = key; auto langPtr = findLanguage(key); - if (langCode.length() == 0) + if (langCode.isEmpty()) { d->no_language_set = true; } From 2dd2555a63a3098359e5a9873b748619b20b98ef Mon Sep 17 00:00:00 2001 From: Philipp David Date: Thu, 13 Jan 2022 09:41:51 +0100 Subject: [PATCH 24/45] Update selected language automatically --- launcher/translations/TranslationsModel.cpp | 2 +- launcher/ui/widgets/LanguageSelectionWidget.cpp | 11 +++++++++++ launcher/ui/widgets/LanguageSelectionWidget.h | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index bfbe8f58e..aa76faa22 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -179,8 +179,8 @@ void TranslationsModel::translationDirChanged(const QString& path) if (selectedLanguage() != defaultLangCode) { updateLanguage(selectedLanguage()); - APPLICATION->settings()->set("Language", selectedLanguage()); } + APPLICATION->settings()->set("Language", selectedLanguage()); d->no_language_set = false; } else diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp index cf70c7b41..964d2b7c2 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.cpp +++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp @@ -6,6 +6,7 @@ #include #include "Application.h" #include "translations/TranslationsModel.h" +#include "settings/Setting.h" LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : QWidget(parent) @@ -37,6 +38,9 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch); connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged); verticalLayout->setContentsMargins(0,0,0,0); + + auto language_setting = APPLICATION->settings()->getSetting("Language"); + connect(language_setting.get(), &Setting::SettingChanged, this, &LanguageSelectionWidget::languageSettingChanged); } QString LanguageSelectionWidget::getSelectedLanguageKey() const @@ -64,3 +68,10 @@ void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, con translations->selectLanguage(key); translations->updateLanguage(key); } + +void LanguageSelectionWidget::languageSettingChanged(const Setting &, const QVariant) +{ + auto translations = APPLICATION->translations(); + auto index = translations->selectedIndex(); + languageView->setCurrentIndex(index); +} diff --git a/launcher/ui/widgets/LanguageSelectionWidget.h b/launcher/ui/widgets/LanguageSelectionWidget.h index e65936db7..4a88924c4 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.h +++ b/launcher/ui/widgets/LanguageSelectionWidget.h @@ -20,6 +20,7 @@ class QVBoxLayout; class QTreeView; class QLabel; +class Setting; class LanguageSelectionWidget: public QWidget { @@ -33,6 +34,7 @@ public: protected slots: void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void languageSettingChanged(const Setting &, const QVariant); private: QVBoxLayout *verticalLayout = nullptr; From b9beb3c7d27f41b601c37caf94044015b0a80cc7 Mon Sep 17 00:00:00 2001 From: Philipp David Date: Thu, 13 Jan 2022 10:20:53 +0100 Subject: [PATCH 25/45] Sort system locale to front of list --- launcher/translations/TranslationsModel.cpp | 26 ++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index aa76faa22..0cf4d5487 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -144,6 +144,9 @@ struct TranslationsModel::Private std::unique_ptr m_po_translator; QFileSystemWatcher *watcher; + const QString m_system_locale = QLocale::system().name(); + const QString m_system_language = m_system_locale.split('_').front(); + bool no_language_set = false; }; @@ -170,12 +173,12 @@ void TranslationsModel::translationDirChanged(const QString& path) if (d->no_language_set) { - auto bcp47Name = QLocale::system().name(); - if (!findLanguage(bcp47Name)) + auto language = d->m_system_locale; + if (!findLanguage(language)) { - bcp47Name = bcp47Name.split('_').front(); + language = d->m_system_language; } - selectLanguage(bcp47Name); + selectLanguage(language); if (selectedLanguage() != defaultLangCode) { updateLanguage(selectedLanguage()); @@ -340,8 +343,19 @@ void TranslationsModel::reloadLocalFiles() { d->m_languages.append(language); } - std::sort(d->m_languages.begin(), d->m_languages.end(), [](const Language& a, const Language& b) { - return a.key.compare(b.key) < 0; + std::sort(d->m_languages.begin(), d->m_languages.end(), [this](const Language& a, const Language& b) { + if (a.key != b.key) + { + if (a.key == d->m_system_locale || a.key == d->m_system_language) + { + return true; + } + if (b.key == d->m_system_locale || b.key == d->m_system_language) + { + return false; + } + } + return a.key < b.key; }); endInsertRows(); } From 83e1dd285af04a1edcc9344bc9316d30f036cadd Mon Sep 17 00:00:00 2001 From: Philipp David Date: Mon, 17 Jan 2022 09:49:47 +0100 Subject: [PATCH 26/45] Set default lang only if index received --- launcher/translations/TranslationsModel.cpp | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 0cf4d5487..0fa82e35e 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -169,10 +169,22 @@ TranslationsModel::~TranslationsModel() void TranslationsModel::translationDirChanged(const QString& path) { qDebug() << "Dir changed:" << path; - reloadLocalFiles(); + if (!d->no_language_set) + { + reloadLocalFiles(); + } + selectLanguage(selectedLanguage()); +} + +void TranslationsModel::indexReceived() +{ + qDebug() << "Got translations index!"; + d->m_index_job.reset(); if (d->no_language_set) { + reloadLocalFiles(); + auto language = d->m_system_locale; if (!findLanguage(language)) { @@ -186,17 +198,8 @@ void TranslationsModel::translationDirChanged(const QString& path) APPLICATION->settings()->set("Language", selectedLanguage()); d->no_language_set = false; } - else - { - selectLanguage(selectedLanguage()); - } -} -void TranslationsModel::indexReceived() -{ - qDebug() << "Got translations index!"; - d->m_index_job.reset(); - if(d->m_selectedLanguage != defaultLangCode) + else if(d->m_selectedLanguage != defaultLangCode) { downloadTranslation(d->m_selectedLanguage); } From b50e58436975761fcb7b8886eb137330e5b6e29a Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 19 Jan 2022 07:44:29 +0000 Subject: [PATCH 27/45] PasteUpload task changed to use 0x0.st's protocol - Modified PasteUpload task to upload the log file to 0x0.st and other services with the same protocol. - Modified Paste settings UI to allow the user to select a custom paste URL, simplified the settings page code. --- launcher/Application.cpp | 2 +- launcher/net/PasteUpload.cpp | 88 +++++++-------------- launcher/net/PasteUpload.h | 22 +----- launcher/ui/GuiUtil.cpp | 17 +--- launcher/ui/pages/global/PastePage.cpp | 23 ++---- launcher/ui/pages/global/PastePage.h | 3 - launcher/ui/pages/global/PastePage.ui | 17 ++-- launcher/ui/pages/instance/LogPage.ui | 2 +- launcher/ui/pages/instance/OtherLogsPage.ui | 2 +- 9 files changed, 51 insertions(+), 125 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 110b2e6b5..a5b861d09 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -715,7 +715,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); // pastebin URL - m_settings->registerSetting("PastebinURL", "0x0.st"); + m_settings->registerSetting("PastebinURL", "https://0x0.st"); // Init page provider { diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 4b69b68a1..6c5aa2214 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -8,44 +8,34 @@ #include #include -PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window) +PasteUpload::PasteUpload(QWidget *window, QString text, QString url) : m_window(window), m_uploadUrl(url), m_text(text.toUtf8()) { - m_key = key; - QByteArray temp; - QJsonObject topLevelObj; - QJsonObject sectionObject; - sectionObject.insert("contents", text); - QJsonArray sectionArray; - sectionArray.append(sectionObject); - topLevelObj.insert("description", "Log Upload"); - topLevelObj.insert("sections", sectionArray); - QJsonDocument docOut; - docOut.setObject(topLevelObj); - m_jsonContent = docOut.toJson(); } PasteUpload::~PasteUpload() { } -bool PasteUpload::validateText() -{ - return m_jsonContent.size() <= maxSize(); -} - void PasteUpload::executeTask() { - QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); + QNetworkRequest request{QUrl(m_uploadUrl)}; request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); - request.setRawHeader("Content-Type", "application/json"); - request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); - request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); + QHttpMultiPart *multiPart = new QHttpMultiPart{QHttpMultiPart::FormDataType}; - QNetworkReply *rep = APPLICATION->network()->post(request, m_jsonContent); + QHttpPart filePart; + filePart.setBody(m_text); + filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\""); + + multiPart->append(filePart); + + QNetworkReply *rep = APPLICATION->network()->post(request, multiPart); + multiPart->setParent(rep); m_reply = std::shared_ptr(rep); - setStatus(tr("Uploading to paste.ee")); + setStatus(tr("Uploading to %1").arg(m_uploadUrl)); + connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); @@ -61,45 +51,23 @@ void PasteUpload::downloadError(QNetworkReply::NetworkError error) void PasteUpload::downloadFinished() { QByteArray data = m_reply->readAll(); - // if the download succeeded - if (m_reply->error() == QNetworkReply::NetworkError::NoError) + int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - emitFailed(jsonError.errorString()); - return; - } - if (!parseResult(doc)) - { - emitFailed(tr("paste.ee returned an error. Please consult the logs for more information")); - return; - } - } - // else the download failed - else - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); + emitFailed(tr("Network error: %1").arg(m_reply->errorString())); m_reply.reset(); return; } + else if (statusCode != 200) + { + QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); + qCritical() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; + m_reply.reset(); + return; + } + + m_pasteLink = QString::fromUtf8(data); emitSucceeded(); } - -bool PasteUpload::parseResult(QJsonDocument doc) -{ - auto object = doc.object(); - auto status = object.value("success").toBool(); - if (!status) - { - qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); - return false; - } - m_pasteLink = object.value("link").toString(); - m_pasteID = object.value("id").toString(); - qDebug() << m_pasteLink; - return true; -} - diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index 5514e058c..62b2dc361 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -8,37 +8,21 @@ class PasteUpload : public Task { Q_OBJECT public: - PasteUpload(QWidget *window, QString text, QString key = "public"); + PasteUpload(QWidget *window, QString text, QString url); virtual ~PasteUpload(); QString pasteLink() { return m_pasteLink; } - QString pasteID() - { - return m_pasteID; - } - int maxSize() - { - // 2MB for paste.ee - public - if(m_key == "public") - return 1024*1024*2; - // 12MB for paste.ee - with actual key - return 1024*1024*12; - } - bool validateText(); protected: virtual void executeTask(); private: - bool parseResult(QJsonDocument doc); - QString m_error; QWidget *m_window; - QString m_pasteID; QString m_pasteLink; - QString m_key; - QByteArray m_jsonContent; + QString m_uploadUrl; + QByteArray m_text; std::shared_ptr m_reply; public slots: diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index efb1a4dfc..9eb658e23 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -16,21 +16,8 @@ QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget) { ProgressDialog dialog(parentWidget); - auto APIKeySetting = APPLICATION->settings()->get("PasteEEAPIKey").toString(); - if(APIKeySetting == "multimc") - { - APIKeySetting = BuildConfig.PASTE_EE_KEY; - } - std::unique_ptr paste(new PasteUpload(parentWidget, text, APIKeySetting)); - - if (!paste->validateText()) - { - CustomMessageBox::selectable( - parentWidget, QObject::tr("Upload failed"), - QObject::tr("The log file is too big. You'll have to upload it manually."), - QMessageBox::Warning)->exec(); - return QString(); - } + auto pasteUrlSetting = APPLICATION->settings()->get("PastebinURL").toString(); + std::unique_ptr paste(new PasteUpload(parentWidget, text, pasteUrlSetting)); dialog.execWithTask(paste.get()); if (!paste->wasSuccessful()) diff --git a/launcher/ui/pages/global/PastePage.cpp b/launcher/ui/pages/global/PastePage.cpp index 495e9937f..0965da77f 100644 --- a/launcher/ui/pages/global/PastePage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "settings/SettingsObject.h" #include "tools/BaseProfiler.h" @@ -31,7 +32,6 @@ PastePage::PastePage(QWidget *parent) : { ui->setupUi(this); ui->tabWidget->tabBar()->hide();\ - connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PastePage::textEdited); loadSettings(); } @@ -43,23 +43,15 @@ PastePage::~PastePage() void PastePage::loadSettings() { auto s = APPLICATION->settings(); - QString pastebin = s->get("PastebinURL"); - int index = ui->urlChoices->findText(pastebin); - ui->urlChoices->setCurrentIndex(index); + QString pastebinURL = s->get("PastebinURL").toString(); + ui->urlChoices->setCurrentText(pastebinURL); } void PastePage::applySettings() { auto s = APPLICATION->settings(); - - QString pasteKeyToUse; - if (ui->customButton->isChecked()) - pasteKeyToUse = ui->customAPIkeyEdit->text(); - else - { - pasteKeyToUse = "multimc"; - } - s->set("PasteEEAPIKey", pasteKeyToUse); + QString pastebinURL = ui->urlChoices->currentText(); + s->set("PastebinURL", pastebinURL); } bool PastePage::apply() @@ -67,8 +59,3 @@ bool PastePage::apply() applySettings(); return true; } - -void PastePage::textEdited(const QString& text) -{ - ui->customButton->setChecked(true); -} diff --git a/launcher/ui/pages/global/PastePage.h b/launcher/ui/pages/global/PastePage.h index 3930d4ec1..d475dfd97 100644 --- a/launcher/ui/pages/global/PastePage.h +++ b/launcher/ui/pages/global/PastePage.h @@ -54,9 +54,6 @@ private: void loadSettings(); void applySettings(); -private slots: - void textEdited(const QString &text); - private: Ui::PastePage *ui; }; diff --git a/launcher/ui/pages/global/PastePage.ui b/launcher/ui/pages/global/PastePage.ui index 784ea3f42..fe3725406 100644 --- a/launcher/ui/pages/global/PastePage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -36,7 +36,7 @@ - Pastebin Site + Pastebin URL @@ -48,14 +48,20 @@ + + true + + + QComboBox::NoInsert + - 0x0.st + https://0x0.st - paste.polymc.org + https://paste.polymc.org @@ -63,7 +69,7 @@ - <html><head/><body><p>paste.polymc.org is a pastebin managed by PolyMC's lead maintainer. Something something trust</p></body></html> + <html><head/><body><p>Here you can choose from a predefined list, or input the URL of a different paste service, provided that it supports the same protocol as 0x0.st, that is POST a file to the URL and return a link in the response body.</p></body></html> Qt::RichText @@ -103,7 +109,4 @@ - - - diff --git a/launcher/ui/pages/instance/LogPage.ui b/launcher/ui/pages/instance/LogPage.ui index ccfc15517..31bb368c8 100644 --- a/launcher/ui/pages/instance/LogPage.ui +++ b/launcher/ui/pages/instance/LogPage.ui @@ -100,7 +100,7 @@ - Upload the log to paste.ee - it will stay online for a month + Upload the log to the paste service configured in preferences Upload diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 56ff3b621..77f3e6477 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -84,7 +84,7 @@ - Upload the log to paste.ee - it will stay online for a month + Upload the log to the paste service configured in preferences. Upload From 7022d3d40129f5e97a70dbdda63e30d341d9e32a Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 19 Jan 2022 18:59:24 +0100 Subject: [PATCH 28/45] fix(readme): header color should follow GH theme As browser color scheme preference and GitHub theme can be different from each other, let's follow GitHub theme instead of browser preference. --- README.md | 3 ++- program_info/polymc-header-black.svg | 31 ++++++++++++++++++++++++++++ program_info/polymc-header.svg | 9 +------- 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 program_info/polymc-header-black.svg diff --git a/README.md b/README.md index 4b5d85f9c..778a2d960 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@

- PolyMC logo + PolyMC logo + PolyMC logo


diff --git a/program_info/polymc-header-black.svg b/program_info/polymc-header-black.svg new file mode 100644 index 000000000..34cda4abe --- /dev/null +++ b/program_info/polymc-header-black.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/program_info/polymc-header.svg b/program_info/polymc-header.svg index fb91a54b8..a896787f6 100644 --- a/program_info/polymc-header.svg +++ b/program_info/polymc-header.svg @@ -1,13 +1,6 @@ - @@ -27,7 +20,7 @@ - + From caeab926bcc8563996f7826a060966b55580376d Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 19 Jan 2022 14:59:09 +0000 Subject: [PATCH 29/45] PasteUpload: Trim whitespace from response body --- launcher/net/PasteUpload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 6c5aa2214..4efcefdd3 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -68,6 +68,6 @@ void PasteUpload::downloadFinished() return; } - m_pasteLink = QString::fromUtf8(data); + m_pasteLink = QString::fromUtf8(data).trimmed(); emitSucceeded(); } From dd76fb0ec7ee997f85c2b00d1c3f550bdebbcce6 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 19 Jan 2022 19:49:43 +0100 Subject: [PATCH 30/45] feat(MSALogin): add open page & copy code button Closes #55 --- launcher/ui/dialogs/MSALoginDialog.cpp | 12 ++++++++++++ launcher/ui/dialogs/MSALoginDialog.ui | 27 ++++++++++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index f46aa3b9a..174ad46c8 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -16,15 +16,19 @@ #include "MSALoginDialog.h" #include "ui_MSALoginDialog.h" +#include "DesktopServices.h" #include "minecraft/auth/AccountTask.h" #include #include +#include +#include MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MSALoginDialog) { ui->setupUi(this); ui->progressBar->setVisible(false); + ui->actionButton->setVisible(false); // ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); @@ -81,10 +85,17 @@ void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& QString urlString = uri.toString(); QString linkString = QString("%2").arg(urlString, urlString); ui->label->setText(tr("

Please open up %1 in a browser and put in the code %2 to proceed with login.

").arg(linkString, code)); + ui->actionButton->setVisible(true); + connect(ui->actionButton, &QPushButton::clicked, [=]() { + DesktopServices::openUrl(uri); + QClipboard* cb = QApplication::clipboard(); + cb->setText(code); + }); } void MSALoginDialog::hideVerificationUriAndCode() { m_externalLoginTimer.stop(); + ui->actionButton->setVisible(false); } void MSALoginDialog::setUserInputsEnabled(bool enable) @@ -110,6 +121,7 @@ void MSALoginDialog::onTaskFailed(const QString &reason) // Re-enable user-interaction setUserInputsEnabled(true); ui->progressBar->setVisible(false); + ui->actionButton->setVisible(false); } void MSALoginDialog::onTaskSucceeded() diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui index 78cbfb269..c18d01a16 100644 --- a/launcher/ui/dialogs/MSALoginDialog.ui +++ b/launcher/ui/dialogs/MSALoginDialog.ui @@ -49,14 +49,25 @@ aaaaa
- - - Qt::Horizontal - - - QDialogButtonBox::Cancel - - + + + + + Open page and copy code + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + +
From 3da69d8e539dea088f18fb575f7b6ef0d0c75b07 Mon Sep 17 00:00:00 2001 From: Daniel Huang <24739403+danielhuang@users.noreply.github.com> Date: Sat, 22 Jan 2022 14:13:21 -0500 Subject: [PATCH 31/45] Update org.polymc.PolyMC.desktop.in --- program_info/org.polymc.PolyMC.desktop.in | 1 + 1 file changed, 1 insertion(+) diff --git a/program_info/org.polymc.PolyMC.desktop.in b/program_info/org.polymc.PolyMC.desktop.in index 8bbdc505c..5d982b387 100644 --- a/program_info/org.polymc.PolyMC.desktop.in +++ b/program_info/org.polymc.PolyMC.desktop.in @@ -10,3 +10,4 @@ Icon=org.polymc.PolyMC PrefersNonDefaultGPU=true Categories=Game; Keywords=game;minecraft;launcher; +StartupWMClass=PolyMC From af20b5ee0e09d5c9ec36d54c80ca688c7c67012d Mon Sep 17 00:00:00 2001 From: swirl Date: Sun, 23 Jan 2022 12:54:58 -0500 Subject: [PATCH 32/45] support paste.polymc.org --- launcher/net/PasteUpload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 4efcefdd3..52b82a0e1 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -59,7 +59,7 @@ void PasteUpload::downloadFinished() m_reply.reset(); return; } - else if (statusCode != 200) + else if (statusCode != 200 && statusCode != 201) { QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); From 70c04745ee49a127f43a0947e0a0e0d9b2f8eb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 11:43:19 +0100 Subject: [PATCH 33/45] NOISSUE add some logging to profile fetching failures --- launcher/minecraft/auth/steps/MinecraftProfileStep.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index 9fef99b0d..dc3df44f4 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -56,6 +56,11 @@ void MinecraftProfileStep::onRequestDone( return; } if (error != QNetworkReply::NoError) { + qWarning() << "Error getting profile:"; + qWarning() << " HTTP Status: " << requestor->httpStatus_; + qWarning() << " Internal error no.: " << error; + qWarning() << " Error string: " << requestor->errorString_; + emit finished( AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed.") From ddfed7bb878a8e5caa491b67a85d39f2917a0056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 12:05:40 +0100 Subject: [PATCH 34/45] NOISSUE in java checker, ignore invalid lines altogether Declaring them as errors is just causing problems because Java randomly prints garbage to STDOUT now. --- launcher/java/JavaChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 4557784b0..35ddc35c2 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -111,7 +111,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto parts = line.split('=', QString::SkipEmptyParts); if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) { - success = false; + continue; } else { From 54e3438e372c1e21d9fc39e8b08cf5c50219dd6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 12:46:20 +0100 Subject: [PATCH 35/45] NOISSUE correctly set http status code in auth reply --- launcher/minecraft/auth/AuthRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp index 459d2354e..feface80f 100644 --- a/launcher/minecraft/auth/AuthRequest.cpp +++ b/launcher/minecraft/auth/AuthRequest.cpp @@ -44,7 +44,7 @@ void AuthRequest::onRequestFinished() { if (reply_ != qobject_cast(sender())) { return; } - httpStatus_ = 200; + httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); finish(); } From 8804b035b2d6daa1a1a4e04696315d428e782661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 12:51:42 +0100 Subject: [PATCH 36/45] NOISSUE log server response when failing to fetch profile --- launcher/minecraft/auth/steps/MinecraftProfileStep.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index dc3df44f4..add916599 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -61,6 +61,9 @@ void MinecraftProfileStep::onRequestDone( qWarning() << " Internal error no.: " << error; qWarning() << " Error string: " << requestor->errorString_; + qWarning() << " Response:"; + qWarning() << QString::fromUtf8(data); + emit finished( AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed.") From 0235eb5c286413332dcc2f7af8171bd87b61c3c5 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 22 Jan 2022 21:58:32 +0100 Subject: [PATCH 37/45] Fix error message The code is trying to get a string from a json object, and if that fails it should log "is not a string", not "is not a timestamp". --- launcher/minecraft/auth/Parsers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index ed31e9346..2dd36562f 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -94,7 +94,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na return false; } if(!getString(obj.value("Token"), output.token)) { - qWarning() << "User Token is not a timestamp"; + qWarning() << "User Token is not a string"; return false; } auto arrayVal = obj.value("DisplayClaims").toObject().value("xui"); From 4edd2cff9f0fbb0069cea009d1136275b4beaf11 Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Mon, 24 Jan 2022 23:15:54 +0100 Subject: [PATCH 38/45] remove explicit dependencies, correct dependencies to work on OpenSuse --- packages/rpm/polymc.spec | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/rpm/polymc.spec b/packages/rpm/polymc.spec index f5a6fe07c..259dc5263 100644 --- a/packages/rpm/polymc.spec +++ b/packages/rpm/polymc.spec @@ -6,7 +6,7 @@ Name: polymc Version: 1.0.5 -Release: 1%{?dist} +Release: 2%{?dist} Summary: Minecraft launcher with ability to manage multiple instances # @@ -61,23 +61,18 @@ Source0: https://github.com/PolyMC/PolyMC/archive/%{version}/%{name}-%{ve Source1: https://github.com/MultiMC/libnbtplusplus/archive/%{libnbtplusplus_commit}/libnbtplusplus-%{libnbtplusplus_shortcommit}.tar.gz Source2: https://github.com/PolyMC/quazip/archive/%{quazip_commit}/quazip-%{quazip_shortcommit}.tar.gz -BuildRequires: cmake3 +BuildRequires: cmake BuildRequires: desktop-file-utils BuildRequires: gcc-c++ -# Fix warning: Could not complete Guile gdb module initialization from: -# /usr/share/gdb/guile/gdb/boot.scm -BuildRequires: gdb-headless - BuildRequires: java-devel -BuildRequires: pkgconfig(gl) -BuildRequires: pkgconfig(Qt5) -BuildRequires: pkgconfig(zlib) +BuildRequires: %{?suse_version:lib}qt5-qtbase-devel +BuildRequires: zlib-devel -Requires: java-headless -Requires: pkgconfig(gl) -Requires: pkgconfig(Qt5) -Requires: pkgconfig(zlib) +# Minecraft < 1.17 +Recommends: java-1.8.0-openjdk-headless +# Minecraft >= 1.17 +Recommends: java-17-openjdk-headless %description PolyMC is a free, open source launcher for Minecraft. It allows you to have @@ -105,7 +100,6 @@ mv -f libraries/libnbtplusplus-%{libnbtplusplus_commit} libraries/libnbtplusplus %cmake_build - %install %cmake_install @@ -115,8 +109,11 @@ echo "%{_libdir}/%{name}" > "%{buildroot}%{_sysconfdir}/ld.so.conf.d/%{name}-%{_ %check +# skip tests on systems that aren't officially supported +%if ! 0%{?suse_version} %ctest desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.desktop +%endif %files @@ -132,6 +129,9 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.des %changelog +* Mon Jan 24 2022 Jan Drögehoff - 1.0.5-2 +- remove explicit dependencies, correct dependencies to work on OpenSuse + * Sun Jan 09 2022 Jan Drögehoff - 1.0.5-1 - Update to 1.0.5 From 0eff21a4f1ee97e4b0a484c608eac9617897446d Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 26 Jan 2022 00:31:08 +0000 Subject: [PATCH 39/45] Validate Pastebin URL with regex --- launcher/ui/pages/global/PastePage.cpp | 2 ++ launcher/ui/pages/global/PastePage.ui | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/global/PastePage.cpp b/launcher/ui/pages/global/PastePage.cpp index 0965da77f..7c69e1a4e 100644 --- a/launcher/ui/pages/global/PastePage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -30,7 +30,9 @@ PastePage::PastePage(QWidget *parent) : QWidget(parent), ui(new Ui::PastePage) { + static QRegularExpression validUrlRegExp("https?://.+"); ui->setupUi(this); + ui->urlChoices->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->urlChoices)); ui->tabWidget->tabBar()->hide();\ loadSettings(); } diff --git a/launcher/ui/pages/global/PastePage.ui b/launcher/ui/pages/global/PastePage.ui index fe3725406..2d13a765d 100644 --- a/launcher/ui/pages/global/PastePage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -38,7 +38,7 @@ Pastebin URL - + @@ -46,6 +46,21 @@ + + + + + 10 + + + + <html><head/><body><p>Note: only input that starts with <span style=" font-weight:600;">http://</span> or <span style=" font-weight:600;">https://</span> will be accepted.</p></body></html> + + + false + + + @@ -69,7 +84,7 @@ - <html><head/><body><p>Here you can choose from a predefined list, or input the URL of a different paste service, provided that it supports the same protocol as 0x0.st, that is POST a file to the URL and return a link in the response body.</p></body></html> + <html><head/><body><p>Here you can choose from a predefined list of paste services, or input the URL of a different paste service of your choice, provided it supports the same protocol as 0x0.st, that is POST a file parameter to the URL and return a link in the response body.</p></body></html> Qt::RichText From ec1e27031a0b1fb9760f650ad8415d9e14e3f9f3 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 26 Jan 2022 23:51:29 -0500 Subject: [PATCH 40/45] Fix Freedesktop icons This fixes #51. The desktop file is now exactly the same as the window class, which is also now corrected to org.polymc.polymc. The file capitalization is also consistent with other Freedesktop files as well. --- packages/nix/polymc/default.nix | 2 +- packages/rpm/polymc.spec | 4 ++-- program_info/CMakeLists.txt | 6 +++--- ...olymc.PolyMC.desktop.in => org.polymc.polymc.desktop.in} | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) rename program_info/{org.polymc.PolyMC.desktop.in => org.polymc.polymc.desktop.in} (93%) diff --git a/packages/nix/polymc/default.nix b/packages/nix/polymc/default.nix index 5da00ff8b..b6bf6c5ee 100644 --- a/packages/nix/polymc/default.nix +++ b/packages/nix/polymc/default.nix @@ -88,7 +88,7 @@ mkDerivation rec { postInstall = '' install -Dm644 ../launcher/resources/multimc/scalable/launcher.svg $out/share/pixmaps/polymc.svg - install -Dm644 ${desktopItem}/share/applications/polymc.desktop $out/share/applications/org.polymc.PolyMC.desktop + install -Dm644 ${desktopItem}/share/applications/polymc.desktop $out/share/applications/org.polymc.polymc.desktop # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 wrapProgram $out/bin/polymc \ diff --git a/packages/rpm/polymc.spec b/packages/rpm/polymc.spec index 259dc5263..f52b62613 100644 --- a/packages/rpm/polymc.spec +++ b/packages/rpm/polymc.spec @@ -112,7 +112,7 @@ echo "%{_libdir}/%{name}" > "%{buildroot}%{_sysconfdir}/ld.so.conf.d/%{name}-%{_ # skip tests on systems that aren't officially supported %if ! 0%{?suse_version} %ctest -desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.desktop +desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.polymc.desktop %endif @@ -123,7 +123,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.des %{_libdir}/%{name}/* %{_datadir}/%{name}/* %{_datadir}/metainfo/org.polymc.PolyMC.metainfo.xml -%{_datadir}/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg +%{_datadir}/icons/hicolor/scalable/apps/org.polymc.polymC.svg %{_datadir}/applications/org.polymc.PolyMC.desktop %config %{_sysconfdir}/ld.so.conf.d/* diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 77b971fcd..26369fe5c 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -1,19 +1,19 @@ set(Launcher_CommonName "PolyMC") set(Launcher_Copyright "PolyMC Contributors" PARENT_SCOPE) -set(Launcher_Domain "github.com/PolyMC" PARENT_SCOPE) +set(Launcher_Domain "polymc.org" PARENT_SCOPE) set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_RELEASE_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "polymc.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PolyMC/PolyMC" PARENT_SCOPE) -set(Launcher_Desktop "program_info/org.polymc.PolyMC.desktop" PARENT_SCOPE) +set(Launcher_Desktop "program_info/org.polymc.polymc.desktop" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" PARENT_SCOPE) set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE) set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) -configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop) +configure_file(org.polymc.polymc.desktop.in org.polymc.polymc.desktop) configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml) diff --git a/program_info/org.polymc.PolyMC.desktop.in b/program_info/org.polymc.polymc.desktop.in similarity index 93% rename from program_info/org.polymc.PolyMC.desktop.in rename to program_info/org.polymc.polymc.desktop.in index 5d982b387..8bbdc505c 100644 --- a/program_info/org.polymc.PolyMC.desktop.in +++ b/program_info/org.polymc.polymc.desktop.in @@ -10,4 +10,3 @@ Icon=org.polymc.PolyMC PrefersNonDefaultGPU=true Categories=Game; Keywords=game;minecraft;launcher; -StartupWMClass=PolyMC From cd5faee7d746a132bd690f9e3a2b35dcc5af91c6 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 26 Jan 2022 23:51:29 -0500 Subject: [PATCH 41/45] Fix RPM spec referencing old desktop file --- packages/rpm/polymc.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rpm/polymc.spec b/packages/rpm/polymc.spec index f52b62613..0b659ed5a 100644 --- a/packages/rpm/polymc.spec +++ b/packages/rpm/polymc.spec @@ -123,8 +123,8 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.polymc.des %{_libdir}/%{name}/* %{_datadir}/%{name}/* %{_datadir}/metainfo/org.polymc.PolyMC.metainfo.xml -%{_datadir}/icons/hicolor/scalable/apps/org.polymc.polymC.svg -%{_datadir}/applications/org.polymc.PolyMC.desktop +%{_datadir}/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg +%{_datadir}/applications/org.polymc.polymc.desktop %config %{_sysconfdir}/ld.so.conf.d/* From 1c982b01824d1b3892cd91208fe295d6816f352e Mon Sep 17 00:00:00 2001 From: redstrate <54911369+redstrate@users.noreply.github.com> Date: Thu, 27 Jan 2022 13:17:26 +0000 Subject: [PATCH 42/45] Fix RPM build instructions --- BUILD.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index 1c06febb8..d14f29411 100644 --- a/BUILD.md +++ b/BUILD.md @@ -47,16 +47,25 @@ If everything works correctly, the .deb will be next to the build script, in `Po ### Building a .rpm -You don't need to install the build dependencies, as the script will use `dnf` to install them for you. +Build dependencies are automatically installed using `dnf`, but you do need the `rpmdevtools` package (on Fedora) +in order to fetch sources and setup your tree. ``` +cd ~ +# setup your ~/rpmbuild directory, required for rpmbuild to work. +rpmdev-setuptree +# note: submodules are not needed here, as the spec file will download the tarball instead git clone https://github.com/PolyMC/PolyMC.git -cd packages/rpm -sudo dnf builddep ./polymec.spec -rpmbuild -bb ./polymec.spec +cd PolyMC/packages/rpm +# install build dependencies +sudo dnf builddep polymc.spec +# download build sources +spectool -g -R polymc.spec +# now build! +rpmbuild -bb polymc.spec ``` -The path to the rpm packages will be printed at the end of building +The path to the rpm packages will be printed when the build is complete. ### Building from command line You need a source folder, a build folder and an install folder. From ad6e3a08683c388a1949e4fb362f85065b77c75b Mon Sep 17 00:00:00 2001 From: swirl Date: Thu, 27 Jan 2022 16:58:28 -0500 Subject: [PATCH 43/45] Fix meta --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91119b2f0..c5006b4d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.") # The metadata server -set(Launcher_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") +set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") # Imgur API Client ID set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application") From 361ce7818ec8891e9a35bdfac4cdea77a0b6a949 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 27 Jan 2022 22:58:38 +0100 Subject: [PATCH 44/45] refactor: remove news feed Closes #63 --- CMakeLists.txt | 3 - launcher/CMakeLists.txt | 9 --- launcher/news/NewsChecker.cpp | 132 ---------------------------------- launcher/news/NewsChecker.h | 105 --------------------------- launcher/news/NewsEntry.cpp | 77 -------------------- launcher/news/NewsEntry.h | 65 ----------------- launcher/ui/MainWindow.cpp | 89 ----------------------- launcher/ui/MainWindow.h | 9 --- 8 files changed, 489 deletions(-) delete mode 100644 launcher/news/NewsChecker.cpp delete mode 100644 launcher/news/NewsChecker.h delete mode 100644 launcher/news/NewsEntry.cpp delete mode 100644 launcher/news/NewsEntry.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af0aa712..b7fb557a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,9 +45,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") ##################################### Set Application options ##################################### -######## Set URLs ######## -set(Launcher_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch Launcher's news RSS feed from.") - ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MINOR 0) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b5c52afa2..c5a2b835e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -177,15 +177,6 @@ set(NOTIFICATIONS_SOURCES notifications/NotificationChecker.cpp ) -# Backend for the news bar... there's usually no news. -set(NEWS_SOURCES - # News System - news/NewsChecker.h - news/NewsChecker.cpp - news/NewsEntry.h - news/NewsEntry.cpp -) - # Icon interface set(ICONS_SOURCES # Icons System and related code diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp deleted file mode 100644 index 4f4359b89..000000000 --- a/launcher/news/NewsChecker.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NewsChecker.h" - -#include -#include - -#include - -NewsChecker::NewsChecker(shared_qobject_ptr network, const QString& feedUrl) -{ - m_network = network; - m_feedUrl = feedUrl; -} - -void NewsChecker::reloadNews() -{ - // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done. - if (isLoadingNews()) - { - qDebug() << "Ignored request to reload news. Currently reloading already."; - return; - } - - qDebug() << "Reloading news."; - - NetJob* job = new NetJob("News RSS Feed", m_network); - job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); - QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); - m_newsNetJob.reset(job); - job->start(); -} - -void NewsChecker::rssDownloadFinished() -{ - // Parse the XML file and process the RSS feed entries. - qDebug() << "Finished loading RSS feed."; - - m_newsNetJob.reset(); - QDomDocument doc; - { - // Stuff to store error info in. - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - // Parse the XML. - if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) - { - QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); - fail(fullErrorMsg); - newsData.clear(); - return; - } - newsData.clear(); - } - - // If the parsing succeeded, read it. - QDomNodeList items = doc.elementsByTagName("item"); - m_newsEntries.clear(); - for (int i = 0; i < items.length(); i++) - { - QDomElement element = items.at(i).toElement(); - NewsEntryPtr entry; - entry.reset(new NewsEntry()); - QString errorMsg = "An unknown error occurred."; - if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) - { - qDebug() << "Loaded news entry" << entry->title; - m_newsEntries.append(entry); - } - else - { - qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; - } - } - - succeed(); -} - -void NewsChecker::rssDownloadFailed(QString reason) -{ - // Set an error message and fail. - fail(tr("Failed to load news RSS feed:\n%1").arg(reason)); -} - - -QList NewsChecker::getNewsEntries() const -{ - return m_newsEntries; -} - -bool NewsChecker::isLoadingNews() const -{ - return m_newsNetJob.get() != nullptr; -} - -QString NewsChecker::getLastLoadErrorMsg() const -{ - return m_lastLoadError; -} - -void NewsChecker::succeed() -{ - m_lastLoadError = ""; - qDebug() << "News loading succeeded."; - m_newsNetJob.reset(); - emit newsLoaded(); -} - -void NewsChecker::fail(const QString& errorMsg) -{ - m_lastLoadError = errorMsg; - qDebug() << "Failed to load news:" << errorMsg; - m_newsNetJob.reset(); - emit newsLoadingFailed(errorMsg); -} - diff --git a/launcher/news/NewsChecker.h b/launcher/news/NewsChecker.h deleted file mode 100644 index 8467a5412..000000000 --- a/launcher/news/NewsChecker.h +++ /dev/null @@ -1,105 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include - -#include "NewsEntry.h" - -class NewsChecker : public QObject -{ - Q_OBJECT -public: - /*! - * Constructs a news reader to read from the given RSS feed URL. - */ - NewsChecker(shared_qobject_ptr network, const QString& feedUrl); - - /*! - * Returns the error message for the last time the news was loaded. - * Empty string if the last load was successful. - */ - QString getLastLoadErrorMsg() const; - - /*! - * Returns true if the news has been loaded successfully. - */ - bool isNewsLoaded() const; - - //! True if the news is currently loading. If true, reloadNews() will do nothing. - bool isLoadingNews() const; - - /*! - * Returns a list of news entries. - */ - QList getNewsEntries() const; - - /*! - * Reloads the news from the website's RSS feed. - * If the news is already loading, this does nothing. - */ - void Q_SLOT reloadNews(); - -signals: - /*! - * Signal fired after the news has finished loading. - */ - void newsLoaded(); - - /*! - * Signal fired after the news fails to load. - */ - void newsLoadingFailed(QString errorMsg); - -protected slots: - void rssDownloadFinished(); - void rssDownloadFailed(QString reason); - -protected: /* data */ - //! The URL for the RSS feed to fetch. - QString m_feedUrl; - - //! List of news entries. - QList m_newsEntries; - - //! The network job to use to load the news. - NetJob::Ptr m_newsNetJob; - - //! True if news has been loaded. - bool m_loadedNews; - - QByteArray newsData; - - /*! - * Gets the error message that was given last time the news was loaded. - * If the last news load succeeded, this will be an empty string. - */ - QString m_lastLoadError; - - shared_qobject_ptr m_network; - -protected slots: - /// Emits newsLoaded() and sets m_lastLoadError to empty string. - void succeed(); - - /// Emits newsLoadingFailed() and sets m_lastLoadError to the given message. - void fail(const QString& errorMsg); -}; - diff --git a/launcher/news/NewsEntry.cpp b/launcher/news/NewsEntry.cpp deleted file mode 100644 index 7eff657b0..000000000 --- a/launcher/news/NewsEntry.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NewsEntry.h" - -#include -#include - -NewsEntry::NewsEntry(QObject* parent) : - QObject(parent) -{ - this->title = tr("Untitled"); - this->content = tr("No content."); - this->link = ""; - this->author = tr("Unknown Author"); - this->pubDate = QDateTime::currentDateTime(); -} - -NewsEntry::NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent) : - QObject(parent) -{ - this->title = title; - this->content = content; - this->link = link; - this->author = author; - this->pubDate = pubDate; -} - -/*! - * Gets the text content of the given child element as a QVariant. - */ -inline QString childValue(const QDomElement& element, const QString& childName, QString defaultVal="") -{ - QDomNodeList nodes = element.elementsByTagName(childName); - if (nodes.count() > 0) - { - QDomElement element = nodes.at(0).toElement(); - return element.text(); - } - else - { - return defaultVal; - } -} - -bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg) -{ - QString title = childValue(element, "title", tr("Untitled")); - QString content = childValue(element, "description", tr("No content.")); - QString link = childValue(element, "link"); - QString author = childValue(element, "dc:creator", tr("Unknown Author")); - QString pubDateStr = childValue(element, "pubDate"); - - // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same. - QString dateFormat("ddd, dd MMM yyyy hh:mm:ss"); - QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat); - - entry->title = title; - entry->content = content; - entry->link = link; - entry->author = author; - entry->pubDate = pubDate; - return true; -} - diff --git a/launcher/news/NewsEntry.h b/launcher/news/NewsEntry.h deleted file mode 100644 index 0dbc70a5a..000000000 --- a/launcher/news/NewsEntry.h +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -#include - -class NewsEntry : public QObject -{ - Q_OBJECT - -public: - /*! - * Constructs an empty news entry. - */ - explicit NewsEntry(QObject* parent=0); - - /*! - * Constructs a new news entry. - * Note that content may contain HTML. - */ - NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0); - - /*! - * Attempts to load information from the given XML element into the given news entry pointer. - * If this fails, the function will return false and store an error message in the errorMsg pointer. - */ - static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0); - - - //! The post title. - QString title; - - //! The post's content. May contain HTML. - QString content; - - //! URL to the post. - QString link; - - //! The post's author. - QString author; - - //! The date and time that this post was published. - QDateTime pubDate; -}; - -typedef std::shared_ptr NewsEntryPtr; - diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 3dcc8ee9b..202924ff9 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -58,7 +58,6 @@ #include #include #include -#include #include #include #include @@ -201,7 +200,6 @@ public: //TranslatedAction actionRefresh; TranslatedAction actionCheckUpdate; TranslatedAction actionSettings; - TranslatedAction actionMoreNews; TranslatedAction actionManageAccounts; TranslatedAction actionLaunchInstance; TranslatedAction actionRenameInstance; @@ -246,7 +244,6 @@ public: TranslatedToolbar mainToolBar; TranslatedToolbar instanceToolBar; - TranslatedToolbar newsToolBar; QVector all_toolbars; bool m_kill = false; @@ -429,29 +426,6 @@ public: MainWindow->setStatusBar(statusBar); } - void createNewsToolbar(QMainWindow *MainWindow) - { - newsToolBar = TranslatedToolbar(MainWindow); - newsToolBar->setObjectName(QStringLiteral("newsToolBar")); - newsToolBar->setMovable(false); - newsToolBar->setAllowedAreas(Qt::BottomToolBarArea); - newsToolBar->setIconSize(QSize(16, 16)); - newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - newsToolBar->setFloatable(false); - newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar")); - - actionMoreNews = TranslatedAction(MainWindow); - actionMoreNews->setObjectName(QStringLiteral("actionMoreNews")); - actionMoreNews->setIcon(APPLICATION->getThemedIcon("news")); - actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news...")); - actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the development blog to read more news about %1.")); - all_actions.append(&actionMoreNews); - newsToolBar->addAction(actionMoreNews); - - all_toolbars.append(&newsToolBar); - MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); - } - void createInstanceToolbar(QMainWindow *MainWindow) { instanceToolBar = TranslatedToolbar(MainWindow); @@ -636,7 +610,6 @@ public: MainWindow->setCentralWidget(centralWidget); createStatusBar(MainWindow); - createNewsToolbar(MainWindow); createInstanceToolbar(MainWindow); retranslateUi(MainWindow); @@ -691,20 +664,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow connect(secretEventFilter, &KonamiCode::triggered, this, &MainWindow::konamiTriggered); } - // Add the news label to the news toolbar. - { - m_newsChecker.reset(new NewsChecker(APPLICATION->network(), BuildConfig.NEWS_RSS_URL)); - newsLabel = new QToolButton(); - newsLabel->setIcon(APPLICATION->getThemedIcon("news")); - newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - newsLabel->setFocusPolicy(Qt::NoFocus); - ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); - QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); - QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); - updateNewsLabel(); - } - // Create the instance list widget { view = new InstanceView(ui->centralWidget); @@ -809,13 +768,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow // TODO: refresh accounts here? // auto accounts = APPLICATION->accounts(); - // load the news - { - m_newsChecker->reloadNews(); - updateNewsLabel(); - } - - if(BuildConfig.UPDATER_ENABLED) { bool updatesAllowed = APPLICATION->updatesAreAllowed(); @@ -1189,29 +1141,6 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev) return QMainWindow::eventFilter(obj, ev); } -void MainWindow::updateNewsLabel() -{ - if (m_newsChecker->isLoadingNews()) - { - newsLabel->setText(tr("Loading news...")); - newsLabel->setEnabled(false); - } - else - { - QList entries = m_newsChecker->getNewsEntries(); - if (entries.length() > 0) - { - newsLabel->setText(entries[0]->title); - newsLabel->setEnabled(true); - } - else - { - newsLabel->setText(tr("No news available.")); - newsLabel->setEnabled(false); - } - } -} - void MainWindow::updateAvailable(GoUpdate::Status status) { if(!APPLICATION->updatesAreAllowed()) @@ -1685,24 +1614,6 @@ void MainWindow::on_actionReportBug_triggered() DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL)); } -void MainWindow::on_actionMoreNews_triggered() -{ - DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); -} - -void MainWindow::newsButtonClicked() -{ - QList entries = m_newsChecker->getNewsEntries(); - if (entries.count() > 0) - { - DesktopServices::openUrl(QUrl(entries[0]->link)); - } - else - { - DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); - } -} - void MainWindow::on_actionAbout_triggered() { AboutDialog dialog(this); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index f6940ab05..38d925a99 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -27,7 +27,6 @@ #include "updater/GoUpdate.h" class LaunchController; -class NewsChecker; class NotificationChecker; class QToolButton; class InstanceProxyModel; @@ -109,10 +108,6 @@ private slots: void on_actionReportBug_triggered(); - void on_actionMoreNews_triggered(); - - void newsButtonClicked(); - void on_actionLaunchInstance_triggered(); void on_actionLaunchInstanceOffline_triggered(); @@ -174,8 +169,6 @@ private slots: void repopulateAccountsMenu(); - void updateNewsLabel(); - /*! * Runs the DownloadTask and installs updates. */ @@ -205,14 +198,12 @@ private: // these are managed by Qt's memory management model! InstanceView *view = nullptr; InstanceProxyModel *proxymodel = nullptr; - QToolButton *newsLabel = nullptr; QLabel *m_statusLeft = nullptr; QLabel *m_statusCenter = nullptr; QMenu *accountMenu = nullptr; QToolButton *accountMenuButton = nullptr; KonamiCode * secretEventFilter = nullptr; - unique_qobject_ptr m_newsChecker; unique_qobject_ptr m_notificationChecker; InstancePtr m_selectedInstance; From 5ac528f141f2c55fb09c6d59c5fd71d5f83c46ce Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Thu, 27 Jan 2022 18:37:57 -0500 Subject: [PATCH 45/45] Fix icons changing when exiting the settings window --- launcher/ui/pages/global/LauncherPage.cpp | 43 +++++++++++------------ launcher/ui/pages/global/LauncherPage.ui | 10 +++--- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 81ecd58f3..0ffe80500 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -246,33 +246,32 @@ void LauncherPage::applySettings() //FIXME: make generic switch (ui->themeComboBox->currentIndex()) { - case 1: + case 0: s->set("IconTheme", "pe_dark"); break; - case 2: + case 1: s->set("IconTheme", "pe_light"); break; - case 3: + case 2: s->set("IconTheme", "pe_blue"); break; - case 4: - s->set("IconTheme", "multimc"); + case 3: + s->set("IconTheme", "pe_colored"); break; - case 5: + case 4: s->set("IconTheme", "OSX"); break; - case 6: + case 5: s->set("IconTheme", "iOS"); break; - case 7: + case 6: s->set("IconTheme", "flat"); break; - case 8: + case 7: s->set("IconTheme", "custom"); break; - case 0: - default: - s->set("IconTheme", "pe_colored"); + case 8: + s->set("IconTheme", "multimc"); break; } @@ -327,29 +326,33 @@ void LauncherPage::loadSettings() auto theme = s->get("IconTheme").toString(); if (theme == "pe_dark") { - ui->themeComboBox->setCurrentIndex(1); + ui->themeComboBox->setCurrentIndex(0); } else if (theme == "pe_light") { - ui->themeComboBox->setCurrentIndex(2); + ui->themeComboBox->setCurrentIndex(1); } else if (theme == "pe_blue") { - ui->themeComboBox->setCurrentIndex(3); + ui->themeComboBox->setCurrentIndex(2); } else if (theme == "pe_colored") { - ui->themeComboBox->setCurrentIndex(4); + ui->themeComboBox->setCurrentIndex(3); } else if (theme == "OSX") { - ui->themeComboBox->setCurrentIndex(5); + ui->themeComboBox->setCurrentIndex(4); } else if (theme == "iOS") { - ui->themeComboBox->setCurrentIndex(6); + ui->themeComboBox->setCurrentIndex(5); } else if (theme == "flat") + { + ui->themeComboBox->setCurrentIndex(6); + } + else if (theme == "multimc") { ui->themeComboBox->setCurrentIndex(7); } @@ -357,10 +360,6 @@ void LauncherPage::loadSettings() { ui->themeComboBox->setCurrentIndex(8); } - else - { - ui->themeComboBox->setCurrentIndex(0); - } { auto currentTheme = s->get("ApplicationTheme").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 2b3729bcf..47fed8730 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -262,11 +262,6 @@ Qt::StrongFocus - - - Default - - Simple (Dark Icons) @@ -307,6 +302,11 @@ Custom + + + MultiMC + +