Skip to content

Conversation

@DanNoby
Copy link

@DanNoby DanNoby commented Jul 27, 2025

This feature aims to allows users to extract text from images in their clipboard history using Apple’s Vision framework.

When previewing an image in the clipboard history, an “Extract Text” button appears.
Clicking the button runs OCR on the image and copies the extracted text directly to the clipboard.
The feature is only visible for image items.
No popups or alerts are shown; feedback is via clipboard update.

ScreenRecording2025-07-27at11 55 15PM-ezgif com-video-to-gif-converter

Technical notes:
Uses Apple’s Vision framework for OCR (macOS 10.15+).
UI/UX is minimal and consistent with the Maccy's design.
Tested on:
macOS Sequoia 15.5, Xcode 16.43

@p0deje
Copy link
Owner

p0deje commented Jul 28, 2025

This was requested part of #1063 and I think we should implement it as "Paste without formatting" shortcut. There is already logic to extract text from image and store it inside "title" field (see 2255498). With this in-place, we only need to add a special handling of paste without formatting to copy the title of HistoryItem instead of its contents.

@DanNoby
Copy link
Author

DanNoby commented Jul 28, 2025

Got it, thanks...

Just to confirm — shall I go ahead and work on the “Paste without formatting” shortcut and update this PR to follow the approach you described (i.e., copying from the title field of the HistoryItem)?

Happy to modify this PR accordingly if that works for you.

@p0deje
Copy link
Owner

p0deje commented Jul 28, 2025

@DanNoby It would be great. This should be as simple as probably adding a condition to check for image + removeFormatting somewhere in

@MainActor
func copy(_ item: HistoryItem?, removeFormatting: Bool = false) {
guard let item else { return }
pasteboard.clearContents()
var contents = item.contents
if removeFormatting {
contents = clearFormatting(contents)
}
for content in contents {
guard content.type != NSPasteboard.PasteboardType.fileURL.rawValue else { continue }
pasteboard.setData(content.value, forType: NSPasteboard.PasteboardType(content.type))
}
// Use writeObjects for file URLs so that multiple files that are copied actually work.
// Only do this for file URLs because it causes an issue with some other data types (like formatted text)
// where the item is pasted more than once.
let fileURLItems: [NSPasteboardItem] = contents.compactMap { item in
guard item.type == NSPasteboard.PasteboardType.fileURL.rawValue else { return nil }
guard let value = item.value else { return nil }
let pasteItem = NSPasteboardItem()
pasteItem.setData(value, forType: NSPasteboard.PasteboardType(item.type))
return pasteItem
}
pasteboard.writeObjects(fileURLItems)
pasteboard.setString("", forType: .fromMaccy)
pasteboard.setString(item.application ?? "", forType: .source)
sync()
Task {
Notifier.notify(body: item.title, sound: .knock)
checkForChangesInPasteboard()
}
}

@DanNoby
Copy link
Author

DanNoby commented Jul 28, 2025

I’ve updated the PR to implement the “Paste without formatting” feature as discussed.
When user presses OPTION + SHIFT + ENTER on a picture they've taken, it pastes the text now.

I ran into a few SwiftLint and build issues along the way — sorry for the extra commits while I worked through them 😅

@avichou
Copy link

avichou commented Aug 3, 2025

I wouldn't be against having both :

a shortcut (what @p0deje proposes) , as well as an option in the form of apple OCR in the bottom right after performing an image preview . generally, good software have both, as visual indicator helps paliate a potential memory issue

@avichou
Copy link

avichou commented Aug 30, 2025

@DanNoby It would be great. This should be as simple as probably adding a condition to check for image + removeFormatting somewhere in

@MainActor
func copy(_ item: HistoryItem?, removeFormatting: Bool = false) {
guard let item else { return }
pasteboard.clearContents()
var contents = item.contents
if removeFormatting {
contents = clearFormatting(contents)
}
for content in contents {
guard content.type != NSPasteboard.PasteboardType.fileURL.rawValue else { continue }
pasteboard.setData(content.value, forType: NSPasteboard.PasteboardType(content.type))
}
// Use writeObjects for file URLs so that multiple files that are copied actually work.
// Only do this for file URLs because it causes an issue with some other data types (like formatted text)
// where the item is pasted more than once.
let fileURLItems: [NSPasteboardItem] = contents.compactMap { item in
guard item.type == NSPasteboard.PasteboardType.fileURL.rawValue else { return nil }
guard let value = item.value else { return nil }
let pasteItem = NSPasteboardItem()
pasteItem.setData(value, forType: NSPasteboard.PasteboardType(item.type))
return pasteItem
}
pasteboard.writeObjects(fileURLItems)
pasteboard.setString("", forType: .fromMaccy)
pasteboard.setString(item.application ?? "", forType: .source)
sync()
Task {
Notifier.notify(body: item.title, sound: .knock)
checkForChangesInPasteboard()
}
}

hi, has this been implemented yet ? i can't really tell because i have issues with paste without formatting anyway

Comment on lines +89 to +101
for content in contents {
guard content.type != NSPasteboard.PasteboardType.fileURL.rawValue else { continue }
pasteboard.setData(content.value, forType: NSPasteboard.PasteboardType(content.type))
}

// Use writeObjects for file URLs so that multiple files that are copied actually work.
// Only do this for file URLs because it causes an issue with some other data types (like formatted text)
// where the item is pasted more than once.
let fileURLItems: [NSPasteboardItem] = contents.compactMap { item in
guard item.type == NSPasteboard.PasteboardType.fileURL.rawValue else { return nil }
guard let value = item.value else { return nil }
let pasteItem = NSPasteboardItem()
pasteItem.setData(value, forType: NSPasteboard.PasteboardType(item.type))
return pasteItem
}
pasteboard.writeObjects(fileURLItems)
// Use writeObjects for file URLs so that multiple files that are copied actually work.
// Only do this for file URLs because it causes an issue with some other data types (like formatted text)
// where the item is pasted more than once.
let fileURLItems: [NSPasteboardItem] = contents.compactMap { item in
guard item.type == NSPasteboard.PasteboardType.fileURL.rawValue else { return nil }
guard let value = item.value else { return nil }
let pasteItem = NSPasteboardItem()
pasteItem.setData(value, forType: NSPasteboard.PasteboardType(item.type))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but it looks like there's a lot of non-meaningful whitespace changes here muddying up the pull request diff. Can those be cleaned up so reviewers can zero in on functional changes?

Please and thank you.


struct PreviewItemView: View {
var item: HistoryItemDecorator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please undo this whitespace change, or move it to a dinstinct formatting-focused PR

return .handled
case .selectCurrentItem:
appState.select()
if let event = NSApp.currentEvent,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need that, it should be already handled in History.select().

}
}

// Based on https://github.com/Clipy/Clipy/blob/develop/Clipy/Sources/Services/PasteService.swift.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you revert?

contents = clearFormatting(contents)
}
@MainActor
func copy(_ item: HistoryItem?, removeFormatting: Bool = false) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you needed to increase indentation, can you revert?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants