Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix JSON crash, run as root properly on macOS, update Qt #70

Merged
merged 4 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
uses: jurplel/install-qt-action@v3
if: ${{ matrix.useQtAction }}
with:
version: '6.6.2'
version: '6.7.2'
arch: 'win64_msvc2019_64'
archives: 'qtbase qttools opengl32sw d3dcompiler_47'
extra: '--external 7z'
Expand Down
13 changes: 7 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ string(TIMESTAMP currentYear "%Y")
set(copyrightYears "2016-${currentYear}")
set_target_properties(LibreELEC.USB-SD.Creator PROPERTIES
# TODO: use Qt's template (<QTDIR>/lib/cmake/Qt6/macos/Info.plist.app.in) once we support dark mode properly
MACOSX_BUNDLE_INFO_PLIST "dmg_osx/Info.plist.in"
MACOSX_BUNDLE_INFO_PLIST "macos/Info.plist.in"
MACOSX_BUNDLE_BUNDLE_NAME "${projectDisplayName}"
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
MACOSX_BUNDLE_COPYRIGHT "LibreELEC © ${copyrightYears}"
Expand Down Expand Up @@ -161,9 +161,10 @@ elseif(APPLE)
deviceenumerator_unix.cpp deviceenumerator_unix.h
diskwriter_unix.cpp diskwriter_unix.h
privileges_unix.cpp privileges_unix.h
macos/askpass.cpp macos/askpass.h
)

install(FILES "dmg_osx/icon.icns"
install(FILES "macos/dmg_osx/icon.icns"
DESTINATION "$<TARGET_BUNDLE_CONTENT_DIR:LibreELEC.USB-SD.Creator>/Resources"
)

Expand All @@ -177,7 +178,7 @@ elseif(APPLE)
endforeach()

# codesign
execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/dmg_osx/codesign.sh\"
execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/macos/codesign.sh\"
WORKING_DIRECTORY \"\\\${bundleContentsDir}\"
)
")
Expand Down Expand Up @@ -231,15 +232,15 @@ elseif(APPLE)

set(notarizeScript "${CMAKE_BINARY_DIR}/notarize.cmake")
file(WRITE "${notarizeScript}"
"execute_process(COMMAND sh -c \"${CMAKE_SOURCE_DIR}/dmg_osx/notarize.sh \${CPACK_PACKAGE_FILES}\")"
"execute_process(COMMAND sh -c \"${CMAKE_SOURCE_DIR}/macos/notarize.sh \${CPACK_PACKAGE_FILES}\")"
)
set(CPACK_POST_BUILD_SCRIPTS "${notarizeScript}")

set(CPACK_GENERATOR DragNDrop)
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/dmg_osx/background.png")
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/macos/dmg_osx/background.png")
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "DS_Store_Setup.scpt")
set(CPACK_DMG_VOLUME_NAME "${projectDisplayName}")

configure_file("dmg_osx/DS_Store_Setup.scpt.in" "${CPACK_DMG_DS_STORE_SETUP_SCRIPT}" @ONLY)
configure_file("macos/dmg_osx/DS_Store_Setup.scpt.in" "${CPACK_DMG_DS_STORE_SETUP_SCRIPT}" @ONLY)
endif()
include(CPack)
5 changes: 4 additions & 1 deletion ci/macos/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

brew install ninja

qtVersion='6.7.2'
qtVersionWithoutDots=${qtVersion//./}

cd ..
for module in base tools ; do
archive="$module.7z"
curl -L -o "$archive" "https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_662/qt.qt6.662.clang_64/6.6.2-0-202402121131qt$module-MacOS-MacOS_13-Clang-MacOS-MacOS_13-X86_64-ARM64.7z"
curl -L -o "$archive" "https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_$qtVersionWithoutDots/qt.qt6.$qtVersionWithoutDots.clang_64/$qtVersion-0-202406110330qt$module-MacOS-MacOS_13-Clang-MacOS-MacOS_13-X86_64-ARM64.7z"
7z x "$archive" '-xr!*.dSYM'
done
echo "CMAKE_PREFIX_PATH=$PWD/$(ls -1 | fgrep 6.)/macos" >> $GITHUB_ENV
Expand Down
52 changes: 25 additions & 27 deletions jsonparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,66 +108,64 @@ bool ReadImageName(const QJsonObject& imageObject, int projectIndex, QList<QVari
void JsonParser::parseAndSet(const QByteArray &data, const QString label)
{
//qDebug() << "parseAndSet data:" << data;
QJsonDocument jsonDocument = QJsonDocument::fromJson(data);
QJsonObject jsonObject = jsonDocument.object();

int imageCount = 0;
const QJsonDocument jsonDocument = QJsonDocument::fromJson(data);
const auto jsonObject = jsonDocument.object();

// get project versions (LibreElec 7.0, 8.0, ...)
for (auto itProjectVersions = jsonObject.begin(); itProjectVersions != jsonObject.end(); itProjectVersions++)
for (auto itProjectVersions = jsonObject.begin(); itProjectVersions != jsonObject.end(); ++itProjectVersions)
{
QString project = itProjectVersions.key();
QString projectUrl = itProjectVersions.value().toObject()["url"].toString();
const auto project = itProjectVersions.key();
const auto projectObj = itProjectVersions.value().toObject();
const auto projectUrl = projectObj["url"].toString();

// get projects (imx6, Wetek, ...)
QJsonObject projectVersionsNode = itProjectVersions.value().toObject()["project"].toObject();
for (auto itProjects = projectVersionsNode.begin(); itProjects != projectVersionsNode.end(); itProjects++)
const auto projectVersionsNode = projectObj["project"].toObject();
for (auto itProjects = projectVersionsNode.begin(); itProjects != projectVersionsNode.end(); ++itProjects)
{
QString projectId = itProjects.key();
QString projectName = itProjects.value().toObject()["displayName"].toString();
const auto projectId = itProjects.key();
const auto releasesNode = itProjects.value().toObject();
auto projectName = releasesNode["displayName"].toString();

// skip Virtual
if (projectId == "Virtual.x86_64")
continue;

if (label != "")
projectName = projectName + " - " + label;
if (!label.isEmpty())
projectName += QString{" - %1"}.arg(label);

QVariantMap projectData;
projectData.insert("name", projectName);
projectData.insert("id", projectId);
projectData.insert("url", projectUrl);

// get releases
QJsonObject releasesNode = itProjects.value().toObject();
for (auto itReleases = releasesNode.begin(); itReleases != releasesNode.end(); itReleases++)
for (auto itReleases = releasesNode.begin(); itReleases != releasesNode.end(); ++itReleases)
{
QList<QVariantMap> imagesList;
ProjectData projectCheck;
projectCheck.name = projectName;
int projectIndex = projectList.indexOf(projectCheck);

QJsonObject releaseNode = itReleases.value().toObject();
for (auto itReleaseItems = releaseNode.begin(); itReleaseItems != releaseNode.end(); itReleaseItems++)
const auto releaseNode = itReleases.value().toObject();
for (auto itReleaseItems = releaseNode.begin(); itReleaseItems != releaseNode.end(); ++itReleaseItems)
{
QJsonObject releaseItemsNode = itReleaseItems.value().toObject();
const auto releaseItemsNode = itReleaseItems.value().toObject();

QJsonObject::Iterator itFile = releaseItemsNode.find("file");
if (ReadImageName(itFile.value().toObject(), projectIndex, imagesList, projectList))
imageCount++;
const auto itFile = releaseItemsNode.find("file");
if (itFile != releaseItemsNode.end())
ReadImageName(itFile.value().toObject(), projectIndex, imagesList, projectList);

QJsonObject::Iterator itImage = releaseItemsNode.find("image");
if (ReadImageName(itImage.value().toObject(), projectIndex, imagesList, projectList))
imageCount++;
const auto itImage = releaseItemsNode.find("image");
if (itImage != releaseItemsNode.end())
ReadImageName(itImage.value().toObject(), projectIndex, imagesList, projectList);

QJsonObject::Iterator itUbootsNode = releaseItemsNode.find("uboot");
const auto itUbootsNode = releaseItemsNode.find("uboot");
if (itUbootsNode != releaseItemsNode.end())
{
QJsonArray ubootsNode = itUbootsNode.value().toArray();
for (QJsonValue uboot : ubootsNode)
{
if (ReadImageName(uboot.toObject(), projectIndex, imagesList, projectList))
imageCount++;
ReadImageName(uboot.toObject(), projectIndex, imagesList, projectList);
}
}
}
Expand Down
File renamed without changes.
47 changes: 47 additions & 0 deletions macos/askpass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
////////////////////////////////////////////////////////////////////////////////
// This file is part of LibreELEC - http://www.libreelec.tv
// Copyright (C) 2024 Team LibreELEC
//
// LibreELEC is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// LibreELEC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with LibreELEC. If not, see <http://www.gnu.org/licenses/>.
////////////////////////////////////////////////////////////////////////////////

#include "askpass.h"

#include <QApplication>
#include <QInputDialog>

#include <iostream>

namespace sudo
{
void askpass()
{
const auto passwordPrompt = QObject::tr("%1 requires admin permissions.\n\nPlease enter your password to allow this.",
"arg is app name");

auto getAdminPasswordDlg = new QInputDialog;
getAdminPasswordDlg->setAttribute(Qt::WA_DeleteOnClose);
getAdminPasswordDlg->setInputMode(QInputDialog::TextInput);
getAdminPasswordDlg->setTextEchoMode(QLineEdit::Password);
getAdminPasswordDlg->setLabelText(passwordPrompt.arg(qApp->applicationDisplayName()));
getAdminPasswordDlg->show();

QObject::connect(getAdminPasswordDlg, &QInputDialog::accepted, [=]{
const auto enteredPassword = getAdminPasswordDlg->textValue();
std::cout << qUtf8Printable(enteredPassword);
std::cout.flush();
qApp->quit();
});
}
}
31 changes: 31 additions & 0 deletions macos/askpass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
////////////////////////////////////////////////////////////////////////////////
// This file is part of LibreELEC - http://www.libreelec.tv
// Copyright (C) 2024 Team LibreELEC
//
// LibreELEC is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// LibreELEC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with LibreELEC. If not, see <http://www.gnu.org/licenses/>.
////////////////////////////////////////////////////////////////////////////////

#ifndef ASKPASS_H
#define ASKPASS_H

#include <QLatin1String>

namespace sudo
{
inline const QLatin1String AskPassEnvVar{"SUDO_ASKPASS"};

void askpass();
}

#endif // ASKPASS_H
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
28 changes: 20 additions & 8 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#ifdef Q_OS_MACOS
#include "privileges_unix.h"
#include "macos/askpass.h"

#include <QLatin1String>
#include <QProcess>
Expand Down Expand Up @@ -59,25 +60,36 @@ int main(int argc, char *argv[])
#endif

#ifdef Q_OS_MACOS
// special GUI when executing as a "get admin password" program
if (qEnvironmentVariableIsSet(sudo::AskPassEnvVar.data()))
{
sudo::askpass();
return app.exec();
}

// If not running with root privileges, relaunch executable with sudo.
const QLatin1String elevatedParam{"--elevated"};
if (!cmdArgs.contains(elevatedParam) && getuid() != 0)
{
const auto sudoPrompt = QLatin1String{"%1 requires admin permissions."}.arg(app.applicationDisplayName());
const QLatin1String appleScript{R"(do shell script "sudo '%1' %2" with prompt "%3" with administrator privileges)"};
const auto cliParams = QStringList{elevatedParam} + cmdArgs.mid(1);
const auto executablePath = QCoreApplication::applicationFilePath();

// Apple Script "do shell script ... with administrator privileges" can't be used here:
// GUI app launched like that doesn't have all GUI capabilities, like accepting file drops
QProcess sudoProc;
sudoProc.setProgram(QLatin1String{"sudo"});
sudoProc.setArguments(QStringList{QLatin1String{"--askpass"}, executablePath, elevatedParam} + cmdArgs.mid(1));

QProcess myProcess;
myProcess.setProgram(QLatin1String{"osascript"});
myProcess.setArguments({"-e", appleScript.arg(QCoreApplication::applicationFilePath(), cliParams.join(' '), sudoPrompt)});
QProcessEnvironment sudoEnv;
sudoEnv.insert(sudo::AskPassEnvVar, executablePath);
sudoProc.setProcessEnvironment(sudoEnv);

if (myProcess.startDetached())
if (sudoProc.startDetached())
{
return 0;
}
else
{
qDebug() << "Unable to start elevated process for " << QCoreApplication::applicationFilePath();
qDebug() << "Unable to start elevated process for " << executablePath;
}
}
#endif
Expand Down
Loading