diff --git a/.gitignore b/.gitignore
index e3282f7..5ac5cfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
.DS_Store
arm64
x86_64
+build
Grapp
Grapp.app
+ids
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1f48d33
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+APPLE_ID := $(shell cat ./ids/APPLE_ID)
+TEAM_ID := $(shell cat ./ids/TEAM_ID)
+APP_SPECIFIC_PASSWORD := $(shell cat ./ids/APP_SPECIFIC_PASSWORD)
+
+exe = Grapp
+
+$(exe).app:
+ $(MAKE) -C src FLAGS=-O CFLAGS=-O3 default
+
+container:
+ ditto -c -k --keepParent ./src/$(exe).app ./build/$(exe).zip
+
+notarize:
+ xcrun notarytool submit ./build/$(exe).zip \
+ --apple-id "$(APPLE_ID)" --team-id "$(TEAM_ID)" \
+ --password "$(APP_SPECIFIC_PASSWORD)" --wait
+
+staple:
+ cd build && \
+ ditto -xk $(exe).zip . && \
+ rm $(exe).zip && \
+ xcrun stapler staple $(exe).app
+
+zip:
+ cd build && \
+ ditto -c -k --keepParent $(exe).app $(exe).zip
+
+dmg:
+ cp background.png build && cd build && \
+ create-dmg --volname "$(exe) Installer" --window-size 600 400 \
+ --background "background.png" --icon "$(exe).app" 200 170 \
+ --app-drop-link 400 170 --icon-size 100 "$(exe).dmg" "$(exe)"
+ rm build/background.png
+
+all: $(exe).app container notarize staple zip dmg
+
+status:
+ @echo 'xcrun notarytool log "" --apple-id "$(APPLE_ID)" --team-id "$(TEAM_ID)" --password "$(APP_SPECIFIC_PASSWORD)" developer_log.json'
+
+clean:
+ rm -rf build
+
+clean-all: clean
+ mkdir build
+ @$(MAKE) -C src clean-all
diff --git a/src/AboutViewController.swift b/src/AboutViewController.swift
index 15629ef..90ff324 100644
--- a/src/AboutViewController.swift
+++ b/src/AboutViewController.swift
@@ -2,21 +2,14 @@ import AppKit
// TODO: Change to appropriate links.
fileprivate enum AboutLinks {
- // static let website = "https://cmdbar.app"
+ static let website = "https://cmdbar.rednera.com"
// static let documentation = "https://cmdbar.app/documentation"
- // static let privacy = "https://cmdbar.app/#privacy-policy"
- static let author = "https://kolokolnikov.org"
+ static let privacy = "https://cmdbar.rednera.com/#privacy-policy"
+ static let author = "https://kolokolnikov.dev"
}
enum Strings {
- static let copyright = "Copyright © 2024\nGarikMI. All rights reserved."
- static let evaluationTitle = "License - Evaluation"
- static let evaluationMessage = "You are currently using evaluation license. CmdBar will quit after 20 minutes. If you already own a license, enter it below or purchase a license."
- static let activate = "Activate"
- static let proTitle = "License - Activated"
- static let proMessage = "Thank you for purchasing CmdBar! Enjoy!"
- static let deactivate = "Deactivate"
- static let activating = "Activating..."
+ static let copyright = "Copyright © 2026 Rednera.\nAll rights reserved."
}
class AboutViewController: NSViewController, NSTextFieldDelegate {
@@ -83,26 +76,26 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
return textField
}()
- private var authorButton: NSButton = {
- let button = NSButton()
- button.title = "Author"
- button.sizeToFit()
- button.bezelStyle = .rounded
- button.action = #selector(author)
- button.translatesAutoresizingMaskIntoConstraints = false
- return button
- }()
-
- // private var privacyButton: NSButton = {
+ // private var authorButton: NSButton = {
// let button = NSButton()
- // button.title = "Privacy Policy"
+ // button.title = "Author"
// button.sizeToFit()
// button.bezelStyle = .rounded
- // button.action = #selector(privacy)
+ // button.action = #selector(author)
// button.translatesAutoresizingMaskIntoConstraints = false
// return button
// }()
+ private var privacyButton: NSButton = {
+ let button = NSButton()
+ button.title = "Privacy Policy"
+ button.sizeToFit()
+ button.bezelStyle = .rounded
+ button.action = #selector(privacy)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ return button
+ }()
+
// private var documentationButton: NSButton = {
// let button = NSButton()
// button.title = "Docs"
@@ -112,16 +105,16 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
// button.translatesAutoresizingMaskIntoConstraints = false
// return button
// }()
- //
- // private var websiteButton: NSButton = {
- // let button = NSButton()
- // button.title = "CmdBar.app"
- // button.sizeToFit()
- // button.bezelStyle = .rounded
- // button.action = #selector(website)
- // button.translatesAutoresizingMaskIntoConstraints = false
- // return button
- // }()
+
+ private var websiteButton: NSButton = {
+ let button = NSButton()
+ button.title = "Grapp"
+ button.sizeToFit()
+ button.bezelStyle = .rounded
+ button.action = #selector(website)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ return button
+ }()
private var buttonsContainer: NSLayoutGuide = {
let container = NSLayoutGuide()
@@ -139,10 +132,10 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
// Buttons
view.addLayoutGuide(buttonsContainer)
- // view.addSubview(privacyButton)
+ view.addSubview(privacyButton)
// view.addSubview(documentationButton)
- // view.addSubview(websiteButton)
- view.addSubview(authorButton)
+ view.addSubview(websiteButton)
+ // view.addSubview(authorButton)
setupConstraints()
}
@@ -208,51 +201,51 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
buttonsContainer.centerXAnchor
.constraint(equalTo: view.centerXAnchor),
- authorButton.topAnchor
- .constraint(equalTo: buttonsContainer.topAnchor),
- authorButton.bottomAnchor
- .constraint(equalTo: buttonsContainer.bottomAnchor),
- authorButton.leadingAnchor
- .constraint(equalTo: buttonsContainer.leadingAnchor),
- authorButton.trailingAnchor
- .constraint(equalTo: buttonsContainer.trailingAnchor),
-
- // privacyButton.topAnchor
+ // authorButton.topAnchor
// .constraint(equalTo: buttonsContainer.topAnchor),
- // privacyButton.bottomAnchor
+ // authorButton.bottomAnchor
// .constraint(equalTo: buttonsContainer.bottomAnchor),
- // privacyButton.leadingAnchor
+ // authorButton.leadingAnchor
// .constraint(equalTo: buttonsContainer.leadingAnchor),
- //
+ // authorButton.trailingAnchor
+ // .constraint(equalTo: buttonsContainer.trailingAnchor),
+
+ privacyButton.topAnchor
+ .constraint(equalTo: buttonsContainer.topAnchor),
+ privacyButton.bottomAnchor
+ .constraint(equalTo: buttonsContainer.bottomAnchor),
+ privacyButton.leadingAnchor
+ .constraint(equalTo: buttonsContainer.leadingAnchor),
+
// documentationButton.firstBaselineAnchor
// .constraint(equalTo: privacyButton.firstBaselineAnchor),
// documentationButton.leadingAnchor
// .constraint(equalTo: privacyButton.trailingAnchor,
// constant: ViewConstants.spacing10),
- //
- // websiteButton.firstBaselineAnchor
- // .constraint(equalTo: privacyButton.firstBaselineAnchor),
- // websiteButton.leadingAnchor
- // .constraint(equalTo: documentationButton.trailingAnchor,
- // constant: ViewConstants.spacing10),
- // websiteButton.trailingAnchor
- // .constraint(equalTo: buttonsContainer.trailingAnchor),
+
+ websiteButton.firstBaselineAnchor
+ .constraint(equalTo: privacyButton.firstBaselineAnchor),
+ websiteButton.leadingAnchor
+ .constraint(equalTo: privacyButton.trailingAnchor,
+ constant: ViewConstants.spacing10),
+ websiteButton.trailingAnchor
+ .constraint(equalTo: buttonsContainer.trailingAnchor),
])
}
- @objc private func author() {
- NSWorkspace.shared.open(URL(string: AboutLinks.author)!)
+ // @objc private func author() {
+ // NSWorkspace.shared.open(URL(string: AboutLinks.author)!)
+ // }
+
+ @objc private func privacy() {
+ NSWorkspace.shared.open(URL(string: AboutLinks.privacy)!)
}
- // @objc private func privacy() {
- // NSWorkspace.shared.open(URL(string: AboutLinks.privacy)!)
- // }
- //
// @objc private func documentation() {
// NSWorkspace.shared.open(URL(string: AboutLinks.documentation)!)
// }
- //
- // @objc private func website() {
- // NSWorkspace.shared.open(URL(string: AboutLinks.website)!)
- // }
+
+ @objc private func website() {
+ NSWorkspace.shared.open(URL(string: AboutLinks.website)!)
+ }
}
diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift
index 82dcfff..3893bac 100644
--- a/src/AppDelegate.swift
+++ b/src/AppDelegate.swift
@@ -2,6 +2,41 @@ import Cocoa
import Carbon
import ServiceManagement
+class ClipboardMonitor {
+ private var timer: Timer?
+ private var lastChangeCount: Int = NSPasteboard.general.changeCount
+
+ func startMonitoring(interval: TimeInterval = 0.5) {
+ timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
+ print("CALLED")
+ self?.checkForChanges()
+ }
+ }
+
+ func stopMonitoring() {
+ timer?.invalidate()
+ timer = nil
+ }
+
+ private func checkForChanges() {
+ print("CHECKING")
+ let pasteboard = NSPasteboard.general
+ if pasteboard.changeCount != lastChangeCount {
+ lastChangeCount = pasteboard.changeCount
+ handlePasteboardChange(pasteboard)
+ }
+ }
+
+ private func handlePasteboardChange(_ pasteboard: NSPasteboard) {
+ if let copiedString = pasteboard.string(forType: .string) {
+ print("Clipboard changed: \(copiedString)")
+ } else {
+ print("Clipboard changed (non-string content)")
+ }
+ }
+}
+
+
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
let fileManager = FileManager.default
@@ -10,7 +45,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
MenulessWindow(viewController: SettingsViewController())
let aboutWindow = MenulessWindow(viewController: AboutViewController())
+ var monitor: ClipboardMonitor?
+
func applicationDidFinishLaunching(_ notification: Notification) {
+
+
+monitor = ClipboardMonitor()
+monitor?.startMonitoring()
+
+
+
+
+
+
settingsWindow.title = "Settings"
aboutWindow.level = .statusBar
diff --git a/src/Info.plist b/src/Info.plist
index 26bb547..ec531bc 100644
--- a/src/Info.plist
+++ b/src/Info.plist
@@ -11,7 +11,7 @@
CFBundleIconName
AppIcon
CFBundleIdentifier
- com.garikme.Grapp
+ com.rednera.Grapp
CFBundleInfoDictionaryVersion
6.0
CFBundleName
@@ -19,13 +19,13 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.2
+ 0.2.0
CFBundleSupportedPlatforms
MacOSX
CFBundleVersion
- 0.2
+ 0.2.0
DTPlatformName
macosx
DTPlatformVersion
@@ -40,5 +40,7 @@
NSPrincipalClass
NSApplication
+ NSHumanReadableCopyright
+ Copyright © 2026 Rednera. All rights reserved.
diff --git a/src/Makefile b/src/Makefile
index 8408377..de0be60 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,3 +1,6 @@
+APPLE_DEVELOPMENT := $(shell cat ../ids/APPLE_DEVELOPMENT)
+APPLE_DEVELOPER_ID_APPLICATION := $(shell cat ../ids/APPLE_DEVELOPER_ID_APPLICATION)
+
FLAGS = -g
CFLAGS = -g
#-O
@@ -23,12 +26,6 @@ FRAMEWORKS = -framework AppKit -framework ServiceManagement
default: $(EXEC).app
-# HACK: Target is getting touched because timestamps of the generated
-# object file don't change unless there's an actual change in the
-# outputted object code. This results in this target running every
-# single time. I'm not sure whether that's the exact reason, but
-# I can't imagine why timestamps wouldn't change. When clang
-# generates same exact executable, timestamps do change.
./arm64/%.o: %.swift
swift -frontend -c \
-target arm64-apple-macos$(MACOS_VERSION) $(FLAGS) \
@@ -86,11 +83,11 @@ $(EXEC).app: $(EXEC)
mkdir -p $@/Contents/Resources/ && \
cp Info.plist $@/Contents/ && \
cp resources/AppIcon.icns $@/Contents/Resources/ && \
- cp $(EXEC) $@/Contents/MacOS/
- # $(if $(DEBUG), codesign --entitlements Grapp.entitlements \
- # -s ${APPLE_DEVELOPMENT} -f --timestamp -o runtime $(EXEC).app, \
- # codesign -s ${APPLE_DEVELOPER_ID_APPLICATION} -f --timestamp \
- # -o runtime $(EXEC).app)
+ cp $(EXEC) $@/Contents/MacOS/ && \
+ $(if $(DEBUG), codesign --entitlements Grapp.entitlements \
+ -s ${APPLE_DEVELOPMENT} -f --timestamp -o runtime $(EXEC).app, \
+ codesign -s ${APPLE_DEVELOPER_ID_APPLICATION} -f --timestamp \
+ -o runtime $(EXEC).app)
clean:
rm -rf $(EXEC) $(EXEC).app arm64 x86_64
diff --git a/src/PathManager.swift b/src/PathManager.swift
index 8c5ea4b..44ba077 100644
--- a/src/PathManager.swift
+++ b/src/PathManager.swift
@@ -19,9 +19,9 @@ final class PathManager {
"/Applications",
"/System/Applications",
"/System/Applications/Utilities",
- "/System/Library/CoreServices",
- "/Applications/Xcode.app/Contents/Applications",
- "/System/Library/CoreServices/Applications"
+ "/System/Library/CoreServices", // TODO: NOTE: Remove this one?
+ "/System/Library/CoreServices/Applications",
+ "/Applications/Xcode.app/Contents/Applications"
]
private(set) var paths: [String: [Program]] = [:]