diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index b69ebc8..40c980e 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -39,7 +39,8 @@ jobs: - name: Build Linux Deps if: matrix.os == 'ubuntu-latest' run: | - sudo apt install \ + sudo apt update && \ + sudo apt install -y \ libfontconfig1-dev \ libfreetype-dev \ libgtk-3-dev \ diff --git a/installer-framework b/installer-framework index e5528be..d1d0274 160000 --- a/installer-framework +++ b/installer-framework @@ -1 +1 @@ -Subproject commit e5528bea17b11ff1981d2a113da862bd49d2445e +Subproject commit d1d0274d6d6bcf31b7580e37529ed8d2e0254745 diff --git a/installer/config/controller.qs b/installer/config/controller.qs index 0b4933f..81eed58 100644 --- a/installer/config/controller.qs +++ b/installer/config/controller.qs @@ -1,130 +1,18 @@ -let existingMaintenanceTool = ""; -let maintenanceToolStatus = 0; - function Controller() { installer.setDefaultPageVisible(QInstaller.TargetDirectory, false); - - const existingInstall = getExistingInstall(); - if (existingInstall && installer.isInstaller()) { - existingMaintenanceTool = existingInstall; - - installer.setDefaultPageVisible(QInstaller.TargetDirectory, false); - installer.setDefaultPageVisible(QInstaller.ReadyForInstallation, false); - installer.setDefaultPageVisible(QInstaller.ComponentSelection, false); - installer.setDefaultPageVisible(QInstaller.StartMenuSelection, false); - installer.setDefaultPageVisible(QInstaller.PerformInstallation, false); - installer.setDefaultPageVisible(QInstaller.LicenseCheck, false); - } -} - -Controller.prototype.IntroductionPageCallback = function () { - if (existingMaintenanceTool) { - const introPage = gui.currentPageWidget(); - introPage.setComplete(false); - introPage.MessageLabel.text = "Waiting for Maintenance Tool to Complete"; - const tempRepo = `${QDesktopServices.storageLocation(QDesktopServices.TempLocation)}/tmpImpactoInstallRepository`; - const tempCache = `${QDesktopServices.storageLocation(QDesktopServices.TempLocation)}/tmpImpactoInstallCache`; - installer.performOperation("CreateLocalRepository", ["@InstallerFilePath@", tempRepo]); - const output = - (() => { - if (systemInfo.productType === "windows") { - return installer.execute( - "cmd", ["/c", "start", "", "/wait", existingMaintenanceTool, "--set-temp-repository", tempRepo, "--cache-path", tempCache] - ); - } else if (systemInfo.productType === "macos") { - return installer.execute( - "open", - ["-W", existingMaintenanceTool, "--args", - "--set-temp-repository", tempRepo, - "--cache-path", tempCache] - ); - } else { - return installer.execute( - existingMaintenanceTool, - ["--set-temp-repository", tempRepo, "--cache-path", tempCache] - ); - } - })(); - console.log(`Maintenance Tool Output: ${output[0]}`) - console.log(`Maintenance Tool Status Code: ${output[1]}`) - maintenanceToolStatus = Number(output[1]); - installer.performOperation("Rmdir", [tempRepo, "FORCE"]); - installer.performOperation("Rmdir", [tempCache, "FORCE"]); - introPage.setComplete(true); - gui.clickButton(buttons.NextButton); - } } Controller.prototype.FinishedPageCallback = function () { - if (existingMaintenanceTool) { + const wasUpdated = installer.containsValue("ExistingMaintenanceTool"); + if (wasUpdated) { const finishedPage = gui.currentPageWidget(); + const maintenanceToolStatus = Number(installer.value("MaintenanceToolStatus")); if (maintenanceToolStatus == 0) finishedPage.MessageLabel.text = `Operation completed.`; if (maintenanceToolStatus == 1) finishedPage.MessageLabel.text = `Operation failed.`; if (maintenanceToolStatus == 3) finishedPage.MessageLabel.text = `Operation canceled.`; } } -function getConfigDir() { - const productName = installer.value("Name"); - const publisher = installer.value("Publisher"); - if (systemInfo.productType === "windows") { - const roaming = installer.fromNativeSeparators(QDesktopServices.storageLocation(QDesktopServices.AppDataLocation)); - const roamingSlash = roaming.lastIndexOf("/"); - const configDir = roaming.slice(0, roamingSlash) + `/${publisher}/${productName}`; - return installer.toNativeSeparators(configDir); - } - - if (systemInfo.productType === "macos") { - const configDir = `${QDesktopServices.storageLocation(QDesktopServices.GenericDataLocation)}/${publisher}/${productName}`; - return configDir; - } - - const configDir = `${QDesktopServices.storageLocation(QDesktopServices.ConfigLocation)}/${publisher}/${productName}`; - return configDir; -} - -function getExistingInstall() { - const configDir = getConfigDir(); - console.log(configDir); - const basePathsFilePath = installer.toNativeSeparators(`${configDir}/basepaths.lua`); - console.log(basePathsFilePath); - const basePaths = installer.readFile(basePathsFilePath, "UTF-8"); - console.log(basePaths); - - if (basePaths.length === 0) return null; - // 1. Strip out Lua block comments --[[ ... ]] so we do not match dead code - let cleanContent = basePaths.replace(/--\[\[[\s\S]*?\]\]/g, ""); - - // 2. Strip out single line comments -- ... - cleanContent = basePaths.replace(/--.*$/gm, ""); - - // 3. Regex matching the Lua standard for strings: - // Pattern 1: "..." or '...' using (["'])(.*?)\1 - // Pattern 2: [[...]] using \[\[([\s\S]*?)\]\] - let regex = /RootInstallDir\s*=\s*(?:(["'])(.*?)\1|\[\[([\s\S]*?)\]\])/g; - let match; - - - // Loop through all matches in the file string - while ((match = regex.exec(cleanContent)) !== null) { - // If it matched a quoted string, the path is in match[2] - // If it matched a Lua long bracket [[ ]], the path is in match[3] - let extractedPath = match[2] || match[3]; - - console.log("Extracted Lua RootInstallDir: " + extractedPath); - if (extractedPath.length > 0) { - let maintenanceToolName = installer.value("MaintenanceToolName"); - if (systemInfo.productType === "windows") maintenanceToolName += ".exe"; - if (systemInfo.productType === "macos") maintenanceToolName += ".dmg"; - const maintenanceTool = installer.findPath(maintenanceToolName, [extractedPath]); - if (maintenanceTool.length > 0) { - console.log(`Found Maintenance Tool at ${maintenanceTool}`); - return maintenanceTool; - } - } - } - return null; -} function componentIsSelected(widget) { if (!widget) return false; @@ -166,35 +54,6 @@ Controller.prototype.onSelectionChange = function () { } } - // Make Impacto Selections Mutually Exclusive - const impactoComponents = [ - "com.committeeofzero.impacto.windows", - "com.committeeofzero.impacto.linux", - "com.committeeofzero.impacto.macos_arm64", - "com.committeeofzero.impacto.macos_x64", - "com.committeeofzero.impacto.switch", - "com.committeeofzero.impacto.android", - ]; - let selectedImpactoComponent = null; - for (const componentName of impactoComponents) { - const component = installer.componentByName(componentName); - if (componentIsSelected(component)) { - selectedImpactoComponent = componentName; - break; - } - } - - // TODO use radio buttons page. - for (const componentName of impactoComponents) { - const component = installer.componentByName(componentName); - if(!component) continue; - if(selectedImpactoComponent == null) { - component.enabled = true; // Enable all if none selected - } else if (componentName !== selectedImpactoComponent) { - component.enabled = !selectedImpactoComponent; // Disable if another is selected - } - } - validateSelection(); page.completeChanged.connect(this, Controller.prototype.onSelectionChange); @@ -207,67 +66,6 @@ Controller.prototype.ComponentSelectionPageCallback = function () { Controller.prototype.onSelectionChange(); }; -function checkImpactoDesktopInstall() { - const componentInstaller = installer.componentByName("com.committeeofzero.installer"); - - if (componentInstaller) { - const errors = []; - - const impactoGroup = installer.componentByName("com.committeeofzero.impacto"); - - if (!componentIsSelected(impactoGroup)) { - errors.push( - "Please select an Impacto component." - ); - } - const pageTargetDesktop = gui.pageWidgetByObjectName("DynamicTargetWidget"); - const pageTargetMobile = gui.pageWidgetByObjectName("DynamicTargetWidgetMobile"); - const componentImpactoWin = installer.componentByName("com.committeeofzero.impacto.windows"); - const componentImpactoLin = installer.componentByName("com.committeeofzero.impacto.linux"); - const componentImpactoMacArm = installer.componentByName("com.committeeofzero.impacto.macos_arm64"); - const componentImpactoMacX64 = installer.componentByName("com.committeeofzero.impacto.macos_x64"); - const componentImpactoSwitch = installer.componentByName("com.committeeofzero.impacto.switch"); - const componentImpactoAndroid = installer.componentByName("com.committeeofzero.impacto.android"); - - const requestDesktopInstall = - componentImpactoWin?.installationRequested() || - componentImpactoLin?.installationRequested() || - componentImpactoMacArm?.installationRequested() || - componentImpactoMacX64?.installationRequested(); - - const requestMobileInstall = - componentImpactoSwitch?.installationRequested() || - componentImpactoAndroid?.installationRequested(); - - if (installer.isInstaller() && requestMobileInstall && !pageTargetMobile) { - installer.addWizardPage(componentInstaller, "TargetWidgetMobile", QInstaller.ReadyForInstallation); - } else if (installer.isInstaller() && !requestMobileInstall && pageTargetMobile) { - installer.removeWizardPage(componentInstaller, "TargetWidgetMobile"); - } - - if (installer.isInstaller() && requestDesktopInstall && !pageTargetDesktop) { - installer.addWizardPage(componentInstaller, "TargetWidget", QInstaller.ReadyForInstallation); - } else if (installer.isInstaller() && !requestDesktopInstall && pageTargetDesktop) { - installer.removeWizardPage(componentInstaller, "TargetWidget"); - } - - // Add block continue page w/ error if impacto is not selected - if (errors.length > 0) { - let page = gui.pageWidgetByObjectName("DynamicSelectionValidationPage"); - if (!page) { - installer.addWizardPage(componentInstaller, "SelectionValidationPage", QInstaller.LicenseCheck); - page = gui.pageWidgetByObjectName("DynamicSelectionValidationPage"); - } - if (page) { - page.label.text = errors.join("\n"); - page.complete = false; - } - } else { - installer.removeWizardPage(componentInstaller, "SelectionValidationPage"); - } - } -} - function addCclccPs4AssetsInstallPage() { const componentCclccPs4Assets = installer.componentByName("com.committeeofzero.cclcc.ps4.assets"); if (componentCclccPs4Assets) { @@ -297,10 +95,6 @@ function addChlccPs3AssetsInstallPage() { } function validateSelection() { - - // Desktop path selection page - checkImpactoDesktopInstall(); - addCclccPs4AssetsInstallPage(); addChlccPs3AssetsInstallPage(); } @@ -308,4 +102,41 @@ function validateSelection() { Controller.prototype.DynamicTargetWidgetMobileCallback = function () { console.log("Setting to portable install"); installer.setPortableInstaller(); -} \ No newline at end of file +} + +Controller.prototype.DynamicUpdateWaitingRoomCallback = function () { + const waitingPage = gui.pageByObjectName("DynamicUpdateWaitingRoom"); + waitingPage.setComplete(false); + const tempRepo = `${QDesktopServices.storageLocation(QDesktopServices.TempLocation)}/tmpImpactoInstallRepository`; + const tempCache = `${QDesktopServices.storageLocation(QDesktopServices.TempLocation)}/tmpImpactoInstallCache`; + installer.performOperation("CreateLocalRepository", ["@InstallerFilePath@", tempRepo]); + const existingInstall = installer.value("ExistingMaintenanceTool"); + const output = + (() => { + if (systemInfo.productType === "windows") { + return installer.execute( + "cmd", ["/c", "start", "", "/wait", existingInstall, "--set-temp-repository", tempRepo, "--cache-path", tempCache] + ); + } else if (systemInfo.productType === "macos") { + return installer.execute( + "open", + ["-W", existingInstall, "--args", + "--set-temp-repository", tempRepo, + "--cache-path", tempCache] + ); + } else { + return installer.execute( + existingInstall, + ["--set-temp-repository", tempRepo, "--cache-path", tempCache] + ); + } + })(); + console.log(`Maintenance Tool Output: ${output[0]}`) + console.log(`Maintenance Tool Status Code: ${output[1]}`) + installer.setValue("MaintenanceToolStatus", output[1]); + installer.performOperation("Rmdir", [tempRepo, "FORCE"]); + installer.performOperation("Rmdir", [tempCache, "FORCE"]); + waitingPage.setComplete(true); + gui.clickButton(buttons.NextButton); + gui.setVisible(true); +} diff --git a/installer/packages/com.committeeofzero.impacto/meta/package.xml b/installer/packages/com.committeeofzero.impacto/meta/package.xml index d411ee9..52e6350 100644 --- a/installer/packages/com.committeeofzero.impacto/meta/package.xml +++ b/installer/packages/com.committeeofzero.impacto/meta/package.xml @@ -5,4 +5,5 @@ 0.10.8.810 2026-10-05 + true diff --git a/installer/packages/com.committeeofzero.installer/meta/installscript.qs b/installer/packages/com.committeeofzero.installer/meta/installscript.qs index d4c8f80..6d9331c 100644 --- a/installer/packages/com.committeeofzero.installer/meta/installscript.qs +++ b/installer/packages/com.committeeofzero.installer/meta/installscript.qs @@ -15,6 +15,103 @@ const targetChooserImpactoMobileCallback = () => Component.prototype.chooseTarge function Component() { installer.wizardPageInsertionRequested.connect(this, Component.prototype.onWizardPageInsertionRequested); installer.wizardPageRemovalRequested.connect(this, Component.prototype.onWizardPageRemovalRequested); + + // Select platform to install on + installer.setValue("Platform", "desktop"); + + if (!installer.isInstaller()) return; + + if (installer.addWizardPage(component, "PlatformSelection", QInstaller.ComponentSelection)) { + const widget = gui.pageWidgetByObjectName("DynamicPlatformSelection"); + widget.platformDesktop.setChecked(true); + widget.platformAndroid.toggled.connect(this, checked => { + if (checked) installer.setValue("Platform", "android"); + }); + widget.platformDesktop.toggled.connect(this, checked => { + if (checked) installer.setValue("Platform", "desktop"); + }); + widget.platformSwitch.toggled.connect(this, checked => { + if (checked) installer.setValue("Platform", "switch"); + }); + + const page = gui.pageByObjectName("DynamicPlatformSelection"); + page.left.connect(OnPlatformSelectionLeave) + } +} + +function OnPlatformSelectionLeave() { + const componentSuffixes = ["windows", "macos_x64", "macos_arm64", "linux", "android", "switch"]; + const impactoGroup = "com.committeeofzero.impacto"; + const componentSuffix = GetTargetPlatformComponent(); + const platformComponentName = impactoGroup + "." + componentSuffix; + installer.deselectComponent(impactoGroup); + for (const suffix of componentSuffixes) { + const componentName = "com.committeeofzero.impacto." + suffix; + const componentWidget = installer.componentByName(componentName); + if (componentWidget) { + installer.deselectComponent(componentName); + } + } + installer.selectComponent(platformComponentName) + console.log(`selected component ${platformComponentName}`); + + const platform = installer.value("Platform"); + const componentInstaller = installer.componentByName("com.committeeofzero.installer"); + const pageTargetDesktop = gui.pageWidgetByObjectName("DynamicTargetWidget"); + const pageTargetMobile = gui.pageWidgetByObjectName("DynamicTargetWidgetMobile"); + + // give the illusion of an updater if an existing install is detected, a la controller.qs + const existingInstall = getExistingInstall(); + if (platform === "desktop" && existingInstall) { + installer.setValue("ExistingMaintenanceTool", existingInstall); + + installer.setDefaultPageVisible(QInstaller.TargetDirectory, false); + installer.setDefaultPageVisible(QInstaller.ReadyForInstallation, false); + installer.setDefaultPageVisible(QInstaller.ComponentSelection, false); + installer.setDefaultPageVisible(QInstaller.StartMenuSelection, false); + installer.setDefaultPageVisible(QInstaller.PerformInstallation, false); + installer.setDefaultPageVisible(QInstaller.LicenseCheck, false); + + installer.addWizardPage(componentInstaller, "UpdateWaitingRoom", QInstaller.LicenseCheck) + gui.setVisible(false); + gui.clickButton(buttons.NextButton); + + return; + } + + // only add the desktop install page if desktop is selected and the page doesn't already exist + if (platform === "desktop" && !pageTargetDesktop) { + installer.addWizardPage(componentInstaller, "TargetWidget", QInstaller.ReadyForInstallation); + } else if (platform !== "desktop" && pageTargetDesktop) { + installer.removeWizardPage(componentInstaller, "TargetWidget"); + } + + // switch/android + if (platform !== "desktop" && !pageTargetMobile) { + installer.addWizardPage(componentInstaller, "TargetWidgetMobile", QInstaller.ReadyForInstallation); + } else if (platform === "desktop" && pageTargetMobile) { + installer.removeWizardPage(componentInstaller, "TargetWidgetMobile"); + } +} + +function GetTargetPlatformComponent() { + const platform = installer.value("Platform"); + if (platform === "android" || platform === "switch") { + return platform; + } + + // Otherwise it's desktop, so detect which OS + switch (systemInfo.productType) { + case "windows": + return "windows"; + case "macos": + return systemInfo.currentCpuArchitecture === "arm64" + ? "macos_arm64" + : "macos_x64"; + default: + // TempleOS users get fucked lmao + return "linux"; + } } Component.prototype.isDefault = function () { @@ -304,4 +401,66 @@ Component.prototype.targetChangedImpactoMobile = function (text) { targetDirectoriesValidationState.Patches = true; targetDirectoriesValidationState.Profiles = true; -} \ No newline at end of file +} + +function getConfigDir() { + const productName = installer.value("Name"); + const publisher = installer.value("Publisher"); + if (systemInfo.productType === "windows") { + const roaming = installer.fromNativeSeparators(QDesktopServices.storageLocation(QDesktopServices.AppDataLocation)); + const roamingSlash = roaming.lastIndexOf("/"); + const configDir = roaming.slice(0, roamingSlash) + `/${publisher}/${productName}`; + return installer.toNativeSeparators(configDir); + } + + if (systemInfo.productType === "macos") { + const configDir = `${QDesktopServices.storageLocation(QDesktopServices.GenericDataLocation)}/${publisher}/${productName}`; + return configDir; + } + + const configDir = `${QDesktopServices.storageLocation(QDesktopServices.ConfigLocation)}/${publisher}/${productName}`; + return configDir; +} + +function getExistingInstall() { + const configDir = getConfigDir(); + console.log(configDir); + const basePathsFilePath = installer.toNativeSeparators(`${configDir}/basepaths.lua`); + console.log(basePathsFilePath); + const basePaths = installer.readFile(basePathsFilePath, "UTF-8"); + console.log(basePaths); + + if (basePaths.length === 0) return null; + // 1. Strip out Lua block comments --[[ ... ]] so we do not match dead code + let cleanContent = basePaths.replace(/--\[\[[\s\S]*?\]\]/g, ""); + + // 2. Strip out single line comments -- ... + cleanContent = basePaths.replace(/--.*$/gm, ""); + + // 3. Regex matching the Lua standard for strings: + // Pattern 1: "..." or '...' using (["'])(.*?)\1 + // Pattern 2: [[...]] using \[\[([\s\S]*?)\]\] + let regex = /RootInstallDir\s*=\s*(?:(["'])(.*?)\1|\[\[([\s\S]*?)\]\])/g; + let match; + + + // Loop through all matches in the file string + while ((match = regex.exec(cleanContent)) !== null) { + // If it matched a quoted string, the path is in match[2] + // If it matched a Lua long bracket [[ ]], the path is in match[3] + let extractedPath = match[2] || match[3]; + + console.log("Extracted Lua RootInstallDir: " + extractedPath); + if (extractedPath.length > 0) { + let maintenanceToolName = installer.value("MaintenanceToolName"); + if (systemInfo.productType === "windows") maintenanceToolName += ".exe"; + if (systemInfo.productType === "macos") maintenanceToolName += ".dmg"; + const maintenanceTool = installer.findPath(maintenanceToolName, [extractedPath]); + if (maintenanceTool.length > 0) { + console.log(`Found Maintenance Tool at ${maintenanceTool}`); + return maintenanceTool; + } + } + } + return null; +} diff --git a/installer/packages/com.committeeofzero.installer/meta/package.xml b/installer/packages/com.committeeofzero.installer/meta/package.xml index b7325fa..b0c60f1 100644 --- a/installer/packages/com.committeeofzero.installer/meta/package.xml +++ b/installer/packages/com.committeeofzero.installer/meta/package.xml @@ -7,9 +7,10 @@ true true - selectionValidation.ui targetDirectories.ui targetDirectoriesMobile.ui + platformSelection.ui + updateWaitingRoom.ui \ No newline at end of file diff --git a/installer/packages/com.committeeofzero.installer/meta/platformSelection.ui b/installer/packages/com.committeeofzero.installer/meta/platformSelection.ui new file mode 100644 index 0000000..73d2455 --- /dev/null +++ b/installer/packages/com.committeeofzero.installer/meta/platformSelection.ui @@ -0,0 +1,74 @@ + + + PlatformSelection + + + + 0 + 0 + 636 + 465 + + + + + 0 + 0 + + + + + 491 + 190 + + + + Platform Selection + + + + + + On which platform are you installing Impacto? + + + + + + + Desktop + + + + + + + Android + + + + + + + Switch + + + + + + + Qt::Orientation::Vertical + + + + 20 + 317 + + + + + + + + + diff --git a/installer/packages/com.committeeofzero.installer/meta/selectionValidation.ui b/installer/packages/com.committeeofzero.installer/meta/selectionValidation.ui deleted file mode 100644 index e7d70fc..0000000 --- a/installer/packages/com.committeeofzero.installer/meta/selectionValidation.ui +++ /dev/null @@ -1,26 +0,0 @@ - - - SelectionValidationPage - - - Component Selection Validation - - - - - - - - - true - - - color: red; font-weight: bold; - - - - - - - - \ No newline at end of file diff --git a/installer/packages/com.committeeofzero.installer/meta/updateWaitingRoom.ui b/installer/packages/com.committeeofzero.installer/meta/updateWaitingRoom.ui new file mode 100644 index 0000000..b23a2b9 --- /dev/null +++ b/installer/packages/com.committeeofzero.installer/meta/updateWaitingRoom.ui @@ -0,0 +1,53 @@ + + + UpdateWaitingRoom + + + + 0 + 0 + 636 + 465 + + + + + 0 + 0 + + + + + 491 + 190 + + + + Update Waiting Room + + + + + + Waiting for Maintenance Tool to Complete... + + + + + + + Qt::Orientation::Vertical + + + + 20 + 317 + + + + + + + + + diff --git a/scripts/lib/qt.py b/scripts/lib/qt.py index 327bf85..8411418 100644 --- a/scripts/lib/qt.py +++ b/scripts/lib/qt.py @@ -6,6 +6,8 @@ QT_PREFIX = Path() +QT_MODULES = "qtbase,qtdeclarative,qttools,qttranslations,qt5compat" + def set_qt_prefix(new_prefix: str): global QT_PREFIX QT_PREFIX = Path(new_prefix) @@ -27,7 +29,7 @@ def init_qt(): common.run( [ str(qt_script("init-repository")), - "--module-subset=qtbase,qtdeclarative,qttools,qttranslations,qt5compat", + f"--module-subset={QT_MODULES}", ], cwd=common.QT, ) @@ -97,8 +99,7 @@ def build_qt(rebuild=False, symbols = False): args = [ str(qt_script("configure")), - "-prefix", - str(qt_prefix()), + "-prefix", str(qt_prefix()), "-static", "-release", @@ -108,11 +109,11 @@ def build_qt(rebuild=False, symbols = False): "-opensource", "-confirm-license", - "-nomake", - "examples", + "-submodules", QT_MODULES, + + "-nomake", "examples", - "-nomake", - "tests", + "-nomake", "tests", "-no-sql-sqlite", "-no-qml-debug"