Skip to content

Commit 55e201a

Browse files
authored
Merge pull request #190 from theJayTea/macOS-v4_4
macOS v4.1
2 parents 12820f7 + c5fc9ec commit 55e201a

22 files changed

+2752
-1104
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.0
1+
4.1

macOS/README.md

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,9 @@ Core functionality works well, and it is still an ongoing work in progress.
66

77
---
88

9-
## Working Features
10-
- All of the tools, including the new response windows and the manual chat option.
11-
- Input Window even when no text is selected
12-
- Gemini, OpenAI and Local LLM Support.
13-
- The Gradient Theme (Dark Mode and Light Mode are supported).
14-
- Initial Setup, Settings, and About pages.
15-
16-
---
17-
18-
## Not Yet Available
19-
- All of the original port's features are now available; however, more optimizations and improvements are coming soon.
20-
21-
---
22-
239
## System Requirements
24-
Due to the accessibility features the app uses (e.g., automatically selecting the window containing the text and pasting the updated version), **the minimum macOS version required is 14.0**.
10+
Due to the accessibility features the app uses (e.g., automatically selecting the window containing the text and pasting the updated version), **the minimum macOS version required is 14.0**. You also have to allow this accessibility access in System Settings.
11+
> System Settings → Privacy & Security → Accessibility → Press the plus (+) button → Add *writing-tools* and enable access.
2512
2613
---
2714

@@ -72,14 +59,10 @@ This guide will help you properly set up the Writing Tools macOS project in Xcod
7259

7360
## Credits
7461

75-
The macOS port is being developed by **Aryamirsepasi**.
76-
77-
GitHub: [https://github.com/Aryamirsepasi](https://github.com/Aryamirsepasi)
78-
79-
The amazing picture processing functionality was created by **Joaov41**
80-
GitHub: [https://github.com/Joaov41](https://github.com/Joaov41)
62+
The macOS port is being developed by **Aryamirsepasi**. [GitHub](https://github.com/Aryamirsepasi)
8163

82-
Special Thanks to @sindresorhus for developing an amazing and stable keyboard shortcuts package for Swift.
64+
The amazing gemini picture processing functionality was created by **Joaov41**. [GitHub](https://github.com/Joaov41)
8365

84-
GitHub: [https://github.com/sindresorhus/KeyboardShortcuts](https://github.com/sindresorhus/KeyboardShortcuts)
66+
Special Thanks to @sindresorhus for developing a great and stable keyboard shortcuts package for Swift. [GitHub](https://github.com/sindresorhus/KeyboardShortcuts)
8567

68+
Huge shoutout to MLX Swift Team, creating local LLM compatibility on Apple silicon Macs. [GitHub](https://github.com/ml-explore/mlx-swift-examples)

macOS/writing-tools.xcodeproj/project.pbxproj

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
attributes = {
134134
BuildIndependentTargetsInParallel = 1;
135135
LastSwiftUpdateCheck = 1610;
136-
LastUpgradeCheck = 1620;
136+
LastUpgradeCheck = 1630;
137137
TargetAttributes = {
138138
2ABCBC1D2CDEB606001E4B5E = {
139139
CreatedOnToolsVersion = 16.1;
@@ -195,6 +195,7 @@
195195
buildSettings = {
196196
ALWAYS_SEARCH_USER_PATHS = NO;
197197
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
198+
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
198199
CLANG_ANALYZER_NONNULL = YES;
199200
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
200201
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -226,6 +227,7 @@
226227
COPY_PHASE_STRIP = NO;
227228
DEAD_CODE_STRIPPING = YES;
228229
DEBUG_INFORMATION_FORMAT = dwarf;
230+
DEVELOPMENT_TEAM = MK2V998W66;
229231
ENABLE_STRICT_OBJC_MSGSEND = YES;
230232
ENABLE_TESTABILITY = YES;
231233
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -260,6 +262,7 @@
260262
buildSettings = {
261263
ALWAYS_SEARCH_USER_PATHS = NO;
262264
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
265+
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
263266
CLANG_ANALYZER_NONNULL = YES;
264267
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
265268
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -291,6 +294,7 @@
291294
COPY_PHASE_STRIP = NO;
292295
DEAD_CODE_STRIPPING = YES;
293296
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
297+
DEVELOPMENT_TEAM = MK2V998W66;
294298
ENABLE_NS_ASSERTIONS = NO;
295299
ENABLE_STRICT_OBJC_MSGSEND = YES;
296300
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -318,14 +322,15 @@
318322
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
319323
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
320324
CODE_SIGN_ENTITLEMENTS = "writing-tools/writing_tools.entitlements";
321-
CODE_SIGN_IDENTITY = "Apple Development";
322-
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
323-
CODE_SIGN_STYLE = Automatic;
325+
CODE_SIGN_IDENTITY = "Developer ID Application";
326+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
327+
CODE_SIGN_STYLE = Manual;
324328
COMBINE_HIDPI_IMAGES = YES;
325-
CURRENT_PROJECT_VERSION = 9;
329+
CURRENT_PROJECT_VERSION = 13;
326330
DEAD_CODE_STRIPPING = YES;
327331
DEVELOPMENT_ASSET_PATHS = "\"writing-tools/Preview Content\"";
328-
DEVELOPMENT_TEAM = MK2V998W66;
332+
DEVELOPMENT_TEAM = "";
333+
"DEVELOPMENT_TEAM[sdk=macosx*]" = MK2V998W66;
329334
ENABLE_HARDENED_RUNTIME = YES;
330335
ENABLE_PREVIEWS = YES;
331336
GENERATE_INFOPLIST_FILE = YES;
@@ -338,7 +343,7 @@
338343
"@executable_path/../Frameworks",
339344
);
340345
MACOSX_DEPLOYMENT_TARGET = 14.0;
341-
MARKETING_VERSION = 4.0;
346+
MARKETING_VERSION = 4.1;
342347
PRODUCT_BUNDLE_IDENTIFIER = "com.aryamirsepasi.writing-tools";
343348
PRODUCT_NAME = "$(TARGET_NAME)";
344349
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -353,14 +358,15 @@
353358
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
354359
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
355360
CODE_SIGN_ENTITLEMENTS = "writing-tools/writing_tools.entitlements";
356-
CODE_SIGN_IDENTITY = "Apple Development";
357-
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
358-
CODE_SIGN_STYLE = Automatic;
361+
CODE_SIGN_IDENTITY = "Developer ID Application";
362+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
363+
CODE_SIGN_STYLE = Manual;
359364
COMBINE_HIDPI_IMAGES = YES;
360-
CURRENT_PROJECT_VERSION = 9;
365+
CURRENT_PROJECT_VERSION = 13;
361366
DEAD_CODE_STRIPPING = YES;
362367
DEVELOPMENT_ASSET_PATHS = "\"writing-tools/Preview Content\"";
363-
DEVELOPMENT_TEAM = MK2V998W66;
368+
DEVELOPMENT_TEAM = "";
369+
"DEVELOPMENT_TEAM[sdk=macosx*]" = MK2V998W66;
364370
ENABLE_HARDENED_RUNTIME = YES;
365371
ENABLE_PREVIEWS = YES;
366372
GENERATE_INFOPLIST_FILE = YES;
@@ -373,7 +379,7 @@
373379
"@executable_path/../Frameworks",
374380
);
375381
MACOSX_DEPLOYMENT_TARGET = 14.0;
376-
MARKETING_VERSION = 4.0;
382+
MARKETING_VERSION = 4.1;
377383
PRODUCT_BUNDLE_IDENTIFIER = "com.aryamirsepasi.writing-tools";
378384
PRODUCT_NAME = "$(TARGET_NAME)";
379385
PROVISIONING_PROFILE_SPECIFIER = "";

macOS/writing-tools.xcodeproj/xcshareddata/xcschemes/swift-writing-tools.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1620"
3+
LastUpgradeVersion = "1630"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"

macOS/writing-tools/App/AppDelegate.swift

Lines changed: 94 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
232232
menu.addItem(NSMenuItem(title: "Settings", action: #selector(showSettings), keyEquivalent: ","))
233233
menu.addItem(NSMenuItem(title: "About", action: #selector(showAbout), keyEquivalent: "i"))
234234
// New Pause/Resume item:
235-
let hotkeyTitle = AppSettings.shared.hotkeysPaused ? "Resume" : "Pause"
236-
menu.addItem(NSMenuItem(title: hotkeyTitle, action: #selector(toggleHotkeys), keyEquivalent: "p"))
235+
let hotkeyTitle = AppSettings.shared.hotkeysPaused ? "Resume" : "Pause"
236+
menu.addItem(NSMenuItem(title: hotkeyTitle, action: #selector(toggleHotkeys), keyEquivalent: "p"))
237237
menu.addItem(NSMenuItem(title: "Reset App", action: #selector(resetApp), keyEquivalent: "r"))
238238
menu.addItem(NSMenuItem.separator())
239239
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
@@ -363,111 +363,111 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
363363
}
364364

365365
@MainActor private func showPopup() {
366-
appState.activeProvider.cancel()
366+
appState.activeProvider.cancel()
367+
368+
DispatchQueue.main.async { [weak self] in
369+
guard let self = self else { return }
367370

368-
DispatchQueue.main.async { [weak self] in
371+
if let currentFrontmostApp = NSWorkspace.shared.frontmostApplication {
372+
self.appState.previousApplication = currentFrontmostApp
373+
}
374+
375+
self.closePopupWindow()
376+
377+
let generalPasteboard = NSPasteboard.general
378+
let oldContents = generalPasteboard.string(forType: .string)
379+
380+
// Clear and perform copy command to get current selection
381+
generalPasteboard.clearContents()
382+
let source = CGEventSource(stateID: .hidSystemState)
383+
let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: true)
384+
let keyUp = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: false)
385+
keyDown?.flags = .maskCommand
386+
keyUp?.flags = .maskCommand
387+
keyDown?.post(tap: .cghidEventTap)
388+
keyUp?.post(tap: .cghidEventTap)
389+
390+
// Wait for the copy operation to complete, then process the pasteboard
391+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
369392
guard let self = self else { return }
370393

371-
if let currentFrontmostApp = NSWorkspace.shared.frontmostApplication {
372-
self.appState.previousApplication = currentFrontmostApp
373-
}
374-
375-
self.closePopupWindow()
394+
var foundImages: [Data] = []
395+
var selectedText = ""
376396

377-
let generalPasteboard = NSPasteboard.general
378-
let oldContents = generalPasteboard.string(forType: .string)
379-
380-
// Clear and perform copy command to get current selection
381-
generalPasteboard.clearContents()
382-
let source = CGEventSource(stateID: .hidSystemState)
383-
let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: true)
384-
let keyUp = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: false)
385-
keyDown?.flags = .maskCommand
386-
keyUp?.flags = .maskCommand
387-
keyDown?.post(tap: .cghidEventTap)
388-
keyUp?.post(tap: .cghidEventTap)
389-
390-
// Wait for the copy operation to complete, then process the pasteboard
391-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
392-
guard let self = self else { return }
393-
394-
var foundImages: [Data] = []
395-
var selectedText = ""
396-
397-
// First check for file URLs (for Finder selections)
398-
let classes = [NSURL.self]
399-
let options: [NSPasteboard.ReadingOptionKey: Any] = [
400-
.urlReadingFileURLsOnly: true,
401-
.urlReadingContentsConformToTypes: [
402-
"public.image",
403-
"public.png",
404-
"public.jpeg",
405-
"public.tiff",
406-
"com.compuserve.gif"
407-
]
397+
// First check for file URLs (for Finder selections)
398+
let classes = [NSURL.self]
399+
let options: [NSPasteboard.ReadingOptionKey: Any] = [
400+
.urlReadingFileURLsOnly: true,
401+
.urlReadingContentsConformToTypes: [
402+
"public.image",
403+
"public.png",
404+
"public.jpeg",
405+
"public.tiff",
406+
"com.compuserve.gif"
408407
]
409-
410-
if let urls = generalPasteboard.readObjects(forClasses: classes, options: options) as? [URL] {
411-
for url in urls {
412-
if let imageData = try? Data(contentsOf: url) {
413-
if NSImage(data: imageData) != nil {
414-
foundImages.append(imageData)
415-
NSLog("Loaded image data from file: \(url.lastPathComponent)")
416-
}
408+
]
409+
410+
if let urls = generalPasteboard.readObjects(forClasses: classes, options: options) as? [URL] {
411+
for url in urls {
412+
if let imageData = try? Data(contentsOf: url) {
413+
if NSImage(data: imageData) != nil {
414+
foundImages.append(imageData)
415+
NSLog("Loaded image data from file: \(url.lastPathComponent)")
417416
}
418417
}
419418
}
419+
}
420+
421+
// If no file URLs found, check for direct image data
422+
if foundImages.isEmpty {
423+
let supportedImageTypes = [
424+
NSPasteboard.PasteboardType("public.png"),
425+
NSPasteboard.PasteboardType("public.jpeg"),
426+
NSPasteboard.PasteboardType("public.tiff"),
427+
NSPasteboard.PasteboardType("com.compuserve.gif"),
428+
NSPasteboard.PasteboardType("public.image")
429+
]
420430

421-
// If no file URLs found, check for direct image data
422-
if foundImages.isEmpty {
423-
let supportedImageTypes = [
424-
NSPasteboard.PasteboardType("public.png"),
425-
NSPasteboard.PasteboardType("public.jpeg"),
426-
NSPasteboard.PasteboardType("public.tiff"),
427-
NSPasteboard.PasteboardType("com.compuserve.gif"),
428-
NSPasteboard.PasteboardType("public.image")
429-
]
430-
431-
for type in supportedImageTypes {
432-
if let data = generalPasteboard.data(forType: type) {
433-
foundImages.append(data)
434-
NSLog("Found direct image data of type: \(type)")
435-
break
436-
}
431+
for type in supportedImageTypes {
432+
if let data = generalPasteboard.data(forType: type) {
433+
foundImages.append(data)
434+
NSLog("Found direct image data of type: \(type)")
435+
break
437436
}
438437
}
439-
440-
// Get any text content
441-
selectedText = generalPasteboard.string(forType: .string) ?? ""
442-
443-
// Restore original pasteboard contents
444-
generalPasteboard.clearContents()
445-
if let oldContents = oldContents {
446-
generalPasteboard.setString(oldContents, forType: .string)
447-
}
448-
449-
// Update app state and show popup
450-
self.appState.selectedImages = foundImages
451-
self.appState.selectedText = selectedText
452-
453-
let window = PopupWindow(appState: self.appState)
454-
window.delegate = self
455-
self.popupWindow = window
456-
457-
// Set window size based on content
458-
if !selectedText.isEmpty || !foundImages.isEmpty {
459-
window.setContentSize(NSSize(width: 400, height: 400))
460-
} else {
461-
window.setContentSize(NSSize(width: 400, height: 100))
462-
}
463-
464-
window.positionNearMouse()
465-
NSApp.activate(ignoringOtherApps: true)
466-
window.makeKeyAndOrderFront(nil)
467-
window.orderFrontRegardless()
468438
}
439+
440+
// Get any text content
441+
selectedText = generalPasteboard.string(forType: .string) ?? ""
442+
443+
// Restore original pasteboard contents
444+
generalPasteboard.clearContents()
445+
if let oldContents = oldContents {
446+
generalPasteboard.setString(oldContents, forType: .string)
447+
}
448+
449+
// Update app state and show popup
450+
self.appState.selectedImages = foundImages
451+
self.appState.selectedText = selectedText
452+
453+
let window = PopupWindow(appState: self.appState)
454+
window.delegate = self
455+
self.popupWindow = window
456+
457+
// Set window size based on content
458+
if !selectedText.isEmpty || !foundImages.isEmpty {
459+
window.setContentSize(NSSize(width: 400, height: 400))
460+
} else {
461+
window.setContentSize(NSSize(width: 400, height: 100))
462+
}
463+
464+
window.positionNearMouse()
465+
NSApp.activate(ignoringOtherApps: true)
466+
window.makeKeyAndOrderFront(nil)
467+
window.orderFrontRegardless()
469468
}
470469
}
470+
}
471471

472472
// Closes and cleans up the popup window
473473
private func closePopupWindow() {
@@ -512,7 +512,7 @@ extension AppDelegate {
512512

513513
// Register services provider
514514
NSApp.servicesProvider = self
515-
515+
516516
// Register the service
517517
NSUpdateDynamicServices()
518518
}

0 commit comments

Comments
 (0)