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
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
cmd/update-release/venv*
*.xcodeproj/xcuserdata
*.xcodeproj/project.xcworkspace
*.xcodeproj/xcuserdata*
*.xcworkspace/xcuserdata
*.xcodeproj/*.xcworkspace*
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* Jerry Jacobs ([@xor-gate](https://github.com/xor-gate))
* Victor Babenko ([@virusman](https://github.com/virusman))
* Jakob Borg ([@calmh](https://github.com/calmh))
* Tommy van der Vorst ([@pixelspark](https://github.com/pixelspark))

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 62 additions & 1 deletion cmd/update-release/update-release.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,73 @@
if 'tag_name' not in data:
raise ValueError("tag_name not present in latest_url")

import urllib.request
import json
import semver

def get_latest_v1_tag_name(repo_owner, repo_name, allow_prerelease: bool = False):
"""
Fetches the latest v1 release tag_name from a GitHub repository's releases.

Args:
repo_owner (str): The owner of the GitHub repository (e.g., 'syncthing').
repo_name (str): The name of the GitHub repository (e.g., 'syncthing').

Returns:
str or None: The tag_name of the latest v1 release, or None if not found.
"""
url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases"
try:
with urllib.request.urlopen(url) as response:
if response.getcode() == 200:
data = json.loads(response.read().decode('utf-8'))
else:
print(f"Error fetching data: HTTP {response.getcode()}")
return None
except urllib.error.URLError as e:
print(f"Error connecting to GitHub API: {e.reason}")
return None
except json.JSONDecodeError:
print("Error decoding JSON response.")
return None

v1_releases = []
for release in data:
tag_name = release.get('tag_name')
prerelease = release.get('prerelease')

if tag_name:
try:
version = semver.Version.parse(tag_name.lstrip('v')) # Remove 'v' prefix if present
if allow_prerelease and version.major == 1 and version.prerelease:
v1_releases.append(version)
elif version.major == 1:
v1_releases.append(version)
except ValueError:
# Not a valid semver string, skip
continue

if not v1_releases:
return None

# Sort the prereleases to find the latest
latest_v1_release = max(v1_releases)
return f"v{latest_v1_release}" # Re-add the 'v' prefix for consistency

###
# Parse the tag version and generate CFBundleShortVersionString and CFBundleVersion
###
owner = "syncthing"
repo = "syncthing"
latest_tag = get_latest_v1_tag_name(owner, repo)

if latest_tag:
print(f"The latest v1 release tag_name for {owner}/{repo} is: {latest_tag}")
else:
print(f"No v1 release found for {owner}/{repo}.")

# Ugly hack because of https://github.com/python-semver/python-semver/issues/137
tag_name = data['tag_name'].replace('v', '')
tag_name = latest_tag.replace('v', '')
version = semver.VersionInfo.parse(tag_name)

CFBundleShortVersionString = "{}-{:d}".format(
Expand Down
12 changes: 10 additions & 2 deletions syncthing.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
C4A415681D0D579D00DC6018 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C4A415671D0D579D00DC6018 /* main.m */; };
C4A4156A1D0D579D00DC6018 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C4A415691D0D579D00DC6018 /* Assets.xcassets */; };
C4A4156D1D0D579D00DC6018 /* STApplication.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4A4156B1D0D579D00DC6018 /* STApplication.xib */; };
C4D434FE2E5CFF3600B91C7F /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D434FD2E5CFF3600B91C7F /* OnboardingView.swift */; };
C4D435022E5D067C00B91C7F /* OnboardingViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D435012E5D067600B91C7F /* OnboardingViewFactory.swift */; };
C4F0E82E1DA1B9CF00435310 /* STPreferencesWindowGeneralViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C4F0E82C1DA1B9CF00435310 /* STPreferencesWindowGeneralViewController.m */; };
C4F0E82F1DA1B9CF00435310 /* STPreferencesWindowGeneralView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4F0E82D1DA1B9CF00435310 /* STPreferencesWindowGeneralView.xib */; };
C4FFB0661D0D7F870015D14A /* XGSyncthing.m in Sources */ = {isa = PBXBuildFile; fileRef = C4FFB0641D0D7E4C0015D14A /* XGSyncthing.m */; };
Expand Down Expand Up @@ -92,6 +94,8 @@
C4A415691D0D579D00DC6018 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C4A4156C1D0D579D00DC6018 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/STApplication.xib; sourceTree = "<group>"; };
C4A4156E1D0D579D00DC6018 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C4D434FD2E5CFF3600B91C7F /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OnboardingView.swift; path = UI/OnboardingView.swift; sourceTree = "<group>"; };
C4D435012E5D067600B91C7F /* OnboardingViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewFactory.swift; sourceTree = "<group>"; };
C4D6DD581D0D93D80024D20A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
C4D896121D0DF90900D42F73 /* syncthing-resource.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "syncthing-resource.sh"; path = "syncthing/Scripts/syncthing-resource.sh"; sourceTree = "<group>"; };
C4F0E82B1DA1B9CF00435310 /* STPreferencesWindowGeneralViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPreferencesWindowGeneralViewController.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -228,6 +232,8 @@
C4D6DD601D0DB18A0024D20A /* UI */ = {
isa = PBXGroup;
children = (
C4D435012E5D067600B91C7F /* OnboardingViewFactory.swift */,
C4D434FD2E5CFF3600B91C7F /* OnboardingView.swift */,
C4A4156B1D0D579D00DC6018 /* STApplication.xib */,
C4946B001D5877F2008447A2 /* STAboutWindow.xib */,
C4460A821D0DD38F00200C21 /* STPreferencesWindow.xib */,
Expand Down Expand Up @@ -395,6 +401,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C4D435022E5D067C00B91C7F /* OnboardingViewFactory.swift in Sources */,
29AF1BA3210F11BF004212DE /* DaemonProcess.swift in Sources */,
C4FFB0661D0D7F870015D14A /* XGSyncthing.m in Sources */,
298A5C45210DA6C40034B89F /* LocalhostTLSDelegate.m in Sources */,
Expand All @@ -405,6 +412,7 @@
C4460A801D0DD2D500200C21 /* STPreferencesWindowController.m in Sources */,
C4F0E82E1DA1B9CF00435310 /* STPreferencesWindowGeneralViewController.m in Sources */,
C4A415651D0D579D00DC6018 /* STApplication.m in Sources */,
C4D434FE2E5CFF3600B91C7F /* OnboardingView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -574,7 +582,7 @@
);
INFOPLIST_FILE = syncthing/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 12.4;
PRODUCT_BUNDLE_IDENTIFIER = "com.github.xor-gate.syncthing-macosx";
PRODUCT_NAME = Syncthing;
PROVISIONING_PROFILE = "";
Expand Down Expand Up @@ -605,7 +613,7 @@
);
INFOPLIST_FILE = syncthing/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 12.4;
PRODUCT_BUNDLE_IDENTIFIER = "com.github.xor-gate.syncthing-macosx";
PRODUCT_NAME = Syncthing;
PROVISIONING_PROFILE = "";
Expand Down
Binary file not shown.
4 changes: 2 additions & 2 deletions syncthing/Base.lproj/STApplication.xib
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
Expand Down
4 changes: 2 additions & 2 deletions syncthing/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.29.7-1</string>
<string>1.30.0-1</string>
<key>CFBundleVersion</key>
<string>102900701</string>
<string>103000001</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
Expand Down
26 changes: 26 additions & 0 deletions syncthing/OnboardingViewFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// OnboardingViewFactory.swift
// syncthing
//
// Created by Jerry Jacobs on 25/08/2025.
// Copyright © 2025 syncthing-macos authors. All rights reserved.
//
import SwiftUI
import AppKit

// Use @objcMembers to expose this class and its methods to Objective-C.
@objcMembers
public final class OnboardingViewFactory: NSObject {

// This factory method returns an NSViewController that can be used in AppKit.
public static func makeOnboardingViewController() -> NSViewController {
// 1. Instantiate your SwiftUI view.
let onboardingView = OnboardingView()

// 2. Wrap it in an NSHostingController.
let hostingController = NSHostingController(rootView: onboardingView)

// 3. Return it as a standard NSViewController.
return hostingController
}
}
33 changes: 32 additions & 1 deletion syncthing/STApplication.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ @interface STAppDelegate ()
@property (weak) IBOutlet NSMenuItem *daemonRestartMenuItem;
@property (strong) STPreferencesWindowController *preferencesWindow;
@property (strong) STAboutWindowController *aboutWindow;
@property (strong) NSWindowController *myWindowController;
@property (nonatomic, assign) BOOL devicesPaused;
@property (nonatomic, assign) BOOL daemonOK;
@property (nonatomic, assign) BOOL connectionOK;
Expand All @@ -29,9 +30,11 @@ @implementation STAppDelegate

- (void) applicationDidFinishLaunching:(NSNotification *)aNotification {
_syncthing = [[XGSyncthing alloc] init];

[self applicationLoadConfiguration];

[self showOnboardingView];

_process = [[DaemonProcess alloc] initWithPath:_executable arguments: _arguments delegate:self];
[_process launch];

Expand All @@ -46,6 +49,34 @@ - (void) clickedFolder:(id)sender {
[[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:@""];
}

- (void)showOnboardingView {
// 1. Call the Swift factory method to get the NSViewController.
NSViewController *onboardingViewController = [OnboardingViewFactory makeOnboardingViewController];

// 2. Create a new window instance.
NSRect frame = NSMakeRect(0, 0, 500, 500);
NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
NSWindow *newWindow = [[NSWindow alloc] initWithContentRect:frame
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
[newWindow center];
[newWindow setLevel:NSFloatingWindowLevel];

// 3. Set your view controller as the window's content view controller.
// This is the key step that connects them.
newWindow.contentViewController = onboardingViewController;

// 4. Create a window controller to manage the window.
self.myWindowController = [[NSWindowController alloc] initWithWindow:newWindow];

// 5. Show the window.
[self.myWindowController showWindow:nil];

NSLog(@"Frame %@", NSStringFromRect(frame));
NSLog(@"Frame %@", NSStringFromRect(newWindow.frame));
}

- (void) applicationWillTerminate:(NSNotification *)aNotification {
[_process terminate];
}
Expand Down
2 changes: 1 addition & 1 deletion syncthing/Scripts/syncthing-resource.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail

# Download and unpack syncthing into ${PRODUCT_NAME}.app/Contents/Resources
SYNCTHING_VERSION="1.29.7"
SYNCTHING_VERSION="1.30.0"
SYNCTHING_DIST_URL="https://github.com/syncthing/syncthing/releases/download"
SYNCTHING_TARBALL_URL="${SYNCTHING_DIST_URL}/v${SYNCTHING_VERSION}/syncthing-macos-universal-v${SYNCTHING_VERSION}.zip"

Expand Down
Loading