Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions .github/workflows/build-installer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
name: Build macOS installer

on:
workflow_dispatch:
push:
branches:
- fix/todesk-remote-scroll

permissions:
contents: write

concurrency:
group: build-installer-${{ github.ref }}
cancel-in-progress: true

jobs:
build-installer:
name: Build unsigned DMG
runs-on: macos-26
timeout-minutes: 30
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}

steps:
- name: Check out source
uses: actions/checkout@v6

- name: Show build environment
run: |
xcodebuild -version
swift --version

- name: Resolve Swift packages
run: |
xcodebuild -resolvePackageDependencies \
-project Mos.xcodeproj \
-scheme Debug \
-derivedDataPath "$RUNNER_TEMP/DerivedData"

- name: Compute unique build number
id: version
run: |
BUILD="$(date +%Y%m%d).${GITHUB_RUN_NUMBER}"
echo "build=$BUILD" >> "$GITHUB_OUTPUT"
echo "Using CURRENT_PROJECT_VERSION=$BUILD"

- name: Build release archive
run: |
xcodebuild archive \
-project Mos.xcodeproj \
-scheme Debug \
-configuration Release \
-destination "generic/platform=macOS" \
-archivePath "$RUNNER_TEMP/Mos.xcarchive" \
-derivedDataPath "$RUNNER_TEMP/DerivedData" \
ARCHS="arm64 x86_64" \
ONLY_ACTIVE_ARCH=NO \
CURRENT_PROJECT_VERSION="${{ steps.version.outputs.build }}" \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO

- name: Apply ad-hoc signature
run: |
APP_PATH="$RUNNER_TEMP/Mos.xcarchive/Products/Applications/Mos.app"
test -d "$APP_PATH"
codesign --force --deep --sign - "$APP_PATH"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
SIGNATURE_INFO=$(codesign -dvv "$APP_PATH" 2>&1)
echo "$SIGNATURE_INFO"
if [[ "$SIGNATURE_INFO" == *runtime* ]]; then
echo "Ad-hoc builds must not enable Hardened Runtime because embedded frameworks have no shared Team ID."
exit 1
fi
lipo -archs "$APP_PATH/Contents/MacOS/Mos"

- name: Read package metadata
id: metadata
run: |
APP_PATH="$RUNNER_TEMP/Mos.xcarchive/Products/Applications/Mos.app"
INFO_PLIST="$APP_PATH/Contents/Info.plist"
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFO_PLIST")
BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$INFO_PLIST")
DMG_NAME="Mos.${VERSION}-${BUILD}-unsigned.dmg"
ZIP_NAME="Mos.Versions.${VERSION}-${BUILD}.zip"
TAG="fork-${VERSION}-${BUILD}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "build=$BUILD" >> "$GITHUB_OUTPUT"
echo "dmg_name=$DMG_NAME" >> "$GITHUB_OUTPUT"
echo "dmg_path=$RUNNER_TEMP/$DMG_NAME" >> "$GITHUB_OUTPUT"
echo "zip_name=$ZIP_NAME" >> "$GITHUB_OUTPUT"
echo "zip_path=$RUNNER_TEMP/$ZIP_NAME" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "artifact_name=Mos-${VERSION}-${BUILD}-unsigned" >> "$GITHUB_OUTPUT"

- name: Create installer DMG
run: |
APP_PATH="$RUNNER_TEMP/Mos.xcarchive/Products/Applications/Mos.app"
DMG_ROOT="$RUNNER_TEMP/dmg-root"
mkdir -p "$DMG_ROOT"
ditto --norsrc --noextattr "$APP_PATH" "$DMG_ROOT/Mos.app"
ln -s /Applications "$DMG_ROOT/Applications"
hdiutil create \
-volname Mos \
-srcfolder "$DMG_ROOT" \
-format UDZO \
-ov \
"${{ steps.metadata.outputs.dmg_path }}"

- name: Verify installer
run: |
DMG_PATH="${{ steps.metadata.outputs.dmg_path }}"
MOUNT_PATH="$RUNNER_TEMP/dmg-mount"
test -s "$DMG_PATH"
hdiutil verify "$DMG_PATH"
mkdir -p "$MOUNT_PATH"
hdiutil attach "$DMG_PATH" -mountpoint "$MOUNT_PATH" -nobrowse -readonly
trap 'hdiutil detach "$MOUNT_PATH"' EXIT
codesign --verify --deep --strict --verbose=2 "$MOUNT_PATH/Mos.app"
lipo -archs "$MOUNT_PATH/Mos.app/Contents/MacOS/Mos"
shasum -a 256 "$DMG_PATH"

- name: Upload installer
uses: actions/upload-artifact@v6
with:
name: ${{ steps.metadata.outputs.artifact_name }}
path: ${{ steps.metadata.outputs.dmg_path }}
if-no-files-found: error
compression-level: 0
retention-days: 7

- name: Package Sparkle zip
run: |
APP_PATH="$RUNNER_TEMP/Mos.xcarchive/Products/Applications/Mos.app"
ditto -c -k --norsrc --noextattr --keepParent "$APP_PATH" "${{ steps.metadata.outputs.zip_path }}"
zipinfo -1 "${{ steps.metadata.outputs.zip_path }}" | grep '/\._' && {
echo "ERROR: AppleDouble files found in zip"
exit 1
} || echo "OK: no AppleDouble files"

- name: Sign and publish fork Sparkle update
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
ZIP_PATH="${{ steps.metadata.outputs.zip_path }}"
ZIP_NAME="${{ steps.metadata.outputs.zip_name }}"
VERSION="${{ steps.metadata.outputs.version }}"
BUILD="${{ steps.metadata.outputs.build }}"
TAG="${{ steps.metadata.outputs.tag }}"
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${ZIP_NAME}"

SIGN_UPDATE=$(find "$RUNNER_TEMP/DerivedData" -path "*/artifacts/sparkle/Sparkle/bin/sign_update" -type f | head -1)
if [[ -z "$SIGN_UPDATE" ]]; then
SIGN_UPDATE=$(find "$RUNNER_TEMP/DerivedData" -name sign_update -type f | head -1)
fi
test -n "$SIGN_UPDATE"
chmod +x "$SIGN_UPDATE"

SIGN_OUTPUT=$(printf '%s' "$SPARKLE_PRIVATE_KEY" | "$SIGN_UPDATE" --ed-key-file - "$ZIP_PATH")
echo "$SIGN_OUTPUT"
ED_SIGNATURE=$(echo "$SIGN_OUTPUT" | grep -o 'sparkle:edSignature="[^"]*"' | sed 's/sparkle:edSignature="//;s/"//')
FILE_LENGTH=$(echo "$SIGN_OUTPUT" | grep -o 'length="[^"]*"' | sed 's/length="//;s/"//')
test -n "$ED_SIGNATURE"
test -n "$FILE_LENGTH"

NOTES=$(git log -1 --pretty=%B | sed 's/]]>/]]]]><![CDATA[>/g')
CHANGELOG="<h2>更新</h2><ul><li>${NOTES}</li></ul>"
PUB_DATE=$(date -u "+%a, %d %b %Y %H:%M:%S +0000")
export NEW_ITEM
NEW_ITEM=$(cat <<EOF
<item>
<title>Mos ${VERSION}</title>
<description><![CDATA[${CHANGELOG}]]></description>
<pubDate>${PUB_DATE}</pubDate>
<enclosure
url="${DOWNLOAD_URL}"
length="${FILE_LENGTH}"
type="application/octet-stream"
sparkle:shortVersionString="${VERSION}"
sparkle:version="${BUILD}"
sparkle:edSignature="${ED_SIGNATURE}"
/>
</item>
EOF
)

BASE_APPCAST=""
if gh release view sparkle-feed >/dev/null 2>&1; then
mkdir -p "$RUNNER_TEMP/old-feed"
gh release download sparkle-feed --pattern appcast.xml --dir "$RUNNER_TEMP/old-feed" || true
if [[ -f "$RUNNER_TEMP/old-feed/appcast.xml" ]]; then
BASE_APPCAST="$RUNNER_TEMP/old-feed/appcast.xml"
fi
fi

python3 scripts/generate-fork-appcast.py "${BASE_APPCAST:--}" "$BUILD" > "$RUNNER_TEMP/appcast.xml"

gh release create "$TAG" \
--title "Mos ${VERSION} (${BUILD})" \
--notes "$NOTES" \
--target "$GITHUB_SHA" \
"$ZIP_PATH"

if gh release view sparkle-feed >/dev/null 2>&1; then
gh release upload sparkle-feed "$RUNNER_TEMP/appcast.xml" --clobber
else
gh release create sparkle-feed \
--title "Sparkle feed" \
--notes "Independent update feed for this fork. Not affiliated with the upstream Caldis/Mos appcast." \
--target "$GITHUB_SHA" \
"$RUNNER_TEMP/appcast.xml"
fi

- name: Skip Sparkle publish
if: ${{ env.SPARKLE_PRIVATE_KEY == '' }}
run: echo "SPARKLE_PRIVATE_KEY secret is missing; installer artifact was still uploaded."
4 changes: 2 additions & 2 deletions Mos/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUFeedURL</key>
<string>https://mos.caldis.me/appcast.xml</string>
<string>https://github.com/ZHOUSJ6/Mos/releases/download/sparkle-feed/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>zskqekl7thqvIANuR8SLKrfQ4LpqsZckKiDWfcD86JQ=</string>
<string>ZIPGVBhcI0PGAJV0+nYtILxcZGj95CSm77+w4tyYxkk=</string>
</dict>
</plist>
6 changes: 6 additions & 0 deletions Mos/Managers/UpdateManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Sparkle
final class UpdateManager: NSObject {

static let shared = UpdateManager()
/// 本 fork 独立更新源,不走原项目 mos.caldis.me
static let forkAppcastURL = "https://github.com/ZHOUSJ6/Mos/releases/download/sparkle-feed/appcast.xml"

private lazy var updaterController = SPUStandardUpdaterController(
startingUpdater: true,
Expand Down Expand Up @@ -44,6 +46,10 @@ extension UpdateManager {

extension UpdateManager: SPUUpdaterDelegate {

func feedURLString(for updater: SPUUpdater) -> String? {
return UpdateManager.forkAppcastURL
}

func allowedChannels(for updater: SPUUpdater) -> Set<String> {
Options.shared.update.includingBetaVersion ? ["beta"] : []
}
Expand Down
22 changes: 18 additions & 4 deletions Mos/ScrollCore/ScrollCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,18 @@ class ScrollCore {
if scrollEvent.isTrackpad() {
return Unmanaged.passUnretained(event)
}
// 当事件来自远程桌面,且其发送的事件 isContinuous=1.0,此时跳过本地平滑
if ScrollUtils.shared.isRemoteSmoothedEvent(event) {
return Unmanaged.passUnretained(event)
}
// 当鼠标输入, 根据需要执行翻转方向/平滑滚动
// 获取事件目标
let targetRunningApplication = ScrollUtils.shared.getRunningApplication(from: event)
// ToDesk 等远程控制链路直接使用原始滚轮:关闭平滑和方向翻转。
let useRawScrollForRemoteControl = ScrollUtils.shared.shouldUseRawScrollForRemoteControl(
event,
targetRunningApplication: targetRunningApplication
)
let isRemoteSmoothedEvent = ScrollUtils.shared.isRemoteSmoothedEvent(event)
if isRemoteSmoothedEvent && !useRawScrollForRemoteControl {
return Unmanaged.passUnretained(event)
}
// 获取列表中应用程序的列外设置信息
ScrollCore.shared.application = ScrollUtils.shared.getTargetApplication(from: targetRunningApplication)
// 平滑/翻转
Expand Down Expand Up @@ -117,6 +122,15 @@ class ScrollCore {
enableReverseVertical = allowReverse && Options.shared.scroll.reverseVertical
enableReverseHorizontal = allowReverse && Options.shared.scroll.reverseHorizontal
}
if useRawScrollForRemoteControl || isRemoteSmoothedEvent {
enableSmooth = false
enableSmoothVertical = false
enableSmoothHorizontal = false
}
if useRawScrollForRemoteControl {
enableReverseVertical = false
enableReverseHorizontal = false
}
// Launchpad 激活则强制屏蔽平滑
if ScrollUtils.shared.getLaunchpadActivity(withRunningApplication: targetRunningApplication) {
enableSmooth = false
Expand Down
3 changes: 2 additions & 1 deletion Mos/ScrollCore/ScrollDispatchContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ final class ScrollDispatchContext {
#endif
return false
}
let pid = pid_t(event.getIntegerValueField(.eventTargetUnixProcessID))
let rawPID = pid_t(event.getIntegerValueField(.eventTargetUnixProcessID))
let pid = ScrollUtils.shared.resolveScrollTargetPID(rawPID)
os_unfair_lock_lock(&lock)
state.eventTemplate = template
state.targetPID = pid
Expand Down
Loading