mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-09-01 06:12:01 +08:00
[refactor] reorganize platform and wire sources
This commit is contained in:
Executable
+131
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Usage: $0 <app-bundle> <expected-architecture>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
APP_BUNDLE="$1"
|
||||
EXPECTED_ARCH="$2"
|
||||
APP_EXECUTABLE="$APP_BUNDLE/Contents/MacOS/CrossDesk"
|
||||
FRAMEWORKS_DIR="$APP_BUNDLE/Contents/Frameworks"
|
||||
|
||||
for command_name in codesign install_name_tool lipo otool; do
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
echo "Required bundling command is missing: $command_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -x "$APP_EXECUTABLE" ]]; then
|
||||
echo "Missing app executable: $APP_EXECUTABLE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! lipo "$APP_EXECUTABLE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||
echo "App executable does not contain the expected architecture: $EXPECTED_ARCH" >&2
|
||||
lipo -archs "$APP_EXECUTABLE" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SLINT_REFERENCE="$(otool -L "$APP_EXECUTABLE" | awk \
|
||||
'$1 ~ /(^|\/)libslint_cpp[^\/[:space:]]*\.dylib$/ { print $1; exit }')"
|
||||
if [[ -z "$SLINT_REFERENCE" ]]; then
|
||||
echo "The app executable does not reference the Slint runtime" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SLINT_NAME="${SLINT_REFERENCE##*/}"
|
||||
SLINT_CURRENT_VERSION="$(otool -L "$APP_EXECUTABLE" | awk -v dependency="$SLINT_REFERENCE" '
|
||||
$1 == dependency {
|
||||
for (i = 1; i <= NF; ++i) {
|
||||
if ($i == "current") {
|
||||
version = $(i + 2)
|
||||
gsub(/[),]/, "", version)
|
||||
print version
|
||||
exit
|
||||
}
|
||||
}
|
||||
}')"
|
||||
|
||||
slint_runtime_version() {
|
||||
local library="$1"
|
||||
otool -L "$library" | awk -v name="$SLINT_NAME" '
|
||||
index($1, "/" name) || $1 == "@rpath/" name {
|
||||
for (i = 1; i <= NF; ++i) {
|
||||
if ($i == "current") {
|
||||
version = $(i + 2)
|
||||
gsub(/[),]/, "", version)
|
||||
print version
|
||||
exit
|
||||
}
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
find_slint_runtime() {
|
||||
local candidate=""
|
||||
local candidate_version=""
|
||||
local package_root=""
|
||||
local package_roots=()
|
||||
|
||||
if [[ "$SLINT_REFERENCE" == /* && -f "$SLINT_REFERENCE" ]]; then
|
||||
if lipo "$SLINT_REFERENCE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||
echo "$SLINT_REFERENCE"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${XMAKE_GLOBALDIR:-}" ]]; then
|
||||
package_roots+=("$XMAKE_GLOBALDIR/.xmake/packages")
|
||||
fi
|
||||
package_roots+=("$HOME/.xmake/packages" "/data/.xmake/packages")
|
||||
|
||||
for package_root in "${package_roots[@]}"; do
|
||||
[[ -d "$package_root/s/slint" ]] || continue
|
||||
while IFS= read -r candidate; do
|
||||
if ! lipo "$candidate" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
|
||||
candidate_version="$(slint_runtime_version "$candidate")"
|
||||
if [[ -n "$SLINT_CURRENT_VERSION" && "$candidate_version" != "$SLINT_CURRENT_VERSION" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "$candidate"
|
||||
return 0
|
||||
done < <(find "$package_root/s/slint" -type f -path "*/lib/$SLINT_NAME" -print 2>/dev/null)
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
SLINT_LIBRARY="$(find_slint_runtime || true)"
|
||||
if [[ -z "$SLINT_LIBRARY" ]]; then
|
||||
echo "Unable to locate $SLINT_NAME for $EXPECTED_ARCH (current version $SLINT_CURRENT_VERSION)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -d "$FRAMEWORKS_DIR"
|
||||
install -m 0755 "$SLINT_LIBRARY" "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||
|
||||
install_name_tool -id "@rpath/$SLINT_NAME" "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||
if [[ "$SLINT_REFERENCE" != "@rpath/$SLINT_NAME" ]]; then
|
||||
install_name_tool -change "$SLINT_REFERENCE" "@rpath/$SLINT_NAME" "$APP_EXECUTABLE"
|
||||
fi
|
||||
|
||||
if ! otool -l "$APP_EXECUTABLE" | awk '
|
||||
$1 == "cmd" { in_rpath = ($2 == "LC_RPATH"); next }
|
||||
in_rpath && $1 == "path" && $2 == "@executable_path/../Frameworks" { found = 1 }
|
||||
END { exit(found ? 0 : 1) }'; then
|
||||
install_name_tool -add_rpath "@executable_path/../Frameworks" "$APP_EXECUTABLE"
|
||||
fi
|
||||
|
||||
# install_name_tool invalidates the linker's ad-hoc signature. Sign nested code
|
||||
# first, then seal the complete app bundle so local packages remain launchable.
|
||||
codesign --force --sign - "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||
codesign --force --deep --sign - "$APP_BUNDLE"
|
||||
|
||||
echo "Bundled Slint runtime: $SLINT_LIBRARY"
|
||||
Executable
+220
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="${PROJECT_ROOT}/build/macosx/arm64/release/crossdesk"
|
||||
PLATFORM="macos"
|
||||
ARCH="arm64"
|
||||
BINARY_ARCH="arm64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="${PROJECT_ROOT}/apps/desktop/resources/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="14.0"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
normalize_app_version() {
|
||||
local input="$1"
|
||||
local prefix=""
|
||||
local body="$input"
|
||||
|
||||
if [[ "$body" == v* ]]; then
|
||||
prefix="v"
|
||||
body="${body#v}"
|
||||
fi
|
||||
|
||||
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
|
||||
else
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
APP_VERSION="$(normalize_app_version "$1")"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
INSTALLER_TITLE="${APP_NAME_UPPER} ${APP_VERSION}"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
||||
find "${APP_BUNDLE}" -name '._*' -delete
|
||||
|
||||
"${SCRIPT_DIR}/bundle_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||
"${SCRIPT_DIR}/verify_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
mkdir -p build_pkg_scripts
|
||||
cp "${SCRIPT_DIR}/tcc_postinstall.sh" build_pkg_scripts/postinstall
|
||||
chmod +x build_pkg_scripts/postinstall
|
||||
|
||||
mkdir -p build_pkg_resources
|
||||
cat > build_pkg_resources/welcome.html <<EOF
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #222;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>欢迎安装 ${INSTALLER_TITLE}</h1>
|
||||
<p>CrossDesk 将安装到“应用程序”文件夹。</p>
|
||||
<p>首次启动时,CrossDesk 会引导你在系统设置中授予必要权限,包括辅助功能、录屏与系统录音等。</p>
|
||||
<p>为避免旧版本授权残留造成状态误判,安装后可能需要重新授权。</p>
|
||||
<p>安装完成后,请从“应用程序”文件夹启动 CrossDesk。</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
--scripts build_pkg_scripts \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
cat > build_pkg_temp/Distribution <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>${INSTALLER_TITLE}</title>
|
||||
<welcome file="welcome.html" mime-type="text/html"/>
|
||||
<options customize="never" require-scripts="false" hostArchitectures="arm64"/>
|
||||
<choices-outline>
|
||||
<line choice="default">
|
||||
<line choice="${IDENTIFIER}"/>
|
||||
</line>
|
||||
</choices-outline>
|
||||
<choice id="default" title="${INSTALLER_TITLE}"/>
|
||||
<choice id="${IDENTIFIER}" title="${INSTALLER_TITLE}" visible="false">
|
||||
<pkg-ref id="${IDENTIFIER}"/>
|
||||
</choice>
|
||||
<pkg-ref id="${IDENTIFIER}" version="${APP_VERSION}" onConclusion="none">crossdesk-component.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
EOF
|
||||
|
||||
productbuild \
|
||||
--distribution build_pkg_temp/Distribution \
|
||||
--package-path build_pkg_temp \
|
||||
--resources build_pkg_resources \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
# Set custom icon for PKG file
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
echo "Setting custom icon for PKG file..."
|
||||
# Create a temporary iconset from icns
|
||||
TEMP_ICON_DIR=$(mktemp -d)
|
||||
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
|
||||
|
||||
# Use sips to create a png from icns for the icon
|
||||
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
|
||||
|
||||
# Method: Use osascript to set file icon (works on macOS)
|
||||
osascript <<APPLESCRIPT
|
||||
use framework "Foundation"
|
||||
use framework "AppKit"
|
||||
|
||||
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
|
||||
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
|
||||
|
||||
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
|
||||
set workspace to current application's NSWorkspace's sharedWorkspace()
|
||||
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
|
||||
APPLESCRIPT
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Custom icon set successfully for ${PKG_NAME}"
|
||||
else
|
||||
echo "Warning: Failed to set custom icon (this is optional)"
|
||||
fi
|
||||
|
||||
rm -rf "${TEMP_ICON_DIR}"
|
||||
fi
|
||||
echo "Set icon finished"
|
||||
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
Executable
+220
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="${PROJECT_ROOT}/build/macosx/x86_64/release/crossdesk"
|
||||
PLATFORM="macos"
|
||||
ARCH="x64"
|
||||
BINARY_ARCH="x86_64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="${PROJECT_ROOT}/apps/desktop/resources/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="14.0"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
normalize_app_version() {
|
||||
local input="$1"
|
||||
local prefix=""
|
||||
local body="$input"
|
||||
|
||||
if [[ "$body" == v* ]]; then
|
||||
prefix="v"
|
||||
body="${body#v}"
|
||||
fi
|
||||
|
||||
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
|
||||
else
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
APP_VERSION="$(normalize_app_version "$1")"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
INSTALLER_TITLE="${APP_NAME_UPPER} ${APP_VERSION}"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
||||
find "${APP_BUNDLE}" -name '._*' -delete
|
||||
|
||||
"${SCRIPT_DIR}/bundle_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||
"${SCRIPT_DIR}/verify_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
mkdir -p build_pkg_scripts
|
||||
cp "${SCRIPT_DIR}/tcc_postinstall.sh" build_pkg_scripts/postinstall
|
||||
chmod +x build_pkg_scripts/postinstall
|
||||
|
||||
mkdir -p build_pkg_resources
|
||||
cat > build_pkg_resources/welcome.html <<EOF
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #222;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>欢迎安装 ${INSTALLER_TITLE}</h1>
|
||||
<p>CrossDesk 将安装到“应用程序”文件夹。</p>
|
||||
<p>首次启动时,CrossDesk 会引导你在系统设置中授予必要权限,包括辅助功能、录屏与系统录音等。</p>
|
||||
<p>为避免旧版本授权残留造成状态误判,安装后可能需要重新授权。</p>
|
||||
<p>安装完成后,请从“应用程序”文件夹启动 CrossDesk。</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
--scripts build_pkg_scripts \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
cat > build_pkg_temp/Distribution <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>${INSTALLER_TITLE}</title>
|
||||
<welcome file="welcome.html" mime-type="text/html"/>
|
||||
<options customize="never" require-scripts="false" hostArchitectures="x86_64"/>
|
||||
<choices-outline>
|
||||
<line choice="default">
|
||||
<line choice="${IDENTIFIER}"/>
|
||||
</line>
|
||||
</choices-outline>
|
||||
<choice id="default" title="${INSTALLER_TITLE}"/>
|
||||
<choice id="${IDENTIFIER}" title="${INSTALLER_TITLE}" visible="false">
|
||||
<pkg-ref id="${IDENTIFIER}"/>
|
||||
</choice>
|
||||
<pkg-ref id="${IDENTIFIER}" version="${APP_VERSION}" onConclusion="none">crossdesk-component.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
EOF
|
||||
|
||||
productbuild \
|
||||
--distribution build_pkg_temp/Distribution \
|
||||
--package-path build_pkg_temp \
|
||||
--resources build_pkg_resources \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
# Set custom icon for PKG file
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
echo "Setting custom icon for PKG file..."
|
||||
# Create a temporary iconset from icns
|
||||
TEMP_ICON_DIR=$(mktemp -d)
|
||||
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
|
||||
|
||||
# Use sips to create a png from icns for the icon
|
||||
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
|
||||
|
||||
# Method: Use osascript to set file icon (works on macOS)
|
||||
osascript <<APPLESCRIPT
|
||||
use framework "Foundation"
|
||||
use framework "AppKit"
|
||||
|
||||
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
|
||||
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
|
||||
|
||||
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
|
||||
set workspace to current application's NSWorkspace's sharedWorkspace()
|
||||
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
|
||||
APPLESCRIPT
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Custom icon set successfully for ${PKG_NAME}"
|
||||
else
|
||||
echo "Warning: Failed to set custom icon (this is optional)"
|
||||
fi
|
||||
|
||||
rm -rf "${TEMP_ICON_DIR}"
|
||||
fi
|
||||
echo "Set icon finished"
|
||||
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_IDENTIFIER="cn.crossdesk.app"
|
||||
|
||||
# Keep known historical identifiers here. tccutil only resets identifiers that
|
||||
# Launch Services can currently resolve, so path/db cleanup below remains a
|
||||
# best-effort fallback for stale entries from unsigned or removed builds.
|
||||
BUNDLE_IDENTIFIERS=(
|
||||
"cn.crossdesk.app"
|
||||
"cn.crossdesk.CrossDesk"
|
||||
"com.crossdesk.app"
|
||||
"com.crossdesk.CrossDesk"
|
||||
"com.kunkundi.crossdesk"
|
||||
"com.kunkundi.CrossDesk"
|
||||
)
|
||||
|
||||
TCC_SERVICES=(
|
||||
"ScreenCapture"
|
||||
"Accessibility"
|
||||
"Microphone"
|
||||
"AudioCapture"
|
||||
)
|
||||
|
||||
run_tccutil() {
|
||||
local user_name="$1"
|
||||
local user_id="$2"
|
||||
local service="$3"
|
||||
local bundle_id="$4"
|
||||
|
||||
if [ -n "$user_name" ] && [ -n "$user_id" ]; then
|
||||
/bin/launchctl asuser "$user_id" \
|
||||
/usr/bin/sudo -u "$user_name" \
|
||||
/usr/bin/tccutil reset "$service" "$bundle_id" >/dev/null 2>&1
|
||||
else
|
||||
/usr/bin/tccutil reset "$service" "$bundle_id" >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
reset_bundle_tcc() {
|
||||
local user_name="$1"
|
||||
local user_id="$2"
|
||||
local bundle_id
|
||||
local service
|
||||
|
||||
for bundle_id in "${BUNDLE_IDENTIFIERS[@]}"; do
|
||||
if run_tccutil "$user_name" "$user_id" "All" "$bundle_id"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
for service in "${TCC_SERVICES[@]}"; do
|
||||
run_tccutil "$user_name" "$user_id" "$service" "$bundle_id" || true
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
cleanup_tcc_db() {
|
||||
local db_path="$1"
|
||||
|
||||
if [ ! -f "$db_path" ] || ! command -v sqlite3 >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
/usr/bin/sqlite3 "$db_path" <<'SQL' >/dev/null 2>&1 || true
|
||||
DELETE FROM access
|
||||
WHERE service IN (
|
||||
'kTCCServiceScreenCapture',
|
||||
'kTCCServiceAccessibility',
|
||||
'kTCCServiceMicrophone',
|
||||
'kTCCServiceAudioCapture'
|
||||
)
|
||||
AND (
|
||||
client IN (
|
||||
'cn.crossdesk.app',
|
||||
'cn.crossdesk.CrossDesk',
|
||||
'com.crossdesk.app',
|
||||
'com.crossdesk.CrossDesk',
|
||||
'com.kunkundi.crossdesk',
|
||||
'com.kunkundi.CrossDesk'
|
||||
)
|
||||
OR lower(client) LIKE '%crossdesk%'
|
||||
);
|
||||
SQL
|
||||
}
|
||||
|
||||
cleanup_user_tcc_db() {
|
||||
local user_name="$1"
|
||||
local home_dir
|
||||
|
||||
home_dir=$(/usr/bin/dscl . -read "/Users/${user_name}" NFSHomeDirectory 2>/dev/null |
|
||||
/usr/bin/awk '{print $2}')
|
||||
if [ -z "$home_dir" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
cleanup_tcc_db "${home_dir}/Library/Application Support/com.apple.TCC/TCC.db"
|
||||
}
|
||||
|
||||
CONSOLE_USER=$(/usr/bin/stat -f "%Su" /dev/console 2>/dev/null || true)
|
||||
if [ -n "$CONSOLE_USER" ] &&
|
||||
[ "$CONSOLE_USER" != "root" ] &&
|
||||
[ "$CONSOLE_USER" != "loginwindow" ]; then
|
||||
CONSOLE_UID=$(/usr/bin/id -u "$CONSOLE_USER" 2>/dev/null || true)
|
||||
reset_bundle_tcc "$CONSOLE_USER" "$CONSOLE_UID"
|
||||
cleanup_user_tcc_db "$CONSOLE_USER"
|
||||
fi
|
||||
|
||||
# Also clear any system/root-scoped decisions as a harmless fallback.
|
||||
reset_bundle_tcc "" ""
|
||||
cleanup_tcc_db "/Library/Application Support/com.apple.TCC/TCC.db"
|
||||
|
||||
exit 0
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 2 || $# -gt 3 ]]; then
|
||||
echo "Usage: $0 <app-bundle> <expected-architecture> [expected-sdk-version]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
APP_BUNDLE="$1"
|
||||
EXPECTED_ARCH="$2"
|
||||
EXPECTED_SDK_VERSION="${3:-}"
|
||||
APP_EXECUTABLE="$APP_BUNDLE/Contents/MacOS/CrossDesk"
|
||||
FRAMEWORKS_DIR="$APP_BUNDLE/Contents/Frameworks"
|
||||
INFO_PLIST="$APP_BUNDLE/Contents/Info.plist"
|
||||
|
||||
if [[ ! -x "$APP_EXECUTABLE" ]]; then
|
||||
echo "Missing app executable: $APP_EXECUTABLE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! lipo "$APP_EXECUTABLE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||
echo "Unexpected app architecture; expected $EXPECTED_ARCH" >&2
|
||||
lipo -archs "$APP_EXECUTABLE" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! otool -l "$APP_EXECUTABLE" | awk '
|
||||
$1 == "cmd" { in_rpath = ($2 == "LC_RPATH"); next }
|
||||
in_rpath && $1 == "path" && $2 == "@executable_path/../Frameworks" { found = 1 }
|
||||
END { exit(found ? 0 : 1) }'; then
|
||||
echo "Missing app rpath: @executable_path/../Frameworks" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_dependencies() {
|
||||
local macho="$1"
|
||||
local dependency=""
|
||||
local relative_path=""
|
||||
|
||||
while IFS= read -r dependency; do
|
||||
case "$dependency" in
|
||||
/System/Library/*|/usr/lib/*)
|
||||
;;
|
||||
@rpath/*)
|
||||
relative_path="${dependency#@rpath/}"
|
||||
if [[ ! -f "$FRAMEWORKS_DIR/$relative_path" ]]; then
|
||||
echo "Unbundled dependency in $macho: $dependency" >&2
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
@executable_path/../Frameworks/*)
|
||||
relative_path="${dependency#@executable_path/../Frameworks/}"
|
||||
if [[ ! -f "$FRAMEWORKS_DIR/$relative_path" ]]; then
|
||||
echo "Unbundled dependency in $macho: $dependency" >&2
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
@loader_path/*)
|
||||
relative_path="${dependency#@loader_path/}"
|
||||
if [[ ! -f "$(dirname "$macho")/$relative_path" ]]; then
|
||||
echo "Unbundled dependency in $macho: $dependency" >&2
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "External non-system dependency in $macho: $dependency" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done < <(otool -L "$macho" | tail -n +2 | awk '{ print $1 }')
|
||||
}
|
||||
|
||||
verify_dependencies "$APP_EXECUTABLE"
|
||||
|
||||
SLINT_LIBRARY="$(find "$FRAMEWORKS_DIR" -maxdepth 1 -type f -name 'libslint_cpp*.dylib' -print -quit 2>/dev/null || true)"
|
||||
if [[ -z "$SLINT_LIBRARY" ]]; then
|
||||
echo "The packaged app does not contain libslint_cpp.dylib" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! lipo "$SLINT_LIBRARY" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||
echo "Unexpected Slint runtime architecture; expected $EXPECTED_ARCH" >&2
|
||||
lipo -archs "$SLINT_LIBRARY" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
verify_dependencies "$SLINT_LIBRARY"
|
||||
|
||||
PLIST_MIN_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$INFO_PLIST")"
|
||||
BINARY_MIN_VERSION="$(otool -l "$APP_EXECUTABLE" | awk '
|
||||
$1 == "cmd" { build_version = ($2 == "LC_BUILD_VERSION"); version_min = ($2 == "LC_VERSION_MIN_MACOSX"); next }
|
||||
build_version && $1 == "minos" { print $2; exit }
|
||||
version_min && $1 == "version" { print $2; exit }')"
|
||||
if [[ -z "$BINARY_MIN_VERSION" || "$PLIST_MIN_VERSION" != "$BINARY_MIN_VERSION" ]]; then
|
||||
echo "Minimum macOS version mismatch: plist=$PLIST_MIN_VERSION binary=${BINARY_MIN_VERSION:-unknown}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$EXPECTED_SDK_VERSION" ]]; then
|
||||
verify_sdk_version() {
|
||||
local macho="$1"
|
||||
local actual_sdk=""
|
||||
actual_sdk="$(otool -l "$macho" | awk '
|
||||
$1 == "cmd" { build_version = ($2 == "LC_BUILD_VERSION"); next }
|
||||
build_version && $1 == "sdk" { print $2; exit }')"
|
||||
if [[ "$actual_sdk" != "$EXPECTED_SDK_VERSION" ]]; then
|
||||
echo "Unexpected macOS SDK in $macho: expected $EXPECTED_SDK_VERSION, got ${actual_sdk:-unknown}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_sdk_version "$APP_EXECUTABLE"
|
||||
verify_sdk_version "$SLINT_LIBRARY"
|
||||
fi
|
||||
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"
|
||||
|
||||
echo "Verified $APP_BUNDLE ($EXPECTED_ARCH, macOS $BINARY_MIN_VERSION+)"
|
||||
Reference in New Issue
Block a user