Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,7 @@ PODS:
- Yoga
- Sentry/HybridSDK (8.56.1)
- SocketRocket (0.7.1)
- Sparkle (2.8.1)
- Sparkle (2.9.0)
- Yoga (0.0.0)

DEPENDENCIES:
Expand Down Expand Up @@ -2173,7 +2173,7 @@ SPEC CHECKSUMS:
RNSentry: eeaaa1a61ce59874fb46bba120042464618723e6
Sentry: b3ec44d01708fce73f99b544beb57e890eca4406
SocketRocket: 03f7111df1a343b162bf5b06ead333be808e1e0a
Sparkle: a346a4341537c625955751ed3ae4b340b68551fa
Sparkle: f4355f9ebbe9b7d932df4980d70f13922ac97b2a
Yoga: be08366e61155f388be00b3943f4b3febdef8d30

PODFILE CHECKSUM: 09d9f4d8690650f7bbc4a5e78de155d44c82904c
Expand Down
20 changes: 20 additions & 0 deletions macos/sol-macOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,26 @@ class AppDelegate: RCTAppDelegate {
return
}

// Check for image data on the pasteboard (supports all image types via NSImage)
let imageTypes: Set<String> = [
"public.tiff", "public.png", "public.jpeg",
"public.heic", "com.compuserve.gif", "com.microsoft.bmp"
]
let hasExplicitImageType = $0.types?.contains(where: { imageTypes.contains($0.rawValue) }) ?? false
if hasExplicitImageType,
let image = NSImage(pasteboard: $0),
let pngRepData = image.PNGRepresentation {
let filename = "clipboard_\(Int(Date().timeIntervalSince1970 * 1000)).png"
let tempFile = NSTemporaryDirectory() + filename
do {
try pngRepData.write(to: URL(fileURLWithPath: tempFile), options: .atomic)
SolEmitter.sharedInstance.fileCopied(filename, tempFile, bundle)
} catch {
print("Could not save clipboard image: \(error.localizedDescription)")
}
return
}

// Try to parse first as string
let txt = $0.string(forType: .string)
if txt != nil {
Expand Down
22 changes: 20 additions & 2 deletions macos/sol-macOS/lib/ClipboardHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ class ClipboardHelper {
self, selector: #selector(frontmostAppChanged(sender:)),
name: NSWorkspace.didActivateApplicationNotification, object: nil)

// TODO find if there is any better way to observe for changes other than continously check if the amount of items has changed
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
if pasteboard.changeCount != changeCount {
callback(pasteboard, self.frontmostApp)
changeCount = pasteboard.changeCount
Expand Down Expand Up @@ -76,4 +75,23 @@ class ClipboardHelper {
event2?.post(tap: CGEventTapLocation.cghidEventTap)
}
}

static func pasteFileToFrontmostApp(_ filePath: String) {
DispatchQueue.main.async {
PanelManager.shared.hideWindow()

let pasteboard = NSPasteboard.general
pasteboard.clearContents()

let url = URL(fileURLWithPath: filePath)
pasteboard.writeObjects([url as NSURL])

let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: true) // cmd-v down
event1?.flags = CGEventFlags.maskCommand
event1?.post(tap: CGEventTapLocation.cghidEventTap)

let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: false) // cmd-v up
event2?.post(tap: CGEventTapLocation.cghidEventTap)
}
}
}
5 changes: 4 additions & 1 deletion macos/sol-macOS/lib/FileSearch.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
#include <vector>
#include <string>

static const int MAX_SEARCH_DEPTH = 5;
static const size_t MAX_SEARCH_RESULTS = 200;

struct File {
std::string path;
bool is_folder;
std::string name;
};

std::vector<File> search_files(NSString *basePath, NSString *query);
std::vector<File> search_files(NSString *basePath, NSString *query, int depth = 0, size_t *result_count = nullptr);

#endif /* FileSearch_h */
61 changes: 50 additions & 11 deletions macos/sol-macOS/lib/FileSearch.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,79 @@
#include "FileSearch.h"
#include "NSString+Score.h"

std::vector<File> search_files(NSString *basePath, NSString *query) {
static NSSet *getSkippedDirectories() {
static NSSet *skippedDirectories = [NSSet setWithObjects:
@"node_modules", @".git", @"Library", @".cache",
@".Trash", @"__pycache__", @".npm", @".yarn",
@"Pods", @"build", @"DerivedData", nil];
return skippedDirectories;
}

static bool shouldSkipDirectory(NSString *name) {
if ([name hasPrefix:@"."]) return true;
if ([name hasSuffix:@".app"]) return true;
if ([name hasSuffix:@".framework"]) return true;
if ([getSkippedDirectories() containsObject:name]) return true;
return false;
}

std::vector<File> search_files(NSString *basePath, NSString *query, int depth, size_t *result_count) {
std::vector<File> files;

size_t localCount = 0;
if (!result_count) {
result_count = &localCount;
}

if (depth >= MAX_SEARCH_DEPTH || *result_count >= MAX_SEARCH_RESULTS) {
return files;
}

NSFileManager *defFM = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *dirPath = [defFM contentsOfDirectoryAtPath:basePath error:&error];
if (!dirPath) {
return files;
}

for(NSString *path in dirPath) {
if (*result_count >= MAX_SEARCH_RESULTS) {
break;
}

BOOL is_dir;
NSString *full_path = [basePath stringByAppendingPathComponent:path];
std::string cpp_full_path = [full_path UTF8String];
float distance = [path scoreAgainst:query];

if([defFM fileExistsAtPath:full_path isDirectory:&is_dir] && is_dir){

if([defFM fileExistsAtPath:full_path isDirectory:&is_dir] && is_dir) {
if (shouldSkipDirectory(path)) {
continue;
}

if (distance > 0.5) {
files.push_back({
.path = cpp_full_path,
.is_folder = true,
.name = [path UTF8String]
});
(*result_count)++;
}

std::vector<File> sub_files = search_files(full_path, query);
files.insert(files.end(), sub_files.begin(), sub_files.end());
} else {

if (distance > 0.5) {
std::vector<File> sub_files = search_files(full_path, query, depth + 1, result_count);
files.insert(files.end(), sub_files.begin(), sub_files.end());
} else {
if (distance > 0.5) {
files.push_back({
.path = cpp_full_path,
.is_folder = false,
.name = [path UTF8String]
});
(*result_count)++;
}
}
}
}
return files;

return files;
}

7 changes: 6 additions & 1 deletion macos/sol-macOS/lib/JSIBindings.mm
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,16 @@ void install(jsi::Runtime &rt,
auto paths = arguments[0].asObject(rt).asArray(rt);
auto query = arguments[1].asString(rt).utf8(rt);
std::vector<File> res;
size_t result_count = 0;
for (size_t i = 0; i < paths.size(rt); i++) {
if (result_count >= MAX_SEARCH_RESULTS) {
break;
}
auto path = paths.getValueAtIndex(rt, i).asString(rt).utf8(rt);
std::vector<File> path_results =
search_files([NSString stringWithUTF8String:path.c_str()],
[NSString stringWithUTF8String:query.c_str()]);
[NSString stringWithUTF8String:query.c_str()],
0, &result_count);
res.insert(res.end(), path_results.begin(), path_results.end());
}

Expand Down
3 changes: 2 additions & 1 deletion macos/sol-macOS/lib/SolNative.mm
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ - (void)loadBundle {
RCT_EXTERN_METHOD(moveFrontmostToNextSpace)
RCT_EXTERN_METHOD(insertToFrontmostApp : (NSString)content)
RCT_EXTERN_METHOD(pasteToFrontmostApp : (NSString)content)
RCT_EXTERN_METHOD(pasteFileToFrontmostApp : (NSString)filePath)
RCT_EXTERN_METHOD(turnOnHorizontalArrowsListeners)
RCT_EXTERN_METHOD(turnOffHorizontalArrowsListeners)
RCT_EXTERN_METHOD(turnOffEnterListener)
Expand Down Expand Up @@ -107,7 +108,7 @@ - (void)loadBundle {
RCT_EXTERN_METHOD(showWifiQR : (NSString)SSID password : (NSString)password)
RCT_EXTERN_METHOD(openFilePicker : (RCTPromiseResolveBlock)
resolve reject : (RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(updateHotkeys : (NSDictionary)hotkeys)
RCT_EXTERN_METHOD(updateHotkeys : (NSDictionary)hotkeys urlMap : (NSDictionary)urlMap)
RCT_EXTERN_METHOD(setUpcomingEventEnabled : (BOOL)enabled)
RCT_EXTERN_METHOD(setHyperKeyEnabled : (BOOL)v)
@end
9 changes: 7 additions & 2 deletions macos/sol-macOS/lib/SolNative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ class SolNative: RCTEventEmitter {
ClipboardHelper.insertToFrontmostApp(content)
}

@objc func pasteFileToFrontmostApp(_ filePath: String) {
ClipboardHelper.pasteFileToFrontmostApp(filePath)
}

@objc func turnOnHorizontalArrowsListeners() {
HotKeyManager.shared.catchHorizontalArrowsPress = true
}
Expand Down Expand Up @@ -437,9 +441,10 @@ class SolNative: RCTEventEmitter {
}
}

@objc func updateHotkeys(_ hotkeys: NSDictionary) {
@objc func updateHotkeys(_ hotkeys: NSDictionary, urlMap: NSDictionary?) {
guard let hotkeys = hotkeys as? [String: String] else { return }
HotKeyManager.shared.updateHotkeys(hotkeyMap: hotkeys)
let urls = urlMap as? [String: String] ?? [:]
HotKeyManager.shared.updateHotkeys(hotkeyMap: hotkeys, urlMap: urls)
}

@objc func setUpcomingEventEnabled(_ enabled: Bool) {
Expand Down
13 changes: 10 additions & 3 deletions macos/sol-macOS/managers/HotKeyManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ final class HotKeyManager {
}
}

func updateHotkeys(hotkeyMap: [String: String]) {
func updateHotkeys(hotkeyMap: [String: String], urlMap: [String: String] = [:]) {
hotkeys.removeAll()

for (key, value) in hotkeyMap {
Expand Down Expand Up @@ -205,8 +205,15 @@ final class HotKeyManager {
guard let finalKey = keyValue else { continue }
let hotKey = HotKey(key: finalKey, modifiers: modifiers)

hotKey.keyUpHandler = {
SolEmitter.sharedInstance.onHotkey(id: key)
// If the hotkey has a URL, open it directly in native (skip JS round-trip)
if let url = urlMap[key] {
hotKey.keyUpHandler = {
NSWorkspace.shared.openFile(url)
}
} else {
hotKey.keyUpHandler = {
SolEmitter.sharedInstance.onHotkey(id: key)
}
}
hotkeys.append(hotKey)
}
Expand Down
5 changes: 5 additions & 0 deletions macos/sol-macOS/views/FileImageView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <React/RCTViewManager.h>

@interface RCT_EXTERN_MODULE (FileImageViewManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(url, NSString)
@end
42 changes: 42 additions & 0 deletions macos/sol-macOS/views/FileImageView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Cocoa

class FileImageView: NSView {
let imageView = NSImageView()

@objc var url: NSString = "" {
didSet {
self.setupView()
}
}

override init(frame: CGRect) {
super.init(frame: frame)
imageView.imageScaling = .scaleProportionallyUpOrDown
imageView.autoresizingMask = [.height, .width]
self.addSubview(imageView)
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

private func setupView() {
let path = (self.url as String).replacingOccurrences(
of: "file://", with: "")
guard !path.isEmpty else { return }
let image = NSImage(contentsOfFile: path)
self.imageView.image = image
}
}

@objc(FileImageViewManager)
class FileImageViewManager: RCTViewManager {

override static func requiresMainQueueSetup() -> Bool {
return true
}

override func view() -> NSView! {
return FileImageView()
}
}
Loading