Skip to content

Commit

Permalink
Merge pull request #70 from kambala-decapitator/macos-adhoc-codesign
Browse files Browse the repository at this point in the history
  • Loading branch information
chewitt authored Aug 7, 2024
2 parents b0b9209 + 27b12a3 commit d961c40
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 43 deletions.
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 @@ -66,7 +66,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 @@ -165,9 +165,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 @@ -181,7 +182,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 @@ -236,15 +237,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

0 comments on commit d961c40

Please sign in to comment.