Skip to content

Commit

Permalink
Add support for system services
Browse files Browse the repository at this point in the history
  • Loading branch information
cyanzhong committed Apr 4, 2024
1 parent a935f51 commit 2f35421
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 5 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

TextGrabber2 is a free and **open-source** macOS menu bar app that efficiently detects text from copied images. This eliminates the need to save images as files and then delete them solely for the purpose of text detection.

<img src="./Screenshots/01.png" width="320" alt="Screenshot 01"> <img src="./Screenshots/02.png" width="320" alt="Screenshot 02">
<img src="./Screenshots/01.png" width="360" alt="Screenshot 01"> <img src="./Screenshots/02.png" width="360" alt="Screenshot 02">

For example, press `Control-Shift-Command-4` to capture a portion of the screen and then open TextGrabber2 from the menu bar.

Expand All @@ -30,7 +30,7 @@ TextGrabber2 is NOT a screenshot tool, meaning it doesn't require access like `S

TextGrabber2 utilizes the built-in [Vision](https://developer.apple.com/documentation/vision/) framework, which is on-device, secure, fast, accurate, and **free**. In fact, it's often superior to many paid services.

TextGrabber2 automatically extracts data types like numbers and links, making copying super easy.
TextGrabber2 connects to [system services](https://github.com/TextGrabber2-app/TextGrabber2/wiki#connect-to-system-services), you can easily integrate your workflows.

TextGrabber2 does NOT have any settings; it works magically until something goes wrong.

Expand Down
Binary file modified Screenshots/01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions TextGrabber2.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
8737A24A2BBEC27700042E83 /* Services in Resources */ = {isa = PBXBuildFile; fileRef = 8737A2492BBEC27700042E83 /* Services */; };
8737A24C2BBECB8800042E83 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8737A24B2BBECB8800042E83 /* Services.swift */; };
8772FAE92BAC743D00DBEAA0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8772FAE82BAC743D00DBEAA0 /* Localizable.xcstrings */; };
87E192C32BAADE1200A87A4E /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E192C22BAADE1200A87A4E /* App.swift */; };
87E192C72BAADE1300A87A4E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87E192C62BAADE1300A87A4E /* Assets.xcassets */; };
Expand All @@ -28,6 +30,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
8737A2492BBEC27700042E83 /* Services */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Services; sourceTree = "<group>"; };
8737A24B2BBECB8800042E83 /* Services.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
8772FAE82BAC743D00DBEAA0 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
87E192BF2BAADE1200A87A4E /* TextGrabber2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextGrabber2.app; sourceTree = BUILT_PRODUCTS_DIR; };
87E192C22BAADE1200A87A4E /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -65,6 +69,7 @@
8772FAE72BAC73F100DBEAA0 /* Resources */ = {
isa = PBXGroup;
children = (
8737A2492BBEC27700042E83 /* Services */,
87E192C62BAADE1300A87A4E /* Assets.xcassets */,
8772FAE82BAC743D00DBEAA0 /* Localizable.xcstrings */,
);
Expand Down Expand Up @@ -133,6 +138,7 @@
87E192F62BABD18900A87A4E /* Logger.swift */,
87E192F02BABB6EA00A87A4E /* Recognizer.swift */,
87E192E82BAAFF0200A87A4E /* Resources.swift */,
8737A24B2BBECB8800042E83 /* Services.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -197,6 +203,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8737A24A2BBEC27700042E83 /* Services in Resources */,
8772FAE92BAC743D00DBEAA0 /* Localizable.xcstrings in Resources */,
87E192C72BAADE1300A87A4E /* Assets.xcassets in Resources */,
);
Expand All @@ -209,6 +216,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8737A24C2BBECB8800042E83 /* Services.swift in Sources */,
87E192DF2BAAE38700A87A4E /* NSControl+Extension.swift in Sources */,
87E192F32BABB71D00A87A4E /* NSPasteboard+Extension.swift in Sources */,
87E192E72BAAFBC900A87A4E /* SMAppService+Extension.swift in Sources */,
Expand Down
68 changes: 68 additions & 0 deletions TextGrabber2/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@
}
}
},
"Configure" : {
"comment" : "[Menu] Configure system services",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "配置"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "配置"
}
}
}
},
"Copy All" : {
"comment" : "[Menu] Copy all text at once",
"localizations" : {
Expand All @@ -86,6 +103,40 @@
}
}
},
"Documentation" : {
"comment" : "[Menu] Open the wiki for system services",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "文档"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "文檔"
}
}
}
},
"en-US" : {
"comment" : "Identifier used to locate localized resources",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "zh-Hans"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "zh-Hant"
}
}
}
},
"GitHub" : {
"comment" : "[Menu] Open the TextGrabber2 repository on GitHub",
"localizations" : {
Expand Down Expand Up @@ -222,6 +273,23 @@
}
}
},
"Services" : {
"comment" : "[Menu] System services menu",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "服务"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "服務"
}
}
}
},
"Version" : {
"comment" : "[Menu] Version number label",
"localizations" : {
Expand Down
34 changes: 34 additions & 0 deletions TextGrabber2/Resources/Services/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"serviceName": "Look Up in Dictionary",
"displayName": "Look Up in Dictionary"
},
{
"serviceName": "Search With %WebSearchProvider@",
"displayName": "Search in Safari"
},
{
"serviceName": "SEARCH_WITH_SPOTLIGHT",
"displayName": "Search with Spotlight"
},
{
"serviceName": "Convert Text from Simplified to Traditional Chinese",
"displayName": "Convert to Traditional Chinese"
},
{
"serviceName": "Convert Text from Traditional to Simplified Chinese",
"displayName": "Convert to Simplified Chinese"
},
{
"serviceName": "New TextEdit Window Containing Selection",
"displayName": "New File in TextEdit"
},
{
"serviceName": "Make Sticky",
"displayName": "Make New Sticky Note"
},
{
"serviceName": "Summarize",
"displayName": "Summarize"
}
]
34 changes: 34 additions & 0 deletions TextGrabber2/Resources/Services/zh-Hans.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"serviceName": "Look Up in Dictionary",
"displayName": "在词典中查询"
},
{
"serviceName": "Search With %WebSearchProvider@",
"displayName": "在 Safari 中搜索"
},
{
"serviceName": "SEARCH_WITH_SPOTLIGHT",
"displayName": "用 Spotlight 搜索"
},
{
"serviceName": "Convert Text from Simplified to Traditional Chinese",
"displayName": "转换为繁体中文"
},
{
"serviceName": "Convert Text from Traditional to Simplified Chinese",
"displayName": "转换为简体中文"
},
{
"serviceName": "New TextEdit Window Containing Selection",
"displayName": "在 TextEdit 新建文件"
},
{
"serviceName": "Make Sticky",
"displayName": "创建新便笺条"
},
{
"serviceName": "Summarize",
"displayName": "摘要"
}
]
34 changes: 34 additions & 0 deletions TextGrabber2/Resources/Services/zh-Hant.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"serviceName": "Look Up in Dictionary",
"displayName": "在辭典中查詢"
},
{
"serviceName": "Search With %WebSearchProvider@",
"displayName": "在 Safari 中搜尋"
},
{
"serviceName": "SEARCH_WITH_SPOTLIGHT",
"displayName": "用 Spotlight 搜尋"
},
{
"serviceName": "Convert Text from Simplified to Traditional Chinese",
"displayName": "轉換為繁體中文"
},
{
"serviceName": "Convert Text from Traditional to Simplified Chinese",
"displayName": "轉換為簡體中文"
},
{
"serviceName": "New TextEdit Window Containing Selection",
"displayName": "在 TextEdit 新建檔案"
},
{
"serviceName": "Make Sticky",
"displayName": "建立新便條紙"
},
{
"serviceName": "Summarize",
"displayName": "摘要"
}
]
36 changes: 33 additions & 3 deletions TextGrabber2/Sources/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final class App: NSObject, NSApplicationDelegate {
menu.addItem(howToItem)
menu.addItem(.separator())
menu.addItem(copyAllItem)
menu.addItem(servicesItem)
menu.addItem(clipboardItem)
menu.addItem(.separator())
menu.addItem(launchAtLoginItem)
Expand Down Expand Up @@ -82,6 +83,23 @@ final class App: NSObject, NSApplicationDelegate {
return item
}()

private let servicesItem: NSMenuItem = {
let item = NSMenuItem(title: Localized.menuTitleServices)
let menu = NSMenu()
menu.addItem(.separator())

menu.addItem(withTitle: Localized.menuTitleConfigure) {
NSWorkspace.shared.open(Services.fileURL)
}

menu.addItem(withTitle: Localized.menuTitleDocumentation) {
NSWorkspace.shared.safelyOpenURL(string: "\(Links.github)/wiki#connect-to-system-services")
}

item.submenu = menu
return item
}()

private let clipboardItem: NSMenuItem = {
let item = NSMenuItem(title: Localized.menuTitleClipboard)
let menu = NSMenu()
Expand Down Expand Up @@ -118,6 +136,7 @@ final class App: NSObject, NSApplicationDelegate {

extension App {
func applicationDidFinishLaunching(_ notification: Notification) {
Services.initialize()
clearMenuItems()
statusItem.isVisible = true
}
Expand All @@ -128,6 +147,18 @@ extension App {
extension App: NSMenuDelegate {
func menuWillOpen(_ menu: NSMenu) {
startDetection()

// Update the services menu
servicesItem.submenu?.removeItems { $0 is ServiceItem }
for service in Services.items.reversed() {
let item = ServiceItem(title: service.displayName)
item.addAction {
NSPasteboard.general.string = self.currentResult?.spacesJoined
NSPerformService(service.serviceName, .general)
}

servicesItem.submenu?.insertItem(item, at: 0)
}

// For an edge case, we can capture the screen while the menu is shown.
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
Expand All @@ -153,9 +184,8 @@ extension App: NSMenuDelegate {
// MARK: - Private

private extension App {
class ResultItem: NSMenuItem {
/* Just a sub-class to be identifiable */
}
class ResultItem: NSMenuItem { /* Just a sub-class to be identifiable */ }
class ServiceItem: NSMenuItem { /* Just a sub-class to be identifiable */ }

func clearMenuItems() {
hintItem.title = Localized.menuTitleHintCapture
Expand Down
4 changes: 4 additions & 0 deletions TextGrabber2/Sources/Resources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ import Foundation
https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog
*/
enum Localized {
static let languageIdentifier = String(localized: "en-US", comment: "Identifier used to locate localized resources")
static let menuTitleHintCapture = String(localized: "Capture Screen to Detect", comment: "[Menu] Hint for capturing the screen")
static let menuTitleHintCopy = String(localized: "Click to Copy", comment: "[Menu] Hint for copying text")
static let menuTitleHintRecognizing = String(localized: "Recognizing...", comment: "[Menu] The recognition is ongoing")
static let menuTitleHowTo = String(localized: "How to Capture?", comment: "[Menu] How to use the app")
static let menuTitleCopyAll = String(localized: "Copy All", comment: "[Menu] Copy all text at once")
static let menuTitleJoinWithLineBreaks = String(localized: "Join with Line Breaks", comment: "[Menu] Join all text with line breaks and copy them")
static let menuTitleJoinWithSpaces = String(localized: "Join with Spaces", comment: "[Menu] Join all text with spaces and copy them")
static let menuTitleServices = String(localized: "Services", comment: "[Menu] System services menu")
static let menuTitleConfigure = String(localized: "Configure", comment: "[Menu] Configure system services")
static let menuTitleDocumentation = String(localized: "Documentation", comment: "[Menu] Open the wiki for system services")
static let menuTitleClipboard = String(localized: "Clipboard", comment: "[Menu] Clipboard options")
static let menuTitleSaveAsFile = String(localized: "Save as File", comment: "[Menu] Save the clipboard as file")
static let menuTitleClearContents = String(localized: "Clear Contents", comment: "[Menu] Clear the clipboard")
Expand Down
Loading

0 comments on commit 2f35421

Please sign in to comment.