Skip to content

Commit f942fec

Browse files
committed
Impl preference and onOpenURL modifiers (pref required prefs system)
The preferences system is similar to SwiftUI's preferences system. It's similar to environment, but it propagates from child to parent instead of parent to child.
1 parent 352c2ce commit f942fec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+762
-475
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ public final class AppKitBackend: AppBackend {
2727
)
2828
}
2929

30-
public init() {}
30+
private let appDelegate = NSCustomApplicationDelegate()
31+
32+
public init() {
33+
NSApplication.shared.delegate = appDelegate
34+
}
3135

3236
public func runMainLoop(_ callback: @escaping () -> Void) {
3337
callback()
@@ -209,6 +213,14 @@ public final class AppKitBackend: AppBackend {
209213
}
210214
}
211215

216+
public func setIncomingURLHandler(to action: @escaping (URL) -> Void) {
217+
appDelegate.onOpenURLs = { urls in
218+
for url in urls {
219+
action(url)
220+
}
221+
}
222+
}
223+
212224
public func show(widget: Widget) {}
213225

214226
class NSContainerView: NSView {
@@ -1361,3 +1373,11 @@ extension Notification.Name {
13611373
"AppleInterfaceThemeChangedNotification"
13621374
)
13631375
}
1376+
1377+
final class NSCustomApplicationDelegate: NSObject, NSApplicationDelegate {
1378+
var onOpenURLs: (([URL]) -> Void)?
1379+
1380+
func application(_ application: NSApplication, open urls: [URL]) {
1381+
onOpenURLs?(urls)
1382+
}
1383+
}

Sources/Gtk/Application.swift

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
//
44

55
import CGtk
6+
import Foundation
7+
8+
public class Application: GObject, GActionMap {
9+
var applicationPointer: UnsafeMutablePointer<GtkApplication> {
10+
UnsafeMutablePointer(OpaquePointer(gobjectPointer))
11+
}
612

7-
public class Application: GActionMap {
8-
public let applicationPointer: UnsafeMutablePointer<GtkApplication>
9-
private(set) var applicationWindow: ApplicationWindow?
1013
private var windowCallback: ((ApplicationWindow) -> Void)?
14+
private var hasActivated = false
1115

1216
public var actionMapPointer: OpaquePointer {
1317
OpaquePointer(applicationPointer)
@@ -27,43 +31,66 @@ public class Application: GActionMap {
2731
}
2832
}
2933

30-
public init(applicationId: String) {
31-
// Ignore the deprecation warning, making the change breaks support for platforms such as
32-
// Ubuntu (before Lunar). This is due to Ubuntu coming with an older version of Gtk in apt.
33-
#if GTK_4_10_PLUS
34-
applicationPointer = gtk_application_new(applicationId, G_APPLICATION_DEFAULT_FLAGS)
35-
#else
36-
applicationPointer = gtk_application_new(applicationId, G_APPLICATION_FLAGS_NONE)
37-
#endif
34+
@GObjectProperty(named: "register-session") public var registerSession: Bool
35+
36+
public init(applicationId: String, flags: GApplicationFlags = .init(rawValue: 0)) {
37+
super.init(
38+
gtk_application_new(applicationId, flags)
39+
)
40+
registerSignals()
3841
}
3942

40-
@discardableResult
41-
public func run(_ windowCallback: @escaping (ApplicationWindow) -> Void) -> Int {
42-
self.windowCallback = windowCallback
43+
public override func registerSignals() {
44+
addSignal(name: "activate") {
45+
self.activate()
46+
}
4347

44-
let handler:
48+
let handler1:
4549
@convention(c) (
50+
UnsafeMutableRawPointer,
51+
UnsafeMutablePointer<OpaquePointer>,
52+
gint,
4653
UnsafeMutableRawPointer,
4754
UnsafeMutableRawPointer
48-
) -> Void = { _, data in
49-
let app = unsafeBitCast(data, to: Application.self)
50-
app.activate()
55+
) -> Void = { _, files, nFiles, _, data in
56+
SignalBox2<UnsafeMutablePointer<OpaquePointer>, Int>.run(data, files, Int(nFiles))
5157
}
5258

53-
connectSignal(
54-
applicationPointer,
55-
name: "activate",
56-
data: Unmanaged.passUnretained(self).toOpaque(),
57-
handler: unsafeBitCast(handler, to: GCallback.self)
58-
)
59+
addSignal(name: "open", handler: gCallback(handler1)) {
60+
[weak self] (files: UnsafeMutablePointer<OpaquePointer>, nFiles: Int) in
61+
guard let self = self else { return }
62+
var uris: [URL] = []
63+
for i in 0..<nFiles {
64+
uris.append(
65+
GFile(files[i]).uri
66+
)
67+
}
68+
self.onOpen?(uris)
69+
}
70+
}
71+
72+
@discardableResult
73+
public func run(_ windowCallback: @escaping (ApplicationWindow) -> Void) -> Int {
74+
self.windowCallback = windowCallback
75+
5976
let status = g_application_run(applicationPointer.cast(), 0, nil)
6077
g_object_unref(applicationPointer)
6178
return Int(status)
6279
}
6380

6481
private func activate() {
82+
// When set up as a DBusActivatable application on Linux and launched
83+
// the GNOME app launcher, the activate signal triggers twice, causing
84+
// two instances of the application's main window unless we ignore the
85+
// second activation.
86+
guard !hasActivated else {
87+
return
88+
}
89+
90+
hasActivated = true
6591
let window = ApplicationWindow(application: self)
6692
windowCallback?(window)
67-
self.applicationWindow = window
6893
}
94+
95+
public var onOpen: (([URL]) -> Void)?
6996
}

Sources/Gtk/Utility/GFile.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import CGtk
2+
import Foundation
23

34
public class GFile: GObject {
45
public var path: String {
56
String(cString: g_file_get_path(opaquePointer))
67
}
8+
9+
public var uri: URL {
10+
URL(string: String(cString: g_file_get_uri(opaquePointer)))!
11+
}
712
}

Sources/Gtk3/Application.swift

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
//
44

55
import CGtk3
6+
import Foundation
7+
8+
public class Application: GObject, GActionMap {
9+
var applicationPointer: UnsafeMutablePointer<GtkApplication> {
10+
UnsafeMutablePointer(OpaquePointer(gobjectPointer))
11+
}
612

7-
public class Application: GActionMap {
8-
let applicationPointer: UnsafeMutablePointer<GtkApplication>
9-
private(set) var applicationWindow: ApplicationWindow?
1013
private var windowCallback: ((ApplicationWindow) -> Void)?
14+
private var hasActivated = false
1115

1216
public var actionMapPointer: OpaquePointer {
1317
OpaquePointer(applicationPointer)
@@ -27,43 +31,66 @@ public class Application: GActionMap {
2731
}
2832
}
2933

30-
public init(applicationId: String) {
31-
// Ignore the deprecation warning, making the change breaks support for platforms such as
32-
// Ubuntu (before Lunar). This is due to Ubuntu coming with an older version of Gtk in apt.
33-
#if GTK_4_10_PLUS
34-
applicationPointer = gtk_application_new(applicationId, G_APPLICATION_DEFAULT_FLAGS)
35-
#else
36-
applicationPointer = gtk_application_new(applicationId, G_APPLICATION_FLAGS_NONE)
37-
#endif
34+
@GObjectProperty(named: "register-session") public var registerSession: Bool
35+
36+
public init(applicationId: String, flags: GApplicationFlags = .init(rawValue: 0)) {
37+
super.init(
38+
gtk_application_new(applicationId, flags)
39+
)
40+
registerSignals()
3841
}
3942

40-
@discardableResult
41-
public func run(_ windowCallback: @escaping (ApplicationWindow) -> Void) -> Int {
42-
self.windowCallback = windowCallback
43+
public override func registerSignals() {
44+
addSignal(name: "activate") {
45+
self.activate()
46+
}
4347

44-
let handler:
48+
let handler1:
4549
@convention(c) (
50+
UnsafeMutableRawPointer,
51+
UnsafeMutablePointer<OpaquePointer>,
52+
gint,
4653
UnsafeMutableRawPointer,
4754
UnsafeMutableRawPointer
48-
) -> Void = { _, data in
49-
let app = unsafeBitCast(data, to: Application.self)
50-
app.activate()
55+
) -> Void = { _, files, nFiles, _, data in
56+
SignalBox2<UnsafeMutablePointer<OpaquePointer>, Int>.run(data, files, Int(nFiles))
5157
}
5258

53-
connectSignal(
54-
applicationPointer,
55-
name: "activate",
56-
data: Unmanaged.passUnretained(self).toOpaque(),
57-
handler: unsafeBitCast(handler, to: GCallback.self)
58-
)
59+
addSignal(name: "open", handler: gCallback(handler1)) {
60+
[weak self] (files: UnsafeMutablePointer<OpaquePointer>, nFiles: Int) in
61+
guard let self = self else { return }
62+
var uris: [URL] = []
63+
for i in 0..<nFiles {
64+
uris.append(
65+
GFile(files[i]).uri
66+
)
67+
}
68+
self.onOpen?(uris)
69+
}
70+
}
71+
72+
@discardableResult
73+
public func run(_ windowCallback: @escaping (ApplicationWindow) -> Void) -> Int {
74+
self.windowCallback = windowCallback
75+
5976
let status = g_application_run(applicationPointer.cast(), 0, nil)
6077
g_object_unref(applicationPointer)
6178
return Int(status)
6279
}
6380

6481
private func activate() {
82+
// When set up as a DBusActivatable application on Linux and launched
83+
// the GNOME app launcher, the activate signal triggers twice, causing
84+
// two instances of the application's main window unless we ignore the
85+
// second activation.
86+
guard !hasActivated else {
87+
return
88+
}
89+
90+
hasActivated = true
6591
let window = ApplicationWindow(application: self)
6692
windowCallback?(window)
67-
self.applicationWindow = window
6893
}
94+
95+
public var onOpen: (([URL]) -> Void)?
6996
}

Sources/Gtk3/Utility/GFile.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import CGtk3
2+
import Foundation
23

34
public class GFile: GObject {
45
public var path: String {
56
return String(cString: g_file_get_path(opaquePointer))
67
}
8+
9+
public var uri: URL {
10+
URL(string: String(cString: g_file_get_uri(opaquePointer)))!
11+
}
712
}

Sources/Gtk3Backend/Gtk3Backend.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ public final class Gtk3Backend: AppBackend {
4141
}
4242

4343
public init(appIdentifier: String) {
44-
gtkApp = Application(applicationId: appIdentifier)
44+
gtkApp = Application(
45+
applicationId: appIdentifier,
46+
flags: G_APPLICATION_HANDLES_OPEN
47+
)
48+
gtkApp.registerSession = true
4549
}
4650

4751
public func runMainLoop(_ callback: @escaping () -> Void) {
@@ -237,6 +241,14 @@ public final class Gtk3Backend: AppBackend {
237241
// TODO: React to theme changes
238242
}
239243

244+
public func setIncomingURLHandler(to action: @escaping (URL) -> Void) {
245+
gtkApp.onOpen = { urls in
246+
for url in urls {
247+
action(url)
248+
}
249+
}
250+
}
251+
240252
public func show(widget: Widget) {
241253
widget.show()
242254
}

Sources/GtkBackend/GtkBackend.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ public final class GtkBackend: AppBackend {
4141
}
4242

4343
public init(appIdentifier: String) {
44-
gtkApp = Application(applicationId: appIdentifier)
44+
gtkApp = Application(
45+
applicationId: appIdentifier,
46+
flags: G_APPLICATION_HANDLES_OPEN
47+
)
48+
gtkApp.registerSession = true
4549
}
4650

4751
public func runMainLoop(_ callback: @escaping () -> Void) {
@@ -236,6 +240,14 @@ public final class GtkBackend: AppBackend {
236240
// TODO: React to theme changes
237241
}
238242

243+
public func setIncomingURLHandler(to action: @escaping (URL) -> Void) {
244+
gtkApp.onOpen = { urls in
245+
for url in urls {
246+
action(url)
247+
}
248+
}
249+
}
250+
239251
public func show(widget: Widget) {
240252
widget.show()
241253
}

Sources/SwiftCrossUI/App.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ extension App {
3232
let app = Self()
3333
let _app = _App(app)
3434
_forceRefresh = {
35-
if String(describing: type(of: app.backend)) == "AppKitBackend" {
36-
DispatchQueue.main.async {
37-
_app.forceRefresh()
38-
}
39-
} else {
35+
app.backend.runInMainThread {
4036
_app.forceRefresh()
4137
}
4238
}

Sources/SwiftCrossUI/Backend/AppBackend.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ public protocol AppBackend {
150150
/// may or may not override the previous handler.
151151
func setRootEnvironmentChangeHandler(to action: @escaping () -> Void)
152152

153+
/// Sets the handler for URLs directed to the application (e.g. URLs
154+
/// associated with a custom URL scheme).
155+
func setIncomingURLHandler(to action: @escaping (URL) -> Void)
156+
153157
/// Shows a widget after it has been created or updated (may be unnecessary
154158
/// for some backends). Predominantly used by ``ViewGraphNode`` after
155159
/// propagating updates.
@@ -486,6 +490,10 @@ extension AppBackend {
486490
todo()
487491
}
488492

493+
public func setIncomingURLHandler(to action: @escaping (URL) -> Void) {
494+
todo()
495+
}
496+
489497
// MARK: Containers
490498

491499
public func createColorableRectangle() -> Widget {

0 commit comments

Comments
 (0)