1
0
mirror of https://gitlab.com/kelteseth/ScreenPlay.git synced 2024-09-14 22:42:34 +02:00

Merge branch 'master' into 172-add-wayland-layer-shell-support

This commit is contained in:
Elias Steurer 2023-08-25 13:42:26 +02:00
commit dccc08f2ff
51 changed files with 1146 additions and 701 deletions

View File

@ -1,118 +1,22 @@
include:
- ".gitlab/ci/base_jobs.yml"
- ".gitlab/ci/build_jobs.yml"
- ".gitlab/ci/build_release_jobs.yml"
- ".gitlab/ci/check_jobs.yml"
stages: stages:
# None Steam build for every commit
- build - build
# Steam build for every commit
- build_steam
# Git release tag builds
- release_build
# Gitlab releases at:
# https://gitlab.com/kelteseth/ScreenPlay/-/releases
- release
# Checks for source code formattings
- check - check
.base_windows_build:
before_script:
- python -m pip install -U pip wheel
- python -m pip install -r Tools/requirements.txt
- python Tools/setup.py
.base_linux_build:
before_script:
# Otherwise libglib2 needs interaction
- export DEBIAN_FRONTEND=noninteractive
- apt update -y
- apt install libwayland-dev wayland-protocols curl wget zip unzip tar git pkg-config libxcb-* libfontconfig-dev apt-transport-https ca-certificates gnupg software-properties-common python3 python3-pip build-essential libgl1-mesa-dev mesa-common-dev lld ninja-build libxkbcommon-* libx11-dev xserver-xorg-dev xorg-dev -y
- wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
- echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal-rc main' | tee -a /etc/apt/sources.list.d/kitware.list >/dev/null
- apt update -y
- apt install cmake -y
- python3 -m pip install -U pip
- python3 -m pip install -r Tools/requirements.txt
- python3 Tools/setup.py
.base_osx_build:
before_script:
- pip3 install -U pip
- pip3 install -r Tools/requirements.txt
- python3 Tools/setup.py
standalone_windows:
stage: build
extends:
- .base_windows_build
tags:
- windows10
script:
- python Tools/build.py -type release -use-aqt -installer -deploy-version
artifacts:
expire_in: "2 weeks"
paths:
- build-x64-windows-release/bin/
- build-x64-windows-release/ScreenPlay-Installer.exe
standalone_osx:
stage: build
extends:
- .base_osx_build
tags:
- osx
script:
- python3 Tools/build.py -type release -use-aqt -deploy-version -sign_osx
artifacts:
expire_in: "2 weeks"
paths:
- build-64-osx-universal-release/bin/
standalone_linux:
stage: build
extends:
- .base_linux_build
image:
name: ubuntu:20.04
tags:
- gitlab-org-docker
script:
- python3 Tools/build.py -type release -deploy-version -use-aqt -installer
artifacts:
expire_in: "4 weeks"
paths:
- build-x64-linux-release/bin/
steam_windows:
stage: build
extends:
- .base_windows_build
tags:
- windows10
script:
- python Tools/build.py -type release -steam -use-aqt -deploy-version
artifacts:
expire_in: "2 weeks"
paths:
- build-x64-windows-release/bin/
steam_osx:
stage: build
extends:
- .base_osx_build
tags:
- osx
script:
- python3 Tools/build.py -type release -steam -use-aqt -deploy-version -sign_osx
artifacts:
expire_in: "2 weeks"
paths:
- build-64-osx-universal-release/bin/
formatting:
stage: check
allow_failure: true
image:
name: ubuntu:20.04
tags:
- gitlab-org-docker
before_script:
- apt-get update -y
- apt-get install python3-pip python-is-python3 clang clang-format -y
script:
- python -m pip install -U pip wheel
- python -m pip install -U cmakelang
- cd Tools
- python clang_format.py
- python cmake_format.py
documentation: documentation:
stage: .post stage: .post
script: script:

49
.gitlab/ci/base_jobs.yml Normal file
View File

@ -0,0 +1,49 @@
.base_windows_build:
dependencies: []
before_script:
- python -m pip install -U pip wheel
- python -m pip install -r Tools/requirements.txt
- python Tools/setup.py
tags:
- windows10
artifacts:
expire_in: "2 weeks"
paths:
- build-x64-windows-release/bin/
- build-x64-windows-release/ScreenPlay-Installer.exe
.base_linux_build:
dependencies: []
before_script:
# Otherwise libglib2 needs interaction
- export DEBIAN_FRONTEND=noninteractive
- apt update -y
- apt install libwayland-dev wayland-protocols curl wget zip unzip tar git pkg-config libxcb-* libfontconfig-dev apt-transport-https ca-certificates gnupg software-properties-common python3 python3-pip build-essential libgl1-mesa-dev mesa-common-dev lld ninja-build libxkbcommon-* libx11-dev xserver-xorg-dev xorg-dev -y
- wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
- echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal-rc main' | tee -a /etc/apt/sources.list.d/kitware.list >/dev/null
- apt update -y
- apt install cmake -y
- python3 -m pip install -U pip
- python3 -m pip install -r Tools/requirements.txt
- python3 Tools/setup.py
image:
name: ubuntu:20.04
tags:
- gitlab-org-docker
artifacts:
expire_in: "4 weeks"
paths:
- build-x64-linux-release/bin/
.base_osx_build:
dependencies: []
before_script:
- pip3 install -U pip
- pip3 install -r Tools/requirements.txt
- python3 Tools/setup.py
tags:
- osx
artifacts:
expire_in: "2 weeks"
paths:
- build-64-osx-universal-release/bin/

41
.gitlab/ci/build_jobs.yml Normal file
View File

@ -0,0 +1,41 @@
standalone_windows:
stage: build
extends:
- .base_windows_build
script:
- python Tools/build.py --type=release --use-aqt --installer --deploy-version
standalone_osx:
stage: build
extends:
- .base_osx_build
script:
- python3 Tools/build.py --type=release --use-aqt --deploy-version --sign_osx
standalone_linux:
stage: build
extends:
- .base_linux_build
script:
- python3 Tools/build.py --type=release --use-aqt --installer --deploy-version
steam_windows:
stage: build_steam
extends:
- .base_windows_build
script:
- python Tools/build.py --type=release -steam --use-aqt --deploy-version
steam_osx:
stage: build_steam
extends:
- .base_osx_build
script:
- python3 Tools/build.py --type=release -steam --use-aqt --deploy-version --sign_osx
steam_linux:
stage: build_steam
extends:
- .base_linux_build
script:
- python3 Tools/build.py --type=release -steam --deploy-version --use-aqt --installer

View File

@ -0,0 +1,78 @@
steam_linux:
stage: release_build
extends:
- .base_linux_build
script:
- python3 Tools/build.py --type=release --steam --deploy-version --use-aqt --installer
rules:
- if: "$CI_COMMIT_TAG"
artifacts:
paths:
- build-x64-linux-release/ScreenPlay-$CI_COMMIT_TAG-x64-linux-release.zip
- build-x64-linux-release/ScreenPlay-$CI_COMMIT_TAG-x64-linux-release.zip.sha256.txt
steam_windows:
stage: release_build
extends:
- .base_windows_build
script:
- python Tools/build.py --type=release --steam --use-aqt --deploy-version
rules:
- if: "$CI_COMMIT_TAG"
artifacts:
paths:
- build-x64-windows-release/ScreenPlay-$CI_COMMIT_TAG-x64-windows-release.zip
- build-x64-windows-release/ScreenPlay-$CI_COMMIT_TAG-x64-windows-release.zip.sha256.txt
steam_osx:
stage: release_build
extends:
- .base_osx_build
script:
- python3 Tools/build.py --type=release --steam --use-aqt --deploy-version --sign_osx
rules:
- if: "$CI_COMMIT_TAG"
artifacts:
paths:
- build-64-osx-universal-release/ScreenPlay-$CI_COMMIT_TAG-64-osx-universal-release.zip
- build-64-osx-universal-release/ScreenPlay-$CI_COMMIT_TAG-64-osx-universal-release.zip.sha256.txt
release_job:
stage: release
image: python:3.11
dependencies:
- steam_linux
- steam_windows
- steam_osx
rules:
- if: "$CI_COMMIT_TAG" # Run this job when a tag is created
when: on_success # Only when all previous jobs succeed
script:
# 1. Combining sha256 files into a SHA512-SUMS.txt
- python3 Tools/create_sha512.py
# 2. Install dependencies and handle the folder
- apt update
- apt install -y sshpass ssh curl
- curl --location --output /usr/local/bin/release-cli "https://gitlab.com/api/v4/projects/gitlab-org%2Frelease-cli/packages/generic/release-cli/latest/release-cli-linux-amd64"
- chmod +x /usr/local/bin/release-cli
- ssh-keyscan 91.204.46.10 >> ~/.ssh/known_hosts
- sshpass -p $GETSP_PASSWORD ssh $GETSP_USERNAME@91.204.46.10 "rm -rf /getsp.de/httpdocs/releases/$CI_COMMIT_TAG; mkdir /getsp.de/httpdocs/releases/$CI_COMMIT_TAG;"
# 3. Upload files to the remote folder
- sshpass -p $GETSP_PASSWORD scp build-x64-linux-release/ScreenPlay-$CI_COMMIT_TAG-x64-linux-release.zip $GETSP_USERNAME@91.204.46.10:/getsp.de/httpdocs/releases/$CI_COMMIT_TAG/
- sshpass -p $GETSP_PASSWORD scp build-x64-windows-release/ScreenPlay-$CI_COMMIT_TAG-x64-windows-release.zip $GETSP_USERNAME@91.204.46.10:/getsp.de/httpdocs/releases/$CI_COMMIT_TAG/
- sshpass -p $GETSP_PASSWORD scp build-64-osx-universal-release/ScreenPlay-$CI_COMMIT_TAG-64-osx-universal-release.zip $GETSP_USERNAME@91.204.46.10:/getsp.de/httpdocs/releases/$CI_COMMIT_TAG/
- sshpass -p $GETSP_PASSWORD scp SHA512-SUMS.txt $GETSP_USERNAME@91.204.46.10:/getsp.de/httpdocs/releases/$CI_COMMIT_TAG/
release:
tag_name: $CI_COMMIT_TAG
name: "ScreenPlay $CI_COMMIT_TAG Released!"
description: "🎉 A Wild ScreenPlay Release Appeared!"
assets:
links:
- name: 🐧 Linux x64 build
url: "https://getsp.de/releases/$CI_COMMIT_TAG/ScreenPlay-$CI_COMMIT_TAG-x64-linux-release.zip"
- name: 🪟 Windows x64 build
url: "https://getsp.de/releases/$CI_COMMIT_TAG/ScreenPlay-$CI_COMMIT_TAG-x64-windows-release.zip"
- name: 🍏 OSX universal build
url: "https://getsp.de/releases/$CI_COMMIT_TAG/ScreenPlay-$CI_COMMIT_TAG-64-osx-universal-release.zip"
- name: SHA512-SUMS.txt
url: "https://getsp.de/releases/$CI_COMMIT_TAG/SHA512-SUMS.txt"

18
.gitlab/ci/check_jobs.yml Normal file
View File

@ -0,0 +1,18 @@
formatting:
dependencies: []
stage: check
allow_failure: true
image:
name: ubuntu:23.10
tags:
- gitlab-org-docker
before_script:
- apt-get update -y
- apt-get install python3-pip python-is-python3 clang clang-format -y
script:
- python -m pip install -U pip wheel --break-system-packages
- python -m pip install -U cmakelang --break-system-packages
- cd Tools
- python check_format_cmake.py --check
- python check_format_cpp.py --check
#- python check_format_qml.py --check

20
.vscode/tasks.json vendored
View File

@ -28,7 +28,7 @@
"cwd": "${workspaceFolder}/Tools" "cwd": "${workspaceFolder}/Tools"
}, },
"args": [ "args": [
"clang_format.py" "check_format_cpp.py"
] ]
}, },
{ {
@ -46,7 +46,7 @@
"cwd": "${workspaceFolder}/Tools" "cwd": "${workspaceFolder}/Tools"
}, },
"args": [ "args": [
"cmake_format.py" "check_format_cmake.py"
] ]
}, },
{ {
@ -64,7 +64,7 @@
"cwd": "${workspaceFolder}/Tools" "cwd": "${workspaceFolder}/Tools"
}, },
"args": [ "args": [
"qml_format.py" "check_format_qml.py"
] ]
}, },
{ {
@ -87,7 +87,7 @@
}, },
{ {
"type": "process", "type": "process",
"label": "Export ScreenPlay release, with deploy and steam enabled", "label": "Build ScreenPlay release, with deploy and steam enabled",
"command": "python3", "command": "python3",
"windows": { "windows": {
"command": "python" "command": "python"
@ -102,16 +102,16 @@
"osx": { "osx": {
"args": [ "args": [
"build.py", "build.py",
"-type=release", "--type=release",
"-deploy-version", "--deploy-version",
"-steam" "--steam"
] ]
}, },
"args": [ "args": [
"build.py", "build.py",
"-type=release", "--type=release",
"-deploy-version", "--deploy-version",
"-steam" "--steam"
] ]
}, },
{ {

View File

@ -2,6 +2,7 @@ project(CMake)
set(FILES set(FILES
CMakeVariables.h.in CMakeVariables.h.in
GetProjectVersion.cmake
GenerateCMakeVariableHeader.cmake GenerateCMakeVariableHeader.cmake
CopyRecursive.cmake CopyRecursive.cmake
CreateIFWInstaller.cmake) CreateIFWInstaller.cmake)

View File

@ -0,0 +1,22 @@
message(STATUS "[DMG INSTALLER ENABLED]: Configuring. This can take some time...")
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_DMG_FORMAT "UDBZ")
set(CPACK_DMG_VOLUME_NAME "ScreenPlay")
set(CPACK_SYSTEM_NAME "OSX")
set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/ScreenPlay/assets/icons/app.ico")
set(CPACK_COMPONENTS_GROUPING IGNORE)
set(CPACK_PACKAGE_NAME "ScreenPlay")
set(CPACK_PACKAGE_FILE_NAME "ScreenPlay-Installer")
set(CPACK_PACKAGE_VENDOR "Elias Steurer")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
include(CPack)
# Install all files from /bin
install(
DIRECTORY "${CMAKE_BINARY_DIR}/bin/"
COMPONENT ScreenPlay
DESTINATION "./")

View File

@ -0,0 +1,33 @@
# Function: get_project_version
#
# Description:
# Fetches the project version from the latest Git tag. If Git is not found or
# the current directory is not a Git repository, it defaults to "0.0.0".
#
# Parameters:
# - VERSION_VAR: The name of the variable in which the fetched or default version will be stored.
#
# Example Usage:
# get_project_version(PROJECT_VERSION)
# message(STATUS "Version: ${PROJECT_VERSION}")
#
function(get_project_version VERSION_VAR)
find_package(Git)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --always
OUTPUT_VARIABLE GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
message(STATUS "Parsing git tag: ${GIT_VERSION}")
string(REPLACE "v" "" STRIPPED_VERSION "${GIT_VERSION}") # Remove the 'v' prefix
string(REPLACE "-" ";" VERSION_LIST ${STRIPPED_VERSION})
list(GET VERSION_LIST 0 VERSION_STRING)
set(${VERSION_VAR} ${VERSION_STRING} PARENT_SCOPE)
else()
set(${VERSION_VAR} "0.0.0" PARENT_SCOPE)
endif()
endfunction()

View File

@ -1,8 +1,12 @@
cmake_minimum_required(VERSION 3.23.0) cmake_minimum_required(VERSION 3.23.0)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH})
include(GetProjectVersion)
get_project_version(PROJECT_VERSION)
project( project(
ScreenPlay ScreenPlay
VERSION 0.15.0 VERSION ${PROJECT_VERSION}
DESCRIPTION "Modern, Cross Plattform, Live Wallpaper, Widgets and AppDrawer!" DESCRIPTION "Modern, Cross Plattform, Live Wallpaper, Widgets and AppDrawer!"
HOMEPAGE_URL "https://screen-play.app/" HOMEPAGE_URL "https://screen-play.app/"
LANGUAGES CXX) LANGUAGES CXX)
@ -146,23 +150,28 @@ if(WIN32)
add_subdirectory(ScreenPlaySysInfo) add_subdirectory(ScreenPlaySysInfo)
endif() endif()
if(${SCREENPLAY_INSTALLER} AND NOT APPLE) if(${SCREENPLAY_INSTALLER})
include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/CreateIFWInstaller.cmake) if(APPLE)
include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/CreateDmgInstaller.cmake)
else()
include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/CreateIFWInstaller.cmake)
endif()
endif() endif()
message(STATUS "[CPP DEFINE] DEPLOY_VERSION = ${DEPLOY_VERSION}") message(STATUS "[PROJECT] PROJECT_VERSION = ${PROJECT_VERSION}")
message(STATUS "[CPP DEFINE] SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}") message(STATUS "[PROJECT] CMAKE_VERSION = ${CMAKE_VERSION}")
message(STATUS "[CPP DEFINE] BUILD_DATE = ${BUILD_DATE}")
message(STATUS "[DEFINE] BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
message(STATUS "[DEFINE] GIT_COMMIT_HASH = ${GIT_COMMIT_HASH}")
message(STATUS "[OPTION] SCREENPLAY_DEPLOY = ${SCREENPLAY_DEPLOY}")
message(STATUS "[OPTION] SCREENPLAY_INSTALLER = ${SCREENPLAY_INSTALLER}")
message(STATUS "[OPTION] SCREENPLAY_STEAM = ${SCREENPLAY_STEAM}")
message(STATUS "[OPTION] SCREENPLAY_TESTS = ${SCREENPLAY_TESTS}")
message(STATUS "[PROJECT] SCREENPLAY_QML_MODULES_PATH = ${SCREENPLAY_QML_MODULES_PATH}") message(STATUS "[PROJECT] SCREENPLAY_QML_MODULES_PATH = ${SCREENPLAY_QML_MODULES_PATH}")
message(STATUS "[PROJECT] CMAKE_TOOLCHAIN_FILE = ${CMAKE_TOOLCHAIN_FILE}") message(STATUS "[PROJECT] CMAKE_TOOLCHAIN_FILE = ${CMAKE_TOOLCHAIN_FILE}")
message(STATUS "[PROJECT] VCPKG_PATH = ${VCPKG_PATH}") message(STATUS "[PROJECT] VCPKG_PATH = ${VCPKG_PATH}")
message(STATUS "[PROJECT] CMAKE_MODULE_PATH = ${CMAKE_MODULE_PATH}") message(STATUS "[PROJECT] CMAKE_MODULE_PATH = ${CMAKE_MODULE_PATH}")
message(STATUS "[PROJECT] VCPKG_TARGET_TRIPLET = ${VCPKG_TARGET_TRIPLET}") message(STATUS "[PROJECT] VCPKG_TARGET_TRIPLET = ${VCPKG_TARGET_TRIPLET}")
message(STATUS "[PROJECT] CMAKE_PREFIX_PATH = ${CMAKE_PREFIX_PATH}") message(STATUS "[PROJECT] CMAKE_PREFIX_PATH = ${CMAKE_PREFIX_PATH}")
message(STATUS "[PROJECT] CMAKE_VERSION = ${CMAKE_VERSION}") message(STATUS "[OPTION] SCREENPLAY_DEPLOY = ${SCREENPLAY_DEPLOY}")
message(STATUS "[OPTION] SCREENPLAY_INSTALLER = ${SCREENPLAY_INSTALLER}")
message(STATUS "[OPTION] SCREENPLAY_STEAM = ${SCREENPLAY_STEAM}")
message(STATUS "[OPTION] SCREENPLAY_TESTS = ${SCREENPLAY_TESTS}")
message(STATUS "[DEFINE] BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
message(STATUS "[DEFINE] GIT_COMMIT_HASH = ${GIT_COMMIT_HASH}")
message(STATUS "[CPP DEFINE] DEPLOY_VERSION = ${DEPLOY_VERSION}")
message(STATUS "[CPP DEFINE] SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "[CPP DEFINE] BUILD_DATE = ${BUILD_DATE}")

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts

View File

@ -42,19 +42,19 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
Component.onCompleted: { Component.onCompleted: {
attractor.pointX = parent.width * .5 attractor.pointX = parent.width * .5;
attractor.pointY = 0 attractor.pointY = 0;
} }
onPressed: { onPressed: {
attractor.enabled = true attractor.enabled = true;
} }
onPositionChanged: { onPositionChanged: {
attractor.pointX = mouseX attractor.pointX = mouseX;
attractor.pointY = mouseY attractor.pointY = mouseY;
} }
onReleased: { onReleased: {
attractor.enabled = false attractor.enabled = false;
} }
} }
@ -119,7 +119,7 @@ Item {
bottom: parent.bottom bottom: parent.bottom
bottomMargin: -width * .65 bottomMargin: -width * .65
} }
SequentialAnimation on opacity { SequentialAnimation on opacity {
loops: Animation.Infinite loops: Animation.Infinite
OpacityAnimator { OpacityAnimator {

View File

@ -8,32 +8,31 @@ Item {
implicitHeight: 100 implicitHeight: 100
function timeChanged() { function timeChanged() {
var date = new Date var date = new Date;
var hours = "" var hours = "";
var minutes = "" var minutes = "";
var seconds = "" var seconds = "";
if (date.getHours() < 10) { if (date.getHours() < 10) {
hours = "0" + date.getHours().toString() hours = "0" + date.getHours().toString();
} else { } else {
hours = date.getHours().toString() hours = date.getHours().toString();
} }
if (date.getMinutes() < 10) { if (date.getMinutes() < 10) {
minutes = "0" + date.getMinutes().toString() minutes = "0" + date.getMinutes().toString();
} else { } else {
minutes = date.getMinutes().toString() minutes = date.getMinutes().toString();
} }
if (date.getSeconds() < 10) { if (date.getSeconds() < 10) {
seconds = "0" + date.getSeconds().toString() seconds = "0" + date.getSeconds().toString();
} else { } else {
seconds = date.getSeconds().toString() seconds = date.getSeconds().toString();
} }
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var day = days[date.getDay()] var day = days[date.getDay()];
var month = months[date.getMonth()] var month = months[date.getMonth()];
txtClock.text = hours + ":" + minutes + ":" + seconds txtClock.text = hours + ":" + minutes + ":" + seconds;
txtDate.text = day + ", " + date.getDay( txtDate.text = day + ", " + date.getDay() + " " + month + ", " + date.getFullYear();
) + " " + month + ", " + date.getFullYear()
} }
Timer { Timer {

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@ -17,16 +16,16 @@ Item {
property string link property string link
property string mediaContent property string mediaContent
onMediaContentChanged: { onMediaContentChanged: {
print("src") print("src");
const src = parseItem(model.mediaContent, 'url="', '"') const src = parseItem(model.mediaContent, 'url="', '"');
print("src", src) print("src", src);
//img.source = src; //img.source = src;
} }
function parseItem(raw, startTag, endTag) { function parseItem(raw, startTag, endTag) {
var startIdx = raw.indexOf(startTag) + startTag.length var startIdx = raw.indexOf(startTag) + startTag.length;
var endIdx = raw.indexOf(endTag, startIdx) var endIdx = raw.indexOf(endTag, startIdx);
return raw.substring(startIdx, endIdx) return raw.substring(startIdx, endIdx);
} }
RowLayout { RowLayout {
@ -49,8 +48,8 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
print(model.category) print(model.category);
print(model.mediaContent) print(model.mediaContent);
//Qt.openUrlExternally(model.link); //Qt.openUrlExternally(model.link);
} }
} }

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
@ -18,7 +17,7 @@ Item {
query: "/rss/channel/item" query: "/rss/channel/item"
onStatusChanged: { onStatusChanged: {
if (status === XmlListModel.Error) { if (status === XmlListModel.Error) {
console.log("Error: " + errorString) console.log("Error: " + errorString);
} }
} }

View File

@ -12,41 +12,38 @@ Item {
property string pubDate property string pubDate
property string pubDateFormatted property string pubDateFormatted
onPubDateChanged: { onPubDateChanged: {
var date = new Date(pubDate) var date = new Date(pubDate);
root.pubDateFormatted = date.toLocaleDateString( root.pubDateFormatted = date.toLocaleDateString(Qt.locale(), "ddd, dd MMM yyyy") + ' ' + date.toLocaleTimeString(Qt.locale(), "HH:mm:ss");
Qt.locale(),
"ddd, dd MMM yyyy") + ' ' + date.toLocaleTimeString(
Qt.locale(), "HH:mm:ss")
} }
property string description property string description
onDescriptionChanged: { onDescriptionChanged: {
print("description") print("description");
// See https://hnrss.org/frontpage <description> content // See https://hnrss.org/frontpage <description> content
// We need to manually parse it here to get the points and comments // We need to manually parse it here to get the points and comments
points = parsePoints(description) points = parsePoints(description);
commentCount = parseCommentCount(description) commentCount = parseCommentCount(description);
print(points, commentCount) print(points, commentCount);
} }
function parseCommentCount(raw) { function parseCommentCount(raw) {
var commentPrefix = "<p># Comments: " var commentPrefix = "<p># Comments: ";
var commentSuffix = "</p>" var commentSuffix = "</p>";
var startIdx = raw.indexOf(commentPrefix) var startIdx = raw.indexOf(commentPrefix);
if (startIdx === -1) if (startIdx === -1)
return "N/A" // return "N/A" if comment count is not found in the description return "N/A"; // return "N/A" if comment count is not found in the description
startIdx += commentPrefix.length startIdx += commentPrefix.length;
var endIdx = raw.indexOf(commentSuffix, startIdx) var endIdx = raw.indexOf(commentSuffix, startIdx);
return raw.substring(startIdx, endIdx) return raw.substring(startIdx, endIdx);
} }
function parsePoints(raw) { function parsePoints(raw) {
var pointsPrefix = "<p>Points: " var pointsPrefix = "<p>Points: ";
var pointsSuffix = "</p>" var pointsSuffix = "</p>";
var startIdx = raw.indexOf(pointsPrefix) var startIdx = raw.indexOf(pointsPrefix);
if (startIdx === -1) if (startIdx === -1)
return "N/A" // return "N/A" if points are not found in the description return "N/A"; // return "N/A" if points are not found in the description
startIdx += pointsPrefix.length startIdx += pointsPrefix.length;
var endIdx = raw.indexOf(pointsSuffix, startIdx) var endIdx = raw.indexOf(pointsSuffix, startIdx);
return raw.substring(startIdx, endIdx) return raw.substring(startIdx, endIdx);
} }
RowLayout { RowLayout {
id: wrapper id: wrapper
@ -68,15 +65,14 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
Qt.openUrlExternally(model.link) Qt.openUrlExternally(model.link);
} }
} }
} }
Text { Text {
id: descriptionText id: descriptionText
text: root.points + " Points • " + root.commentCount text: root.points + " Points • " + root.commentCount + " Comments 🔗 " + "• Published: " + root.pubDateFormatted
+ " Comments 🔗 " + "• Published: " + root.pubDateFormatted
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pointSize: 10 font.pointSize: 10
opacity: .7 opacity: .7
@ -85,7 +81,7 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
Qt.openUrlExternally(model.commentsLink) Qt.openUrlExternally(model.commentsLink);
} }
} }
} }

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@ -44,7 +43,7 @@ Item {
} }
} }
onActivated: { onActivated: {
rssModel.source = combo.currentValue rssModel.source = combo.currentValue;
} }
} }
ToolButton { ToolButton {
@ -79,10 +78,10 @@ Item {
source: combo.currentValue source: combo.currentValue
query: "/rss/channel/item" query: "/rss/channel/item"
function load() { function load() {
print(":load") print(":load");
var tempSource = rssModel.source var tempSource = rssModel.source;
rssModel.source = "" rssModel.source = "";
rssModel.source = tempSource rssModel.source = tempSource;
} }
XmlListModelRole { XmlListModelRole {

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@ -28,11 +27,11 @@ Item {
} }
function stringListToString(list) { function stringListToString(list) {
let out = "" let out = "";
for (var i = 0; i < list.length; i++) { for (var i = 0; i < list.length; i++) {
out += "\n" + list[i] out += "\n" + list[i];
} }
return out return out;
} }
RowLayout { RowLayout {
@ -57,8 +56,7 @@ Item {
} }
} }
Text { Text {
text: root.stringListToString( text: root.stringListToString(ipAddress.privateIpV4AddressList)
ipAddress.privateIpV4AddressList)
color: root.accentColor color: root.accentColor
font { font {
pointSize: 16 pointSize: 16
@ -66,8 +64,7 @@ Item {
} }
} }
Text { Text {
text: root.stringListToString( text: root.stringListToString(ipAddress.privateIpV6AddressList)
ipAddress.privateIpV6AddressList)
color: root.accentColor color: root.accentColor
font { font {
pointSize: 16 pointSize: 16

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@ -20,56 +19,57 @@ Item {
id: weather id: weather
city: "Friedrichshafen" city: "Friedrichshafen"
onReady: { onReady: {
rp.model = weather.days rp.model = weather.days;
// Qt bug https://bugreports.qt.io/browse/QTBUG-105137 // Qt bug https://bugreports.qt.io/browse/QTBUG-105137
test() test();
} }
} }
function test() {} function test() {
}
function mapWeatherCode(code) { function mapWeatherCode(code) {
const weather_time = "" const weather_time = "";
// or "-day", "-night" // or "-day", "-night"
const weather_prefix = "wi" + weather_time + "-" const weather_prefix = "wi" + weather_time + "-";
// https://open-meteo.com/en/docs // https://open-meteo.com/en/docs
// WMO Weather interpretation codes (WW) // WMO Weather interpretation codes (WW)
// to https://erikflowers.github.io/weather-icons/ // to https://erikflowers.github.io/weather-icons/
switch (code) { switch (code) {
case 0: case 0:
return weather_prefix + "day-sunny" return weather_prefix + "day-sunny";
case 1: case 1:
case 2: case 2:
case 3: case 3:
return weather_prefix + "cloud" return weather_prefix + "cloud";
case 45: case 45:
case 48: case 48:
return weather_prefix + "day-sunny" return weather_prefix + "day-sunny";
case 51: case 51:
case 53: case 53:
case 55: case 55:
return weather_prefix + "rain-mix" return weather_prefix + "rain-mix";
case 61: case 61:
case 63: case 63:
case 65: case 65:
return weather_prefix + "rain-mix" return weather_prefix + "rain-mix";
case 71: case 71:
case 73: case 73:
case 75: case 75:
return weather_prefix + "snow" return weather_prefix + "snow";
case 77: case 77:
return weather_prefix + "snow" return weather_prefix + "snow";
case 80: case 80:
case 81: case 81:
case 82: case 82:
return weather_prefix + "snow" return weather_prefix + "snow";
case 85: case 85:
case 86: case 86:
return weather_prefix + "snow" return weather_prefix + "snow";
case 95: case 95:
return weather_prefix + "thunderstorm" return weather_prefix + "thunderstorm";
case 96: case 96:
case 99: case 99:
return weather_prefix + "storm-showers" return weather_prefix + "storm-showers";
} }
} }
@ -89,9 +89,7 @@ Item {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
color: Material.primaryTextColor color: Material.primaryTextColor
text: "longtitude: " + weather.longtitude + " - latitude: " text: "longtitude: " + weather.longtitude + " - latitude: " + weather.latitude + " - elevation: " + weather.elevation + "m - population: " + weather.population
+ weather.latitude + " - elevation: " + weather.elevation
+ "m - population: " + weather.population
} }
RowLayout { RowLayout {
@ -129,8 +127,7 @@ Item {
} }
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
horizontalAlignment: Image.AlignHCenter horizontalAlignment: Image.AlignHCenter
source: "qrc:/qml/ScreenPlayWeather/assets/icons/" + root.mapWeatherCode( source: "qrc:/qml/ScreenPlayWeather/assets/icons/" + root.mapWeatherCode(weatherCode) + ".svg"
weatherCode) + ".svg"
} }
TextItem { TextItem {
text: "Weather Code" text: "Weather Code"

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
@ -15,27 +14,27 @@ Item {
property int defaultHeight: 200 property int defaultHeight: 200
function request(url, callback) { function request(url, callback) {
var xhr = new XMLHttpRequest() var xhr = new XMLHttpRequest();
xhr.onreadystatechange = (function (myxhr) { xhr.onreadystatechange = (function (myxhr) {
return function () { return function () {
if (myxhr.readyState === 4) if (myxhr.readyState === 4)
callback(myxhr) callback(myxhr);
} };
})(xhr) })(xhr);
xhr.open('GET', url) xhr.open('GET', url);
xhr.send('') xhr.send('');
} }
Component.onCompleted: { Component.onCompleted: {
request("http://xkcd.com/info.0.json", function (o) { request("http://xkcd.com/info.0.json", function (o) {
if (o.status === 200) { if (o.status === 200) {
var d = eval('new Object(' + o.responseText + ')') var d = eval('new Object(' + o.responseText + ')');
console.log(o.responseText) console.log(o.responseText);
img.source = d.img img.source = d.img;
} else { } else {
console.log("Some error has occurred") console.log("Some error has occurred");
} }
}) });
} }
Image { Image {
@ -45,22 +44,22 @@ Item {
property size imgSize: Qt.size(root.defaultWidth, defaultHeight) property size imgSize: Qt.size(root.defaultWidth, defaultHeight)
onStatusChanged: { onStatusChanged: {
if (img.status !== Image.Ready) if (img.status !== Image.Ready)
return return;
if (img.sourceSize.width === 0 || img.sourceSize.height === 0) if (img.sourceSize.width === 0 || img.sourceSize.height === 0)
return return;
root.implicitWidth = img.sourceSize.width root.implicitWidth = img.sourceSize.width;
root.implicitHeight = img.sourceSize.height root.implicitHeight = img.sourceSize.height;
print(img.status, img.sourceSize.width, img.sourceSize.height) print(img.status, img.sourceSize.width, img.sourceSize.height);
img.imgSize = Qt.size(img.sourceSize.width, img.sourceSize.height) img.imgSize = Qt.size(img.sourceSize.width, img.sourceSize.height);
print("img.size", img.imgSize) print("img.size", img.imgSize);
} }
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
root.state = root.state === "expanded" ? "normal" : "expanded" root.state = root.state === "expanded" ? "normal" : "expanded";
print(root.state, root.implicitHeight, root.implicitWidth) print(root.state, root.implicitHeight, root.implicitWidth);
} }
} }

View File

@ -11,17 +11,9 @@ Item {
implicitWidth: 240 implicitWidth: 240
implicitHeight: 120 implicitHeight: 120
property int totalHours: 24 property int totalHours: 24
property int remainingHours: Math.max(0, Math.floor( property int remainingHours: Math.max(0, Math.floor((new Date().setHours(24, 0, 0, 0) - new Date()) / 3600000))
(new Date().setHours( property int totalDays: new Date(new Date().getFullYear() + 1, 0, 1) - new Date() / (24 * 60 * 60 * 1000)
24, 0, 0, property int remainingDays: Math.max(0, Math.floor((new Date(new Date().getFullYear() + 1, 0, 1) - new Date()) / (24 * 60 * 60 * 1000)))
0) - new Date()) / 3600000))
property int totalDays: new Date(new Date().getFullYear() + 1, 0,
1) - new Date() / (24 * 60 * 60 * 1000)
property int remainingDays: Math.max(
0, Math.floor(
(new Date(new Date().getFullYear() + 1,
0,
1) - new Date()) / (24 * 60 * 60 * 1000)))
Material.theme: Material.Dark Material.theme: Material.Dark
Material.accent: Material.DeepOrange Material.accent: Material.DeepOrange
@ -50,11 +42,7 @@ Item {
running: true running: true
repeat: true repeat: true
onTriggered: { onTriggered: {
remainingHours = Math.max( remainingHours = Math.max(0, Math.floor((new Date().setHours(24, 0, 0, 0) - new Date()) / 3600000));
0,
Math.floor((new Date().setHours(
24, 0, 0,
0) - new Date()) / 3600000))
} }
} }
} }
@ -81,11 +69,7 @@ Item {
running: true running: true
repeat: true repeat: true
onTriggered: { onTriggered: {
remainingDays = Math.max(0, remainingDays = Math.max(0, Math.floor((new Date(new Date().getFullYear() + 1, 0, 1) - new Date()) / (24 * 60 * 60 * 1000)));
Math.floor((new Date(new Date().getFullYear(
) + 1,
0, 1)
- new Date()) / (24 * 60 * 60 * 1000)))
} }
} }
} }

View File

@ -138,6 +138,7 @@ set(RESOURCES
assets/icons/icon_close.svg assets/icons/icon_close.svg
assets/icons/icon_code.svg assets/icons/icon_code.svg
assets/icons/icon_community.svg assets/icons/icon_community.svg
assets/icons/icon_contains_audio.svg
assets/icons/icon_delete.svg assets/icons/icon_delete.svg
assets/icons/icon_document.svg assets/icons/icon_document.svg
assets/icons/icon_done.svg assets/icons/icon_done.svg
@ -152,7 +153,6 @@ set(RESOURCES
assets/icons/icon_installed.svg assets/icons/icon_installed.svg
assets/icons/icon_launch.svg assets/icons/icon_launch.svg
assets/icons/icon_minimize.svg assets/icons/icon_minimize.svg
assets/icons/icon_contains_audio.svg
assets/icons/icon_movie.svg assets/icons/icon_movie.svg
assets/icons/icon_new_releases.svg assets/icons/icon_new_releases.svg
assets/icons/icon_open_in_new.svg assets/icons/icon_open_in_new.svg

View File

@ -470,7 +470,7 @@ private:
bool m_checkWallpaperVisible { false }; bool m_checkWallpaperVisible { false };
bool m_silentStart { false }; bool m_silentStart { false };
bool m_anonymousTelemetry { true }; bool m_anonymousTelemetry { true };
bool m_showDefaultContent { true }; bool m_showDefaultContent { false };
QString m_decoder; QString m_decoder;
ScreenPlay::FillMode::FillMode m_videoFillMode { ScreenPlay::FillMode::FillMode::Cover }; ScreenPlay::FillMode::FillMode m_videoFillMode { ScreenPlay::FillMode::FillMode::Cover };

View File

@ -65,7 +65,7 @@ ApplicationWindow {
screenSize: Qt.size(root.width, root.height) screenSize: Qt.size(root.width, root.height)
domain: "app.screen-play.app" domain: "app.screen-play.app"
debug: false debug: false
enabled: App.settings.anonymousTelemetry enabled: false
} }
// Partial workaround for // Partial workaround for

View File

@ -175,7 +175,6 @@ Item {
} }
} }
Image { Image {
id: icnType id: icnType

View File

@ -43,7 +43,7 @@ Item {
const item = App.installedListModel.get(root.contentFolderName); const item = App.installedListModel.get(root.contentFolderName);
txtHeadline.text = item.m_title; txtHeadline.text = item.m_title;
const previewGiFilePath = Qt.resolvedUrl(item.m_absoluteStoragePath + "/" + item.m_previewGIF); const previewGiFilePath = Qt.resolvedUrl(item.m_absoluteStoragePath + "/" + item.m_previewGIF);
const previewImageFilePath = Qt.resolvedUrl( item.m_absoluteStoragePath + "/" + item.m_preview); const previewImageFilePath = Qt.resolvedUrl(item.m_absoluteStoragePath + "/" + item.m_preview);
root.hasPreviewGif = App.util.fileExists(previewGiFilePath); root.hasPreviewGif = App.util.fileExists(previewGiFilePath);
if (hasPreviewGif) { if (hasPreviewGif) {
animatedImagePreview.source = previewGiFilePath; animatedImagePreview.source = previewGiFilePath;

View File

@ -82,7 +82,7 @@ Item {
isChecked: App.settings.showDefaultContent isChecked: App.settings.showDefaultContent
onCheckboxChanged: function (checked) { onCheckboxChanged: function (checked) {
App.settings.setShowDefaultContent(checked); App.settings.setShowDefaultContent(checked);
App.installedListModel.reset() App.installedListModel.reset();
} }
} }

View File

@ -186,20 +186,19 @@ void ScreenPlaySDK::ScreenPlaySDK::redirectMessageOutputToMainWindow(QtMsgType t
// Also redirect to regular output if we debug // Also redirect to regular output if we debug
// wallpaper or widgets directly // wallpaper or widgets directly
switch (type) switch (type) {
{
case QtDebugMsg: case QtDebugMsg:
qDebug() << msg; qDebug() << msg;
break; break;
case QtWarningMsg: case QtWarningMsg:
qWarning() << msg; qWarning() << msg;
break; break;
case QtCriticalMsg: case QtCriticalMsg:
case QtFatalMsg: case QtFatalMsg:
qCritical() << msg; qCritical() << msg;
break; break;
case QtInfoMsg: case QtInfoMsg:
qInfo() << msg; qInfo() << msg;
break; break;
default: default:
break; break;

View File

@ -27,9 +27,9 @@ bool ProjectFile::init()
return false; return false;
file = obj.value("file").toString(); file = obj.value("file").toString();
QFileInfo fileInfo(folder.path() + "/"+ file); QFileInfo fileInfo(folder.path() + "/" + file);
if(!fileInfo.exists()){ if (!fileInfo.exists()) {
qCritical() << "Requested file:" << fileInfo.absoluteFilePath() << "does not exist!"; qCritical() << "Requested file:" << fileInfo.absoluteFilePath() << "does not exist!";
return false; return false;
} }
@ -107,7 +107,7 @@ bool ProjectFile::init()
} }
} }
if (type == ScreenPlay::InstalledType::InstalledType::VideoWallpaper){ if (type == ScreenPlay::InstalledType::InstalledType::VideoWallpaper) {
QFileInfo audioFile(folder.absolutePath() + "/audio.mp3"); QFileInfo audioFile(folder.absolutePath() + "/audio.mp3");
containsAudio = audioFile.exists(); containsAudio = audioFile.exists();
} }

View File

@ -49,7 +49,7 @@ Item {
onPlaybackStateChanged: { onPlaybackStateChanged: {
if (mediaPlayer.playbackState == MediaPlayer.PlayingState && !fadeInDone) { if (mediaPlayer.playbackState == MediaPlayer.PlayingState && !fadeInDone) {
fadeInDone = true; fadeInDone = true;
startTimer.start() startTimer.start();
} }
} }
loops: root.loops ? MediaPlayer.Infinite : 1 loops: root.loops ? MediaPlayer.Infinite : 1
@ -72,8 +72,7 @@ Item {
id: pauseTimer id: pauseTimer
interval: 100 interval: 100
onTriggered: { onTriggered: {
mediaPlayer.pause() mediaPlayer.pause();
} }
} }
Connections { Connections {
@ -96,12 +95,12 @@ Item {
} }
function onVisualsPausedChanged(visualsPaused) { function onVisualsPausedChanged(visualsPaused) {
if(!Wallpaper.isPlaying) if (!Wallpaper.isPlaying)
return return;
if(visualsPaused) if (visualsPaused)
pauseTimer.start() pauseTimer.start();
else else
mediaPlayer.play() mediaPlayer.play();
} }
target: Wallpaper target: Wallpaper

View File

@ -60,8 +60,8 @@ ScreenPlay::WallpaperExitCode BaseWindow::setup()
// We do not yet have implemented continue playing the audio.mp3 yet // We do not yet have implemented continue playing the audio.mp3 yet
// so disable the checkWallpaperVisible for now // so disable the checkWallpaperVisible for now
if(checkWallpaperVisible()){ if (checkWallpaperVisible()) {
if(projectFile.containsAudio){ if (projectFile.containsAudio) {
qInfo() << "Disable wallpaper visible check, because it contains audio."; qInfo() << "Disable wallpaper visible check, because it contains audio.";
setCheckWallpaperVisible(false); setCheckWallpaperVisible(false);
} }

View File

@ -26,7 +26,7 @@ int main(int argc, char* argv[])
QtWebEngineQuick::initialize(); QtWebEngineQuick::initialize();
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
// Workaround for Qt 6.5.1 crash https://bugreports.qt.io/browse/QTBUG-113832 // Workaround for Qt 6.5.1 crash https://bugreports.qt.io/browse/QTBUG-113832
qputenv("QT_DISABLE_HW_TEXTURES_CONVERSION", "1"); qputenv("QT_DISABLE_HW_TEXTURES_CONVERSION", "1");
qputenv("QT_MEDIA_BACKEND", "ffmpeg"); qputenv("QT_MEDIA_BACKEND", "ffmpeg");
#endif #endif
@ -55,8 +55,8 @@ int main(int argc, char* argv[])
// Lets center the widget // Lets center the widget
const auto* screen = QGuiApplication::screens().at(0); const auto* screen = QGuiApplication::screens().at(0);
const int offset = - 200; const int offset = -200;
QPoint center((screen->size().width() / 2) + offset, (screen->size().height() / 2)+offset); QPoint center((screen->size().width() / 2) + offset, (screen->size().height() / 2) + offset);
WidgetWindow spwmw(projectPath, WidgetWindow spwmw(projectPath,
"appid", "appid",

View File

@ -8,10 +8,12 @@ import argparse
import time import time
import zipfile import zipfile
import defines import defines
from build_result import BuildResult
from build_config import BuildConfig
from typing import Tuple from typing import Tuple
from pathlib import Path from pathlib import Path
import macos_sign import macos_sign
from util import sha256, cd_repo_root_path,repo_root_path, zipdir, run, get_vs_env_dict from util import sha256, cd_repo_root_path, repo_root_path, zipdir, run, get_vs_env_dict, get_latest_git_tag, parse_semver, semver_to_string
from sys import stdout from sys import stdout
stdout.reconfigure(encoding='utf-8') stdout.reconfigure(encoding='utf-8')
@ -25,54 +27,6 @@ def clean_build_dir(build_dir):
build_dir.mkdir(parents=True, exist_ok=True) build_dir.mkdir(parents=True, exist_ok=True)
class BuildResult:
# Windows example with absolute paths:
# [...]/build-x64-windows-release/
build: Path
# [...]/build-x64-windows-release/bin
bin: Path
# [...]/build-x64-windows-release/ScreenPlay-Installer.exe
installer: Path
# [...]/build-x64-windows-release/ScreenPlay-Installer.zip
installer_zip: Path
# [...]/build-x64-windows-release/ScreenPlay-0.X.0-RCX-x64-windows-release.zip
build_zip: Path
# [...]/build-x64-windows-release/ScreenPlay-0.X.0-RCX-x64-windows-release.txt :sha256, needed for scoop
build_hash: Path
# x64, arm64, universal
build_arch: str
class BuildConfig:
root_path: str
cmake_osx_architectures: str
cmake_target_triplet: str
package: bool
osx_bundle: str
package_command: str
executable_file_ending: str
# qt_* use either aqt or from the maintenance tool
qt_path: str # C:\Qt
qt_bin_path: str # C:\Qt\6.3.2\msvc2019_64
qt_version: str
qt_ifw_version: str
ifw_root_path: str
cmake_toolchain_file: str
aqt_install_qt_packages: str
aqt_install_tool_packages: str
executable_file_ending: str
build_folder: str
bin_dir: str
screenplay_version: str
# CMake variables need str: "ON" or "OFF"
build_steam: str
build_tests: str
build_deploy: str
build_type: str
build_architecture: str
create_installer: str
sign_osx: bool
def execute( def execute(
build_config: BuildConfig build_config: BuildConfig
) -> BuildResult: ) -> BuildResult:
@ -90,12 +44,12 @@ def execute(
# 3rd party tools like the crashreporter create local # 3rd party tools like the crashreporter create local
# temporary files in the build directory. # temporary files in the build directory.
clean_build_dir(build_config.build_folder) clean_build_dir(build_config.build_folder)
# Runs cmake configure and cmake build # Runs cmake configure and cmake build
step_time = time.time() step_time = time.time()
build_result = build(build_config, build_result) build_result = build(build_config, build_result)
build_duration = time.time() - step_time build_duration = time.time() - step_time
#print(f"⏱️ build_duration (for {build_config.build_architecture}): {build_duration}s") print(f"⏱️ build_duration: {build_duration}s")
# Copies all needed libraries and assets into the bin folder # Copies all needed libraries and assets into the bin folder
step_time = time.time() step_time = time.time()
@ -103,6 +57,11 @@ def execute(
package_duration = time.time() - step_time package_duration = time.time() - step_time
print(f"⏱️ package_duration: {package_duration}s") print(f"⏱️ package_duration: {package_duration}s")
if platform.system() == "Darwin":
if (build_config.sign_osx):
print(f"Sign binary at: {build_config.bin_dir}")
macos_sign.sign(build_config=build_config)
# Creates a Qt InstallerFrameWork (IFW) installer # Creates a Qt InstallerFrameWork (IFW) installer
if build_config.create_installer == "ON": if build_config.create_installer == "ON":
step_time = time.time() step_time = time.time()
@ -110,8 +69,14 @@ def execute(
build_installer_duration = time.time() - step_time build_installer_duration = time.time() - step_time
print(f"⏱️ build_installer_duration: {build_installer_duration}s") print(f"⏱️ build_installer_duration: {build_installer_duration}s")
# Create a zip file for scoop & chocolatey if platform.system() == "Darwin":
if platform.system() == "Windows": if (build_config.sign_osx):
print(
f"Sign ScreenPlay-installer.dmg at: {build_config.bin_dir}")
macos_sign.sign_dmg(build_config=build_config)
# Create a zip file of the build
if platform.system() != "Darwin":
step_time = time.time() step_time = time.time()
build_result = zip(build_config, build_result) build_result = zip(build_config, build_result)
zip_duration = time.time() - step_time zip_duration = time.time() - step_time
@ -122,9 +87,11 @@ def execute(
# Print BuildConfig & BuildResult member for easier debugging # Print BuildConfig & BuildResult member for easier debugging
print("\n🆗 BuildResult:") print("\n🆗 BuildResult:")
print(' '.join("\n- %s: \t\t%s" % item for item in vars(build_result).items())) print(' '.join("\n- %s: \t\t%s" %
item for item in vars(build_result).items()))
print("\n⚙️ BuildConfig:") print("\n⚙️ BuildConfig:")
print(' '.join("\n- %s: \t\t%s" % item for item in vars(build_config).items())) print(' '.join("\n- %s: \t\t%s" %
item for item in vars(build_config).items()))
return build_result return build_result
@ -134,10 +101,13 @@ def setup(build_config: BuildConfig) -> Tuple[BuildConfig, BuildResult]:
build_config.qt_path = defines.QT_PATH build_config.qt_path = defines.QT_PATH
if not build_config.qt_path.exists(): if not build_config.qt_path.exists():
print(f"Qt path does not exist at {build_config.qt_path}. Please make sure to run setup.py!") print(
f"Qt path does not exist at {build_config.qt_path}. Please make sure to run setup.py!")
exit(2) exit(2)
build_config.qt_bin_path = Path(build_config.qt_path).joinpath(f"{build_config.qt_version}/{defines.QT_PLATFORM}").resolve() build_config.qt_bin_path = Path(build_config.qt_path).joinpath(
build_config.ifw_root_path = Path(f"{build_config.qt_path}/Tools/QtInstallerFramework/{build_config.qt_ifw_version}").resolve() f"{build_config.qt_version}/{defines.QT_PLATFORM}").resolve()
build_config.ifw_root_path = Path(
f"{build_config.qt_path}/Tools/QtInstallerFramework/{build_config.qt_ifw_version}").resolve()
if platform.system() == "Windows": if platform.system() == "Windows":
build_config.cmake_target_triplet = "x64-windows" build_config.cmake_target_triplet = "x64-windows"
@ -157,10 +127,10 @@ def setup(build_config: BuildConfig) -> Tuple[BuildConfig, BuildResult]:
build_config.cmake_target_triplet = "64-osx-universal" build_config.cmake_target_triplet = "64-osx-universal"
build_config.executable_file_ending = ".app" build_config.executable_file_ending = ".app"
# NO f string we fill it later! # NO f string we fill it later!
#build_config.package_command = "{prefix_path}/bin/macdeployqt ScreenPlay.app -qmldir=../../{app}/qml -executable=ScreenPlay.app/Contents/MacOS/{app} -appstore-compliant -timestamp -hardened-runtime" # build_config.package_command = "{prefix_path}/bin/macdeployqt ScreenPlay.app -qmldir=../../{app}/qml -executable=ScreenPlay.app/Contents/MacOS/{app} -appstore-compliant -timestamp -hardened-runtime"
build_config.aqt_install_qt_packages = f"mac desktop {build_config.qt_version} clang_64 -m all" build_config.aqt_install_qt_packages = f"mac desktop {build_config.qt_version} clang_64 -m all"
build_config.aqt_install_tool_packages = "mac desktop tools_ifw" build_config.aqt_install_tool_packages = "mac desktop tools_ifw"
elif platform.system() == "Linux": elif platform.system() == "Linux":
build_config.cmake_target_triplet = "x64-linux" build_config.cmake_target_triplet = "x64-linux"
build_config.executable_file_ending = "" build_config.executable_file_ending = ""
@ -171,21 +141,27 @@ def setup(build_config: BuildConfig) -> Tuple[BuildConfig, BuildResult]:
"Unsupported platform, ScreenPlay only supports Windows, macOS and Linux.") "Unsupported platform, ScreenPlay only supports Windows, macOS and Linux.")
# Prepare # Prepare
build_config.cmake_toolchain_file = Path(f"{build_config.root_path}/../vcpkg/scripts/buildsystems/vcpkg.cmake").resolve() build_config.cmake_toolchain_file = Path(
f"{build_config.root_path}/../vcpkg/scripts/buildsystems/vcpkg.cmake").resolve()
print(f"cmake_toolchain_file: {build_config.cmake_toolchain_file}") print(f"cmake_toolchain_file: {build_config.cmake_toolchain_file}")
print(f"Starting build with type {build_config.build_type}.") print(f"Starting build with type {build_config.build_type}.")
print(f"Qt Version: {build_config.qt_version}. Root path: {build_config.root_path}") print(
f"Qt Version: {build_config.qt_version}. Root path: {build_config.root_path}")
# Remove old build folder to before configuring to get rid of all cmake chaches # Remove old build folder to before configuring to get rid of all cmake chaches
build_config.build_folder = build_config.root_path.joinpath(f"build-{build_config.cmake_target_triplet}-{build_config.build_type}") build_config.build_folder = build_config.root_path.joinpath(
f"build-{build_config.cmake_target_triplet}-{build_config.build_type}")
build_config.bin_dir = build_config.build_folder.joinpath("bin") build_config.bin_dir = build_config.build_folder.joinpath("bin")
if platform.system() == "Windows": if platform.system() == "Windows":
build_result.installer = Path(build_config.build_folder).joinpath("ScreenPlay-Installer.exe") build_result.installer = Path(build_config.build_folder).joinpath(
"ScreenPlay-Installer.exe")
elif platform.system() == "Darwin": elif platform.system() == "Darwin":
build_result.installer = Path(build_config.build_folder).joinpath("ScreenPlay.dmg") build_result.installer = Path(
build_config.build_folder).joinpath("ScreenPlay.dmg")
elif platform.system() == "Linux": elif platform.system() == "Linux":
build_result.installer = Path(build_config.build_folder).joinpath("ScreenPlay-Installer.run") build_result.installer = Path(build_config.build_folder).joinpath(
"ScreenPlay-Installer.run")
return build_config, build_result return build_config, build_result
@ -201,7 +177,7 @@ def build(build_config: BuildConfig, build_result: BuildResult) -> BuildResult:
-DSCREENPLAY_DEPLOY={build_config.build_deploy} \ -DSCREENPLAY_DEPLOY={build_config.build_deploy} \
-DSCREENPLAY_INSTALLER={build_config.create_installer} \ -DSCREENPLAY_INSTALLER={build_config.create_installer} \
-DSCREENPLAY_IFW_ROOT:STRING={build_config.ifw_root_path} \ -DSCREENPLAY_IFW_ROOT:STRING={build_config.ifw_root_path} \
-G "CodeBlocks - Ninja" \ -G "Ninja" \
-B.' -B.'
print(f"\n⚙️ CMake configure:\n", cmake_configure_command.replace( print(f"\n⚙️ CMake configure:\n", cmake_configure_command.replace(
@ -220,20 +196,21 @@ def package(build_config: BuildConfig):
if platform.system() == "Darwin": if platform.system() == "Darwin":
# Make sure to reset to tools path # Make sure to reset to tools path
os.chdir(repo_root_path()) os.chdir(repo_root_path())
cmd_raw = "{qt_bin_path}/macdeployqt {build_bin_dir}/ScreenPlay.app -qmldir={repo_root_path}/{app}/qml -executable={build_bin_dir}/ScreenPlay.app/Contents/MacOS/{app} -verbose=1 -appstore-compliant -timestamp -hardened-runtime " # -sign-for-notarization=\"Developer ID Application: Elias Steurer (V887LHYKRH)\" # -sign-for-notarization=\"Developer ID Application: Elias Steurer (V887LHYKRH)\"
build_bin_dir = Path(repo_root_path()).joinpath(f"{build_config.build_folder}/bin/") cmd_raw = "{qt_bin_path}/macdeployqt {build_bin_dir}/ScreenPlay.app -qmldir={repo_root_path}/{app}/qml -executable={build_bin_dir}/ScreenPlay.app/Contents/MacOS/{app} -verbose=1 -appstore-compliant -timestamp -hardened-runtime "
cwd = Path(repo_root_path()).joinpath(f"{build_bin_dir}/ScreenPlay.app/Contents/MacOS/") build_bin_dir = Path(repo_root_path()).joinpath(
f"{build_config.build_folder}/bin/")
cwd = Path(repo_root_path()).joinpath(
f"{build_bin_dir}/ScreenPlay.app/Contents/MacOS/")
qt_bin_path = Path(defines.QT_BIN_PATH).resolve() qt_bin_path = Path(defines.QT_BIN_PATH).resolve()
source_path = Path(repo_root_path()).resolve() source_path = Path(repo_root_path()).resolve()
run(cmd=cmd_raw.format(qt_bin_path=qt_bin_path,repo_root_path=source_path, build_bin_dir=build_bin_dir, app="ScreenPlay"), cwd=cwd) run(cmd=cmd_raw.format(qt_bin_path=qt_bin_path, repo_root_path=source_path,
run(cmd=cmd_raw.format(qt_bin_path=qt_bin_path,repo_root_path=source_path, build_bin_dir=build_bin_dir, app="ScreenPlayWallpaper"), cwd=cwd) build_bin_dir=build_bin_dir, app="ScreenPlay"), cwd=cwd)
run(cmd=cmd_raw.format(qt_bin_path=qt_bin_path,repo_root_path=source_path, build_bin_dir=build_bin_dir, app="ScreenPlayWidget"), cwd=cwd) run(cmd=cmd_raw.format(qt_bin_path=qt_bin_path, repo_root_path=source_path,
build_bin_dir=build_bin_dir, app="ScreenPlayWallpaper"), cwd=cwd)
if(build_config.sign_osx): run(cmd=cmd_raw.format(qt_bin_path=qt_bin_path, repo_root_path=source_path,
print(f"Sign binary at: {build_config.bin_dir}") build_bin_dir=build_bin_dir, app="ScreenPlayWidget"), cwd=cwd)
macos_sign.sign(build_config=build_config)
if platform.system() == "Windows": if platform.system() == "Windows":
print("Executing deploy commands...") print("Executing deploy commands...")
@ -291,8 +268,6 @@ def package(build_config: BuildConfig):
shutil.copy(str(file), str(build_config.bin_dir)) shutil.copy(str(file), str(build_config.bin_dir))
print("Copied %s" % file) print("Copied %s" % file)
# Some dlls like openssl do no longer get copied automatically. # Some dlls like openssl do no longer get copied automatically.
# Lets just copy all of them into bin. # Lets just copy all of them into bin.
if platform.system() == "Windows": if platform.system() == "Windows":
@ -326,6 +301,7 @@ def build_installer(build_config: BuildConfig, build_result: BuildResult):
print("Running cpack at: ", os.getcwd()) print("Running cpack at: ", os.getcwd())
run("cpack", cwd=build_config.build_folder) run("cpack", cwd=build_config.build_folder)
def zip(build_config: BuildConfig, build_result: BuildResult) -> BuildResult: def zip(build_config: BuildConfig, build_result: BuildResult) -> BuildResult:
zipName = f"ScreenPlay-{build_config.screenplay_version}-{build_config.cmake_target_triplet}-{build_config.build_type}.zip" zipName = f"ScreenPlay-{build_config.screenplay_version}-{build_config.cmake_target_triplet}-{build_config.build_type}.zip"
build_result.build_zip = Path(build_result.build).joinpath(zipName) build_result.build_zip = Path(build_result.build).joinpath(zipName)
@ -346,9 +322,11 @@ def zip(build_config: BuildConfig, build_result: BuildResult) -> BuildResult:
# Some weird company firewalls do not allow direct .exe downloads # Some weird company firewalls do not allow direct .exe downloads
# lets just zip the installer lol # lets just zip the installer lol
if build_config.create_installer == "ON": if build_config.create_installer == "ON":
build_result.installer_zip = Path(build_result.build).joinpath(build_result.installer.stem + ".zip") build_result.installer_zip = Path(build_result.build).joinpath(
build_result.installer.stem + ".zip")
print(f"Create zip from installer: {build_result.installer_zip}") print(f"Create zip from installer: {build_result.installer_zip}")
zipfile.ZipFile(build_result.installer_zip, 'w').write(build_result.installer, build_result.installer.name) zipfile.ZipFile(build_result.installer_zip, 'w').write(
build_result.installer, build_result.installer.name)
return build_result return build_result
@ -357,35 +335,47 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Build and Package ScreenPlay') description='Build and Package ScreenPlay')
parser.add_argument('--qt-version', action="store", dest="qt_version_overwrite",
parser.add_argument('-qt-version', action="store", dest="qt_version_overwrite",
help="Overwrites the default Qt version") help="Overwrites the default Qt version")
parser.add_argument('-qt-installer-version', action="store", dest="qt_installer_version_overwrite", parser.add_argument('--qt-installer-version', action="store", dest="qt_installer_version_overwrite",
help="Overwrites the default Qt installer framework version") help="Overwrites the default Qt installer framework version")
parser.add_argument('-type', action="store", dest="build_type", default="release", parser.add_argument('--type', action="store", dest="build_type", default="release",
help="Build type. This is either debug or release.") help="Build type. This is either debug or release.")
parser.add_argument('-use-aqt', action="store_true", dest="use_aqt", parser.add_argument('--use-aqt', action="store_true", dest="use_aqt",
help="Absolute qt path. If not set the default path is used\Windows: C:\Qt\nLinux & macOS:~/Qt/.") help="Absolute qt path. If not set the default path is used\Windows: C:\Qt\nLinux & macOS:~/Qt/.")
parser.add_argument('-steam', action="store_true", dest="build_steam", parser.add_argument('--steam', action="store_true", dest="build_steam",
help="Enable if you want to build the Steam workshop plugin.") help="Enable if you want to build the Steam workshop plugin.")
parser.add_argument('-tests', action="store_true", dest="build_tests", parser.add_argument('--tests', action="store_true", dest="build_tests",
help="Build tests.") help="Build tests.")
parser.add_argument('-installer', action="store_true", dest="create_installer", parser.add_argument('--installer', action="store_true", dest="create_installer",
help="Create a installer.") help="Create a installer.")
parser.add_argument('-sign_osx', action="store_true", dest="sign_osx", default=False, parser.add_argument('--sign_osx', action="store_true", dest="sign_osx", default=False,
help="Signs the executable on macOS. This requires a valid Apple Developer ID set up.") help="Signs the executable on macOS. This requires a valid Apple Developer ID set up.")
parser.add_argument('-deploy-version', action="store_true", dest="build_deploy", parser.add_argument('--deploy-version', action="store_true", dest="build_deploy",
help="Create a deploy version of ScreenPlay for sharing with the world. A not deploy version is for local development only!") help="Create a deploy version of ScreenPlay for sharing with the world. A not deploy version is for local development only!")
parser.add_argument('-architecture', action="store", dest="build_architecture", default="", parser.add_argument('--architecture', action="store", dest="build_architecture", default="",
help="Sets the build architecture. Used to build x86 and ARM osx versions. Currently only works with x86_64 and arm64") help="Sets the build architecture. Used to build x86 and ARM osx versions. Currently only works with x86_64 and arm64")
args = parser.parse_args() args = parser.parse_args()
qt_version = defines.QT_VERSION qt_version = defines.QT_VERSION
screenplay_version = defines.SCREENPLAY_VERSION
qt_ifw_version = defines.QT_IFW_VERSION # Not yet used. qt_ifw_version = defines.QT_IFW_VERSION # Not yet used.
qt_version_overwrite: str qt_version_overwrite: str
use_aqt = False use_aqt = False
tag = get_latest_git_tag()
if tag:
print(f"Latest Git tag: {tag}")
semver = parse_semver(tag)
if semver:
print(f"Parsed SemVer: {semver}")
screenplay_version = semver_to_string(semver)
else:
print("Failed to parse SemVer.")
exit(-1)
else:
print("No git tags found.")
exit(-1)
if args.qt_version_overwrite: if args.qt_version_overwrite:
qt_version = args.qt_version_overwrite qt_version = args.qt_version_overwrite
print("Using Qt version {qt_version}") print("Using Qt version {qt_version}")

View File

@ -1,107 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import steam_publish
import shutil
import sys
import macos_sign
import argparse
import os
import build
from pathlib import Path
import platform
import paramiko
import defines
from util import sftp_exists, run, repo_root_path
from sys import stdout
stdout.reconfigure(encoding='utf-8')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Build and Package ScreenPlay')
parser.add_argument('-skip_publish', '-skp', action="store_true", dest="skip_publish", default=False, help="skip publish")
parser.add_argument('-skip_build', '-skb', action="store_true", dest="skip_build", default=False, help="skip build. If we already have a build and only want to upload it")
parser.add_argument('-steam_password', '-sp', action="store", dest="steam_password", help="Steam password")
parser.add_argument('-hosting_username','-hu', action="store", dest="hosting_username", help="ssh username")
parser.add_argument('-hosting_password', '-hp', action="store", dest="hosting_password", help="ssh password")
args = parser.parse_args()
# Script needs to run in the tools folder
tools_path = Path.cwd()
os.chdir(tools_path)
root_path = tools_path.parent
print(f"Set root directory to: {root_path}")
build_result = build.BuildResult()
build_config = build.BuildConfig()
build_config.screenplay_version = defines.SCREENPLAY_VERSION
build_config.qt_version = defines.QT_VERSION
build_config.qt_ifw_version = defines.QT_IFW_VERSION
build_config.build_steam = "ON"
build_config.build_tests = "OFF"
build_config.build_deploy = "ON"
build_config.create_installer = "ON"
build_config.build_type = "release"
if platform.system() == "Darwin" and not args.skip_build:
# We do not yet support a standalone osx installer
build_config.create_installer = "OFF"
# We need to manually package here at the end after
build_config.package = True
build_config.sign_osx = True
# This will build both arm64 and x64 and sign the unversal binary
build_result = build.execute(build_config)
if platform.system() == "Windows" and not args.skip_build:
build_config.build_architecture = "x64"
if not args.skip_publish:
# Steamless version first
build_config.build_steam = "OFF"
build_result = build.execute(build_config)
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('kelteseth.com', username=args.hosting_username, password=args.hosting_password)
sftp = ssh.open_sftp()
release_folder = "/kelteseth_com/public/releases/" + build_config.screenplay_version + "/"
if sftp_exists(sftp,release_folder):
remoteFiles = sftp.listdir(path=release_folder)
for file in remoteFiles:
print(f"Delte old: {release_folder+file}")
sftp.remove(release_folder+file)
else:
sftp.mkdir(release_folder)
print("Uploading files...")
sftp.put(build_result.build_zip, release_folder + str(build_result.build_zip.name))
sftp.put(build_result.installer, release_folder + str(build_result.installer.name))
sftp.put(build_result.installer_zip,release_folder + str(build_result.installer_zip.name))
sftp.put(build_result.build_hash, release_folder + str(build_result.build_hash.name))
sftp.close()
ssh.close()
# Now build the steam version
os.chdir(tools_path)
build_config.build_steam = "ON"
build_config.create_installer = "OFF"
build_result = build.execute(build_config)
if args.skip_publish:
print("Skip publishing.")
sys.exit(0)
if args.steam_password is None:
print("Steam password is required.")
sys.exit(1)
# Make sure to reset to tools path
os.chdir(tools_path)
steam_publish.publish(
steam_username="tachiom",
steam_password=args.steam_password,
set_live_branch_name="internal"
)

View File

@ -0,0 +1,94 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import steam_publish
import sys
import argparse
import os
import build
from pathlib import Path
import platform
import defines
from build_result import BuildResult
from build_config import BuildConfig
from util import get_latest_git_tag, parse_semver, semver_to_string
from sys import stdout
stdout.reconfigure(encoding='utf-8')
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Build and Package ScreenPlay')
parser.add_argument('--skip_steam_publish', '-skstp', action="store_true",
dest="skip_steam_publish", default=False, help="skip publish")
parser.add_argument('--skip_build', '-skb', action="store_true", dest="skip_build",
default=False, help="skip build. If we already have a build and only want to upload it")
parser.add_argument('--steam_password', '-sp', action="store",
dest="steam_password", help="Steam password")
parser.add_argument('--hosting_username', '-hu', action="store",
dest="hosting_username", help="ssh username")
parser.add_argument('--hosting_password', '-hp', action="store",
dest="hosting_password", help="ssh password")
args = parser.parse_args()
if not args.skip_steam_publish and args.steam_password is None:
print("Steam password is required.")
sys.exit(1)
if not steam_publish.check_steam_login("tachiom", args.steam_password):
print("Failed to login to Steam!")
exit(1)
# Script needs to run in the tools folder
tools_path = Path.cwd()
os.chdir(tools_path)
root_path = tools_path.parent
print(f"Set root directory to: {root_path}")
tag = get_latest_git_tag()
if tag:
print(f"Latest Git tag: {tag}")
semver = parse_semver(tag)
if semver:
print(f"Parsed SemVer: {semver}")
screenplay_version = semver_to_string(semver)
else:
print("Failed to parse SemVer.")
exit(-1)
else:
print("No git tags found.")
exit(-1)
build_result = BuildResult()
build_config = BuildConfig()
build_config.qt_version = defines.QT_VERSION
build_config.qt_ifw_version = defines.QT_IFW_VERSION
build_config.build_steam = "ON"
build_config.build_tests = "OFF"
build_config.build_deploy = "ON"
build_config.create_installer = "OFF"
build_config.build_type = "release"
build_config.screenplay_version = screenplay_version
if platform.system() == "Darwin" and not args.skip_build:
# We need to manually package here at the end after
build_config.package = True
build_config.sign_osx = True
# This will build both arm64 and x64 and sign the unversal binary
build_result = build.execute(build_config)
if platform.system() == "Windows" and not args.skip_build:
build_result = build.execute(build_config)
if args.skip_steam_publish:
print("Skip steam publishing.")
sys.exit(0)
# Make sure to reset to tools path
os.chdir(tools_path)
steam_publish.publish(
steam_username="tachiom",
steam_password=args.steam_password,
set_live_branch_name="internal"
)

29
Tools/build_config.py Normal file
View File

@ -0,0 +1,29 @@
class BuildConfig:
root_path: str
cmake_osx_architectures: str
cmake_target_triplet: str
package: bool
osx_bundle: str
package_command: str
executable_file_ending: str
# qt_* use either aqt or from the maintenance tool
qt_path: str # C:\Qt
qt_bin_path: str # C:\Qt\6.3.2\msvc2019_64
qt_version: str
qt_ifw_version: str
ifw_root_path: str
cmake_toolchain_file: str
aqt_install_qt_packages: str
aqt_install_tool_packages: str
executable_file_ending: str
build_folder: str
bin_dir: str
screenplay_version: str
# CMake variables need str: "ON" or "OFF"
build_steam: str
build_tests: str
build_deploy: str
build_type: str
build_architecture: str
create_installer: str
sign_osx: bool

20
Tools/build_result.py Normal file
View File

@ -0,0 +1,20 @@
from pathlib import Path
class BuildResult:
# Windows example with absolute paths:
# [...]/build-x64-windows-release/
build: Path
# [...]/build-x64-windows-release/bin
bin: Path
# [...]/build-x64-windows-release/ScreenPlay-Installer.exe
installer: Path
# [...]/build-x64-windows-release/ScreenPlay-Installer.zip
installer_zip: Path
# [...]/build-x64-windows-release/ScreenPlay-0.X.0-RCX-x64-windows-release.zip
build_zip: Path
# [...]/build-x64-windows-release/ScreenPlay-0.X.0-RCX-x64-windows-release.txt :sha256, needed for scoop
build_hash: Path
# x64, arm64, universal
build_arch: str

View File

@ -0,0 +1,56 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import os
import argparse
import util
from format_util import find_files
from format_util import execute_threaded
from sys import stdout
stdout.reconfigure(encoding='utf-8')
def format_file_function(file):
executable = "cmake-format"
if os.name == 'nt':
executable += ".exe"
os.system(" %s -c ../.cmake-format.py -i %s" % (executable, file))
print("Format: ", file)
def check_format_file_function(file):
executable = "cmake-format"
if os.name == 'nt':
executable += ".exe"
result = os.system(" %s -c ../.cmake-format.py --check %s" %
(executable, file))
# If the return code is non-zero, the file isn't formatted correctly
if result != 0:
print(f"{file} is not correctly formatted.")
return False
return True
def main(git_staged_only=False, check_only=False):
file_list = find_files(
('CMakeLists.txt', '*.cmake'), util.repo_root_path(), git_staged_only)
if check_only:
result = execute_threaded(file_list, check_format_file_function)
if not result: # Since result is a single boolean, you can directly check its value
print("Some files are not correctly formatted!")
exit(1)
else:
execute_threaded(file_list, format_file_function)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--stage-only', action="store_true", dest="stage_only", default=False,
help="Check/format only staged files")
parser.add_argument('-c', '--check', action="store_true", dest="check_only", default=False,
help="Only check if files are correctly formatted without actually formatting them")
args = parser.parse_args()
main(args.stage_only, args.check_only)

61
Tools/check_format_cpp.py Normal file
View File

@ -0,0 +1,61 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import os
import subprocess
import argparse
import util
from format_util import find_files
from format_util import execute_threaded
from sys import stdout
stdout.reconfigure(encoding='utf-8')
def format_file_function(file):
executable = "clang-format"
if os.name == 'nt':
executable = "clang-format.exe"
process = subprocess.run(" %s -style=file -i %s" %
(executable, file), capture_output=True, shell=True)
print("Format: %s \t return: %s" % (file, process.returncode))
def check_format_file_function(file):
executable = "clang-format"
if os.name == 'nt':
executable += ".exe"
result = subprocess.run(" %s -style=file --output-replacements-xml %s" %
(executable, file), capture_output=True, shell=True, text=True)
# Check for opening replacement tag with attributes (a space after it indicates attributes)
if "<replacement " in result.stdout:
print(f"{file} is not correctly formatted.")
print(f"{result.stdout} ")
return False
return True
def main(git_staged_only=False, check_only=False):
exclude_folders = ("ScreenPlayWorkshop/SteamSDK",
"ThirdParty", "build-x64-windows-release")
file_list = find_files(
('.cpp', '.h'), util.repo_root_path(), git_staged_only, exclude_folders)
if check_only:
result = execute_threaded(file_list, check_format_file_function)
if not result: # Since result is a single boolean, you can directly check its value
print("Some files are not correctly formatted!")
exit(1)
else:
execute_threaded(file_list, format_file_function)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--s', '--stage-only', action="store_true", dest="stage_only", default=False,
help="Check/format only staged files")
parser.add_argument('--c', '--check', action="store_true", dest="check_only", default=False,
help="Only check if files are correctly formatted without actually formatting them")
args = parser.parse_args()
main(args.stage_only, args.check_only)

88
Tools/check_format_qml.py Normal file
View File

@ -0,0 +1,88 @@
#!/usr/bin/python3
import os
import subprocess
import argparse
import util
import defines
import hashlib
from format_util import find_files
from format_util import execute_threaded
from sys import stdout, exit
import hashlib
import shutil
stdout.reconfigure(encoding='utf-8')
def format_qml_file(file):
executable = "qmlformat"
if os.name == 'nt':
executable = "qmlformat.exe"
qt_bin_path = defines.QT_BIN_PATH
executable = qt_bin_path.joinpath(executable)
# Add -i for formatting in place
process = subprocess.run(
[executable, "-i", file], capture_output=True, shell=True)
print("Format: %s \t return: %s" % (file, process.returncode))
def compute_md5(file_path):
"""Compute MD5 hash of the content of the given file."""
with open(file_path, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
# Instead of comparing the direct outputs (which was leading to ambiguous results),
# this function uses a workaround that checks the file's MD5 hash before and after formatting.
def check_format_qml_file(file):
executable = "qmlformat"
if os.name == 'nt':
executable = "qmlformat.exe"
qt_bin_path = defines.QT_BIN_PATH
executable = qt_bin_path.joinpath(executable)
# Step 1: Copy the original file
backup_file = file + ".backup"
shutil.copy(file, backup_file)
# Step 2: Run qmlformat with in-place editing
subprocess.run([executable, "-i", file], check=True)
# Step 3: Compare MD5 hash of the original (backup) and the formatted file
original_hash = compute_md5(backup_file)
formatted_hash = compute_md5(file)
# Step 4: Remove the backup copy
os.remove(backup_file)
# Step 5: Return the comparison result
if original_hash != formatted_hash:
print(f"{file} is not correctly formatted.")
return False
return True
def main(git_staged_only=False, check_only=False):
exclude_folders = ("ThirdParty", "build-x64-windows-release")
file_list = find_files(('.qml'), util.repo_root_path(),
git_staged_only, exclude_folders)
if check_only:
result = execute_threaded(file_list, check_format_qml_file)
if not result:
print("Some files are not correctly formatted!")
exit(1)
else:
execute_threaded(file_list, format_qml_file)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--s', '--stage-only', action="store_true", dest="stage_only", default=False,
help="Check/format only staged files")
parser.add_argument('--c', '--check', action="store_true", dest="check_only", default=False,
help="Only check if files are correctly formatted without actually formatting them")
args = parser.parse_args()
main(args.stage_only, args.check_only)

View File

@ -0,0 +1,61 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import argparse
from format_util import find_files
from format_util import execute_threaded
from sys import stdout
import util
stdout.reconfigure(encoding='utf-8')
def check_license_header_file_function(file_path):
"""
Check if the given file has a license header in its first or second line.
Parameters:
- file_path (str): Path to the file to be checked.
Returns:
- bool: True if the license header is present, otherwise False.
"""
try:
with open(file_path, 'r') as f:
# Read the first two lines of the file
first_line = f.readline().strip()
second_line = f.readline().strip()
# Check if either of the two lines has the required header
if "SPDX-License-Identifier:" in first_line or "SPDX-License-Identifier:" in second_line:
return True
else:
print(f"INVALID: {file_path}")
return False
except Exception as e:
print(f"Error reading the file: {e}")
return False
def main(git_staged_only=False):
# Setting exclude_folders before calling the function
exclude_folders = (
"ThirdParty", "ScreenPlayWorkshop/SteamSDK", "build-x64-windows-release")
file_endings = ('.cpp', '.h', '.qml')
file_list = find_files(file_endings_tuple=file_endings,
path=util.repo_root_path(),
git_staged_only=git_staged_only,
exclude_folders=exclude_folders)
result = execute_threaded(file_list, check_license_header_file_function)
if result is False:
print("At least one file is missing the license header!")
# Depending on your requirement, you can exit the script here
exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--s", '--stage-only', action="store_true", dest="stage_only",
default=False)
args = parser.parse_args()
main(args.stage_only)

View File

@ -1,36 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import os
import subprocess
import argparse
from format_util import find_files
from format_util import check_git_exit
from format_util import execute_threaded
from sys import stdout
stdout.reconfigure(encoding='utf-8')
def format_file_function(file):
executable = "clang-format"
if os.name == 'nt':
executable = "clang-format.exe"
process = subprocess.run(" %s -style=file -i %s" %
(executable, file), capture_output=True, shell=True)
print("Format: %s \t return: %s" % (file, process.returncode))
def main(git_staged_only=False):
file_list = find_files(('.cpp', '.h'), "", git_staged_only)
execute_threaded(file_list, format_file_function)
if not git_staged_only:
check_git_exit("Clang Format")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-s', action="store_true", dest="stage_only",
default=False)
args = parser.parse_args()
main(args.stage_only)

View File

@ -1,31 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import os
import argparse
from format_util import find_files
from format_util import check_git_exit
from format_util import execute_threaded
from sys import stdout
stdout.reconfigure(encoding='utf-8')
def format_file_function(file):
executable = "cmake-format"
if os.name == 'nt':
executable = "cmake-format.exe"
os.system(" %s -c ../.cmake-format.py -i %s" % (executable, file))
print("Format: ", file)
def main(git_staged_only=False):
file_list = find_files(('CMakeLists.txt'), "", git_staged_only)
execute_threaded(file_list, format_file_function)
if not git_staged_only:
check_git_exit("CMake Format")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-s', action="store_true", dest="stage_only",
default=False)
args = parser.parse_args()
main(args.stage_only)

30
Tools/create_sha512.py Normal file
View File

@ -0,0 +1,30 @@
import hashlib
import os
from pathlib import Path
import util
def combine_sha256():
tag = os.environ['CI_COMMIT_TAG']
# Get the repo root path as a Path object
root_path = Path(util.repo_root_path())
files = [
Path(
f"{root_path}/build-x64-windows-release/ScreenPlay-{tag}-x64-windows-release.zip"),
Path(
f"{root_path}/build-x64-linux-release/ScreenPlay-{tag}-x64-linux-release.zip"),
Path(f"{root_path}/build-64-osx-universal-release/ScreenPlay-{tag}-64-osx-universal-release.zip")
]
with open('SHA512-SUMS.txt', 'w') as f_out:
for file in files:
with open(file.with_name(f"{file.name}.sha256.txt"), 'r') as f_in:
sha256_hash = f_in.read().strip()
sha512_hash = hashlib.sha512(sha256_hash.encode()).hexdigest()
f_out.write(f"{sha512_hash} {file.name}\n")
if __name__ == "__main__":
combine_sha256()

View File

@ -18,7 +18,6 @@ elif sys.platform == "linux":
OS = "linux" OS = "linux"
QT_PLATFORM = "gcc_64" QT_PLATFORM = "gcc_64"
SCREENPLAY_VERSION = "0.15.1"
QT_PATH = path = Path(os.path.join( QT_PATH = path = Path(os.path.join(
os.path.realpath(__file__), "../../../aqt")).resolve() os.path.realpath(__file__), "../../../aqt")).resolve()
QT_VERSION = "6.5.2" QT_VERSION = "6.5.2"

View File

@ -3,6 +3,7 @@
import subprocess import subprocess
import sys import sys
import os import os
import util
from multiprocessing import cpu_count from multiprocessing import cpu_count
from multiprocessing import Pool from multiprocessing import Pool
from multiprocessing import dummy from multiprocessing import dummy
@ -28,33 +29,38 @@ def find_all_git_staged_files():
return out return out
def find_files(file_endings_tuple, path="", git_staged_only=False): def find_files(file_endings_tuple, path="", git_staged_only=False, exclude_folders=()):
file_list = [] file_list = []
# Get the root folder by moving one up
root = Path(__file__).parent.absolute()
root = os.path.abspath(os.path.join(root, "../"))
root = os.path.join(root, path)
print(root)
if git_staged_only: if git_staged_only:
file_list = find_all_git_staged_files() file_list = find_all_git_staged_files() # Assuming you've defined this
else: else:
file_list = find_all_files(root) for dirpath, dirnames, filenames in os.walk(path):
# Normalize the current directory path
norm_dirpath = os.path.normpath(dirpath)
filtered_file_list = [] # Check if the current directory is to be excluded
for filename in file_list: if exclude_folders and any(os.path.normpath(excl_folder) in norm_dirpath for excl_folder in exclude_folders):
if filename.endswith(file_endings_tuple): continue
filtered_file_list.append(os.path.join(root, filename))
return filtered_file_list for filename in filenames:
if filename.endswith(file_endings_tuple):
file_list.append(os.path.join(dirpath, filename))
return file_list
def execute_threaded(file_list, format_file_function): def execute_threaded(file_list, format_file_function):
p = Pool(cpu_count()) if not file_list:
p.map(format_file_function, file_list) return True # or False, depending on how you want to handle an empty list
p.close()
p.join() with Pool(cpu_count()) as p:
results = p.map(format_file_function, file_list)
# Check if any result is False and return accordingly
if any(result is False for result in results):
return False
return True
def check_git_exit(caller_name): def check_git_exit(caller_name):
@ -70,4 +76,3 @@ def check_git_exit(caller_name):
out.replace("\\n", "\n") out.replace("\\n", "\n")
# print(out) # print(out)
sys.exit(1) sys.exit(1)

View File

@ -1,32 +1,32 @@
#!/usr/bin/python3 #!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only # SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
from build import BuildConfig from build_config import BuildConfig
from util import run from util import run
from sys import stdout from sys import stdout
import time
stdout.reconfigure(encoding='utf-8') stdout.reconfigure(encoding='utf-8')
def sign(build_config: BuildConfig): def sign(build_config: BuildConfig):
print("Run codedesign") print("Run codedesign")
#run("codesign -f -s 'Developer ID Application: Elias Steurer (V887LHYKRH)' --verbose --force --timestamp --options 'runtime' -f --entitlements '../../ScreenPlay/entitlements.plist' 'ScreenPlay.app/' ", # run("codesign -f -s 'Developer ID Application: Elias Steurer (V887LHYKRH)' --verbose --force --timestamp --options 'runtime' -f --entitlements '../../ScreenPlay/entitlements.plist' 'ScreenPlay.app/' ",
# cwd=build_config.bin_dir) # cwd=build_config.bin_dir)
# Do not use --deep https://developer.apple.com/forums/thread/129980 # Do not use --deep https://developer.apple.com/forums/thread/129980
# base_sign_command = "codesign -s \"Developer ID Application: Elias Steurer (V887LHYKRH)\" --verbose --force --timestamp --options \"runtime\" \"ScreenPlay.app/Contents/MacOS/{app}\"" # base_sign_command = "codesign -s \"Developer ID Application: Elias Steurer (V887LHYKRH)\" --verbose --force --timestamp --options \"runtime\" \"ScreenPlay.app/Contents/MacOS/{app}\""
# run(base_sign_command.format(app="ffmpeg"), cwd=build_config.bin_dir) # run(base_sign_command.format(app="ffmpeg"), cwd=build_config.bin_dir)
# run(base_sign_command.format(app="ffprobe"), cwd=build_config.bin_dir) # run(base_sign_command.format(app="ffprobe"), cwd=build_config.bin_dir)
run("codesign --deep -s \"Developer ID Application: Elias Steurer (V887LHYKRH)\" --verbose --force --timestamp --options \"runtime\" --entitlements \"../../ScreenPlay/entitlements.plist\" \"ScreenPlay.app/\"", run("codesign --deep -s \"Developer ID Application: Elias Steurer (V887LHYKRH)\" --verbose --force --timestamp --options \"runtime\" --entitlements \"../../ScreenPlay/entitlements.plist\" \"ScreenPlay.app/\"",
cwd=build_config.bin_dir) cwd=build_config.bin_dir)
print("Run codedesign verify") print("Run codedesign verify")
run("codesign --verify --verbose=4 'ScreenPlay.app/'", run("codesign --verify --verbose=4 'ScreenPlay.app/'",
cwd=build_config.bin_dir) cwd=build_config.bin_dir)
# Note the profile is the one name of the first step of (App Store Connect API) in the macOSSigning.md # Note the profile is the one name of the first step of (App Store Connect API) in the macOSSigning.md
# xcrun notarytool submit "ScreenPlay.app.zip" --keychain-profile "ScreenPlay" --wait # xcrun notarytool submit "ScreenPlay.app.zip" --keychain-profile "ScreenPlay" --wait
# xcrun stapler staple "ScreenPlay.app" # xcrun stapler staple "ScreenPlay.app"
print("Packing .apps for upload") print("Packing .apps for upload")
run("ditto -c -k --keepParent 'ScreenPlay.app' 'ScreenPlay.app.zip'", cwd=build_config.bin_dir) run("ditto -c -k --keepParent 'ScreenPlay.app' 'ScreenPlay.app.zip'",
cwd=build_config.bin_dir)
# run this if you get an error: # run this if you get an error:
# `xcrun notarytool log --apple-id "xxxxx@xxxx.com" --password "xxxx-xxxx-xxxx-xxxx" --team-id "xxxxxxxxxxx" <ID>` # `xcrun notarytool log --apple-id "xxxxx@xxxx.com" --password "xxxx-xxxx-xxxx-xxxx" --team-id "xxxxxxxxxxx" <ID>`
@ -34,32 +34,41 @@ def sign(build_config: BuildConfig):
# id: xxxxxx-xxxxxx-xxxx-xxxxx-xxxxx # id: xxxxxx-xxxxxx-xxxx-xxxxx-xxxxx
# status: Invalid # status: Invalid
print("Run xcnotary submit") print("Run xcnotary submit")
run("xcrun notarytool submit --keychain-profile 'ScreenPlay' ScreenPlay.app.zip --wait", cwd=build_config.bin_dir) run("xcrun notarytool submit --keychain-profile 'ScreenPlay' ScreenPlay.app.zip --wait",
cwd=build_config.bin_dir)
print("Run stapler staple") print("Run stapler staple")
run("xcrun stapler staple ScreenPlay.app", cwd=build_config.bin_dir) run("xcrun stapler staple ScreenPlay.app", cwd=build_config.bin_dir)
print("Run spctl assess") print("Run spctl assess")
run("spctl --assess --verbose 'ScreenPlay.app/'", cwd=build_config.bin_dir) run("spctl --assess --verbose 'ScreenPlay.app/'", cwd=build_config.bin_dir)
print("Remove *.app.zip files.") print("Remove ScreenPlay.app.zip.")
run("rm ScreenPlay.app.zip", cwd=build_config.bin_dir) run("rm ScreenPlay.app.zip", cwd=build_config.bin_dir)
# We also need to sign the installer in osx:
if build_config.create_installer == "ON":
run("codesign --deep -f -s \"Developer ID Application: Elias Steurer (V887LHYKRH)\" --timestamp --options \"runtime\" -f --deep \"ScreenPlay-Installer.dmg/ScreenPlay-Installer.app/Contents/MacOS/ScreenPlay-Installer\"", cwd=build_config.build_folder)
run("codesign --verify --verbose=4 \"ScreenPlay-Installer.dmg/ScreenPlay-Installer.app/Contents/MacOS/ScreenPlay-Installer\"",
cwd=build_config.build_folder)
run("xcnotary notarize ScreenPlay-Installer.dmg/ScreenPlay-Installer.app -d kelteseth@gmail.com -k ScreenPlay",
cwd=build_config.build_folder)
run("spctl --assess --verbose \"ScreenPlay-Installer.dmg/ScreenPlay-Installer.app/\"",
cwd=build_config.build_folder)
run("codesign --deep -f -s \"Developer ID Application: Elias Steurer (V887LHYKRH)\" --timestamp --options \"runtime\" -f --deep \"ScreenPlay-Installer.dmg/\"", cwd=build_config.build_folder) def sign_dmg(build_config: BuildConfig):
run("codesign --verify --verbose=4 \"ScreenPlay-Installer.dmg/\"", # Sign the DMG
cwd=build_config.build_folder) run("codesign -f -s \"Developer ID Application: Elias Steurer (V887LHYKRH)\" --timestamp --options \"runtime\" -f --deep \"ScreenPlay-Installer.dmg\"", cwd=build_config.build_folder)
run("xcnotary notarize ScreenPlay-Installer.dmg -d kelteseth@gmail.com -k ScreenPlay",
cwd=build_config.build_folder)
run("spctl --assess --verbose \"ScreenPlay-Installer.dmg/\"",
cwd=build_config.build_folder)
# Verify the DMG's signature
run("codesign --verify --verbose=4 \"ScreenPlay-Installer.dmg\"",
cwd=build_config.build_folder)
# Pack the DMG for notarization
run("ditto -c -k --keepParent ScreenPlay-Installer.dmg ScreenPlay-Installer.dmg.zip",
cwd=build_config.build_folder)
# Notarize the DMG using notarytool
run("xcrun notarytool submit ScreenPlay-Installer.dmg.zip --keychain-profile 'ScreenPlay' --wait",
cwd=build_config.build_folder)
# Staple the notarization ticket to the DMG
run("xcrun stapler staple ScreenPlay-Installer.dmg",
cwd=build_config.build_folder)
# Check the notarization status for the DMG
run("spctl --assess --verbose \"ScreenPlay-Installer.dmg\"",
cwd=build_config.build_folder)
# Clean up the zip file
run("rm ScreenPlay-Installer.dmg.zip", cwd=build_config.build_folder)

View File

@ -1,40 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LicenseRef-EliasSteurerTachiom OR AGPL-3.0-only
import os
import subprocess
import argparse
import sys
import defines
from format_util import find_files
from format_util import check_git_exit
from format_util import execute_threaded
from sys import stdout
stdout.reconfigure(encoding='utf-8')
def format_file_function(file):
executable = "qmlformat"
if os.name == 'nt':
executable = "qmlformat.exe"
qt_bin_path = defines.QT_BIN_PATH
executable = qt_bin_path.joinpath(executable)
process = subprocess.run(
"%s -i %s" % (executable, file), capture_output=True, shell=True)
print("Format: %s \t return: %s" % (file, process.returncode))
def main(git_staged_only=False):
file_list = find_files(('.qml'), os.path.abspath(os.path.join(os.getcwd(), "../")), git_staged_only)
execute_threaded(file_list, format_file_function)
if not git_staged_only:
check_git_exit("QML Format")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-s', action="store_true", dest="stage_only",
default=False)
args = parser.parse_args()
main(args.stage_only)

View File

@ -1,3 +1,2 @@
cmake-format cmake-format
paramiko
aqtinstall aqtinstall

View File

@ -5,6 +5,7 @@ import sys
import subprocess import subprocess
import shutil import shutil
import argparse import argparse
from pathlib import Path
from sys import platform from sys import platform
from execute_util import execute from execute_util import execute
from datetime import datetime from datetime import datetime
@ -16,6 +17,67 @@ from sys import stdout
stdout.reconfigure(encoding='utf-8') stdout.reconfigure(encoding='utf-8')
class PublishConfig:
vdf_config_name: str
depot_config_name: str
steamcmd_path: Path
def init_publish_config():
config = PublishConfig()
root_path = cd_repo_root_path()
tools_path = os.path.join(root_path, "Tools")
contentBuiler_path = os.path.join(tools_path, "Steam/ContentBuilder/")
if platform.system() == "Windows":
config.vdf_config_name = "app_build_windows.vdf"
config.depot_config_name = "depot_build_windows.vdf"
config.steamcmd_path = os.path.join(
contentBuiler_path, "builder/steamcmd.exe")
elif platform.system() == "Darwin":
config.vdf_config_name = "app_build_mac.vdf"
config.depot_config_name = "depot_build_mac.vdf"
config.steamcmd_path = os.path.join(
contentBuiler_path, "builder_osx/steamcmd")
execute(f"chmod +x {config.steamcmd_path}")
elif platform.system() == "Linux":
config.vdf_config_name = "app_build_linux.vdf"
config.depot_config_name = "depot_build_linux.vdf"
config.steamcmd_path = os.path.join(
contentBuiler_path, "builder_linux/steamcmd.sh")
execute(f"chmod +x {config.steamcmd_path}")
return config
def check_steam_login(username: str, password: str):
config = init_publish_config()
cmd = [config.steamcmd_path, "+login", username, password, "+quit"]
try:
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as process:
try:
stdout, stderr = process.communicate(
timeout=60) # 1 minute timeout
for line in stdout.splitlines():
print(line) # Print the line for debugging purposes
if "Logging in user" in line:
return True
elif "Steam Guard code" in line:
process.terminate()
return False
except subprocess.TimeoutExpired:
process.kill()
print("Steam login check timed out after 1 minute.")
return False
except Exception as e:
print(f"Error during Steam login check: {e}")
return False
# Executes steamcmd with username and password. Changes the content of the config # Executes steamcmd with username and password. Changes the content of the config
# for better readability in the steam builds tab # for better readability in the steam builds tab
# https://partner.steamgames.com/apps/builds/672870 # https://partner.steamgames.com/apps/builds/672870
@ -27,6 +89,7 @@ def get_git_revision_short_hash():
def get_git_commit_text(): def get_git_commit_text():
return subprocess.check_output(['git', 'log', '-1', '--pretty=%B']) return subprocess.check_output(['git', 'log', '-1', '--pretty=%B'])
def publish( def publish(
steam_username, steam_username,
steam_password, steam_password,
@ -36,30 +99,13 @@ def publish(
# Make sure the script is always started from the same folder # Make sure the script is always started from the same folder
root_path = cd_repo_root_path() root_path = cd_repo_root_path()
tools_path = os.path.join(root_path, "Tools") tools_path = os.path.join(root_path, "Tools")
contentBuiler_path = os.path.join(tools_path, "Steam/ContentBuilder/")
vdf_config_name = "" config = init_publish_config()
depot_config_name = ""
steamcmd_path = ""
if platform.system() == "Windows":
vdf_config_name = "app_build_windows.vdf"
depot_config_name = "depot_build_windows.vdf"
steamcmd_path = os.path.join(contentBuiler_path, "builder/steamcmd.exe")
steamcmd_path = steamcmd_path.replace("/","\\")
elif platform.system() == "Darwin":
vdf_config_name = "app_build_mac.vdf"
depot_config_name = "depot_build_mac.vdf"
steamcmd_path = os.path.join(contentBuiler_path, "builder_osx/steamcmd")
execute(f"chmod +x {steamcmd_path}")
elif platform.system() == "Linux":
vdf_config_name = "app_build_linux.vdf"
depot_config_name = "depot_build_linux.vdf"
steamcmd_path = os.path.join(contentBuiler_path, "builder_linux/steamcmd.sh")
execute(f"chmod +x {steamcmd_path}")
print(f"Set steamCmd path: {steamcmd_path}") print(f"Set steamCmd path: {config.steamcmd_path}")
abs_vdf_path = os.path.join(tools_path,"Steam/steamcmd/" + vdf_config_name) abs_vdf_path = os.path.join(
tools_path, "Steam/steamcmd/" + config.vdf_config_name)
if not os.path.isfile(abs_vdf_path): if not os.path.isfile(abs_vdf_path):
print("Incorrect vdf name") print("Incorrect vdf name")
@ -71,15 +117,19 @@ def publish(
git_hash = get_git_revision_short_hash().decode("utf-8").replace("\n", "") git_hash = get_git_revision_short_hash().decode("utf-8").replace("\n", "")
git_commit_text = get_git_commit_text().decode("utf-8").replace("\n", "") git_commit_text = get_git_commit_text().decode("utf-8").replace("\n", "")
# Remove ' and " that can occour it is a merge commit # Remove ' and " that can occour it is a merge commit
git_commit_text = git_commit_text.replace('\"','') git_commit_text = git_commit_text.replace('\"', '')
git_commit_text = git_commit_text.replace('\'','') git_commit_text = git_commit_text.replace('\'', '')
current_date_time = datetime.now().strftime("%d/%m/%Y, %H:%M:%S") current_date_time = datetime.now().strftime("%d/%m/%Y, %H:%M:%S")
build_description = "- git hash: " + git_hash + ", commit: " + git_commit_text + " - upload datetime: " + current_date_time build_description = "- git hash: " + git_hash + ", commit: " + \
config_content = config_content.replace("{{BUILD_DESCRIPTION}}", build_description) git_commit_text + " - upload datetime: " + current_date_time
config_content = config_content.replace("{{SET_LIVE_ON_BRANCH}}", set_live_branch_name) config_content = config_content.replace(
"{{BUILD_DESCRIPTION}}", build_description)
config_content = config_content.replace(
"{{SET_LIVE_ON_BRANCH}}", set_live_branch_name)
tmp_steam_config_foldername = "tmp_steam_config/" tmp_steam_config_foldername = "tmp_steam_config/"
tmp_steam_config_dir = os.path.abspath(os.path.join(tools_path,tmp_steam_config_foldername)) tmp_steam_config_dir = os.path.abspath(
os.path.join(tools_path, tmp_steam_config_foldername))
if os.path.isdir(tmp_steam_config_dir): if os.path.isdir(tmp_steam_config_dir):
print(f"Deleting tmp config {tmp_steam_config_dir}") print(f"Deleting tmp config {tmp_steam_config_dir}")
@ -87,24 +137,30 @@ def publish(
os.mkdir(tmp_steam_config_dir) os.mkdir(tmp_steam_config_dir)
f = open(os.path.abspath(tmp_steam_config_dir + "/" + vdf_config_name), "w") f = open(os.path.abspath(tmp_steam_config_dir +
"/" + config.vdf_config_name), "w")
f.write(config_content) f.write(config_content)
f.close() f.close()
print(f"Using config:\n {config_content}\n") print(f"Using config:\n {config_content}\n")
# We also must copy the depot file # We also must copy the depot file
abs_depot_path = os.path.join(tools_path, "Steam/steamcmd/" + depot_config_name) abs_depot_path = os.path.join(
copyfile(abs_depot_path, tmp_steam_config_dir + "/" + depot_config_name) tools_path, "Steam/steamcmd/" + config.depot_config_name)
copyfile(abs_depot_path, tmp_steam_config_dir +
"/" + config.depot_config_name)
tmp_steam_config_path = "\"" + os.path.abspath(os.path.join(tmp_steam_config_dir,vdf_config_name) ) + "\"" tmp_steam_config_path = "\"" + \
os.path.abspath(os.path.join(
tmp_steam_config_dir, config.vdf_config_name)) + "\""
print("Execute steamcmd on: " + tmp_steam_config_path) print("Execute steamcmd on: " + tmp_steam_config_path)
execute(f"{steamcmd_path} +login {steam_username} {steam_password} +run_app_build {tmp_steam_config_path} +quit") execute(f"{config.steamcmd_path} +login {steam_username} {steam_password} +run_app_build {tmp_steam_config_path} +quit")
print("Deleting tmp config") print("Deleting tmp config")
shutil.rmtree(tmp_steam_config_dir) shutil.rmtree(tmp_steam_config_dir)
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Publish ScreenPlay to Steam') parser = argparse.ArgumentParser(description='Publish ScreenPlay to Steam')
parser.add_argument('-steam_username', action="store", dest="steam_username", required=True, parser.add_argument('-steam_username', action="store", dest="steam_username", required=True,
@ -129,4 +185,4 @@ if __name__ == "__main__":
steam_username=args.steam_username, steam_username=args.steam_username,
steam_password=args.steam_password, steam_password=args.steam_password,
set_live_branch_name=set_live_branch_name set_live_branch_name=set_live_branch_name
) )

View File

@ -6,11 +6,13 @@ from pathlib import Path
from os import chdir from os import chdir
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
import os import os
import re
import subprocess import subprocess
from sys import stdout from sys import stdout
stdout.reconfigure(encoding='utf-8') stdout.reconfigure(encoding='utf-8')
def sftp_exists(sftp, path) -> bool: def sftp_exists(sftp, path) -> bool:
try: try:
sftp.stat(path) sftp.stat(path)
@ -24,6 +26,7 @@ def run(cmd, cwd=Path.cwd()):
if result.returncode != 0: if result.returncode != 0:
raise RuntimeError(f"Failed to execute {cmd}") raise RuntimeError(f"Failed to execute {cmd}")
def run_and_capture_output(cmd, cwd=Path.cwd()) -> str: def run_and_capture_output(cmd, cwd=Path.cwd()) -> str:
result = subprocess.run(cmd, shell=True, cwd=cwd, stdout=subprocess.PIPE) result = subprocess.run(cmd, shell=True, cwd=cwd, stdout=subprocess.PIPE)
if result.returncode != 0: if result.returncode != 0:
@ -31,14 +34,16 @@ def run_and_capture_output(cmd, cwd=Path.cwd()) -> str:
if result.stdout is not None: if result.stdout is not None:
return str(result.stdout.decode('utf-8')) return str(result.stdout.decode('utf-8'))
return "" return ""
def repo_root_path() -> str: def repo_root_path() -> str:
# Root dir of the repository # Root dir of the repository
path = os.path.join(os.path.realpath(__file__), "../../") path = os.path.join(os.path.realpath(__file__), "../../")
return os.path.realpath(path) return os.path.realpath(path)
def cd_repo_root_path() -> str: def cd_repo_root_path() -> str:
# Make sure the script is always started from the same # Make sure the script is always started from the same
# ScreenPlay root folder # ScreenPlay root folder
root_path = Path.cwd() root_path = Path.cwd()
if root_path.name == "Tools": if root_path.name == "Tools":
@ -47,6 +52,7 @@ def cd_repo_root_path() -> str:
chdir(root_path) chdir(root_path)
return root_path return root_path
def sha256(fname) -> str: def sha256(fname) -> str:
hash_sha256 = hashlib.sha256() hash_sha256 = hashlib.sha256()
with open(fname, "rb") as f: with open(fname, "rb") as f:
@ -55,7 +61,6 @@ def sha256(fname) -> str:
return hash_sha256.hexdigest() return hash_sha256.hexdigest()
def zipdir(path, ziph): def zipdir(path, ziph):
# ziph is zipfile handle # ziph is zipfile handle
for root, dirs, files in os.walk(path): for root, dirs, files in os.walk(path):
@ -102,3 +107,40 @@ def get_vs_env_dict():
raise ValueError(stderr.decode("mbcs")) raise ValueError(stderr.decode("mbcs"))
output = stdout.decode("mbcs").split("\r\n") output = stdout.decode("mbcs").split("\r\n")
return dict((e[0].upper(), e[1]) for e in [p.rstrip().split("=", 1) for p in output] if len(e) == 2) return dict((e[0].upper(), e[1]) for e in [p.rstrip().split("=", 1) for p in output] if len(e) == 2)
def get_latest_git_tag():
try:
tag = subprocess.check_output(
["git", "describe", "--tags"]).decode("utf-8").strip()
return tag
except subprocess.CalledProcessError:
print("Error fetching the Git tag.")
return None
def parse_semver(tag):
# Regular expression to match semver
# Like v0.15.1-RC1-305-g18b8c402
# Do NOT add a - between RC and the version number (1 in this example)
pattern = r'(?i)^v?(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9]+))?(?:-(\d+)-g([a-f0-9]+))?$'
match = re.match(pattern, tag)
if match:
major, minor, patch, pre_release, commits_since, commit_hash = match.groups()
return {
'major': major,
'minor': minor,
'patch': patch,
'pre_release': pre_release,
'commits_since': commits_since,
'commit_hash': commit_hash
}
else:
return None
def semver_to_string(semver_dict):
version_str = f"v{semver_dict['major']}.{semver_dict['minor']}.{semver_dict['patch']}"
if semver_dict['pre_release']:
version_str += f"-{semver_dict['pre_release']}"
return version_str