Skip to content
This repository has been archived by the owner on Jun 18, 2024. It is now read-only.

Commit

Permalink
Merge branch 'no-swindler' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-pennyworth committed May 9, 2021
2 parents e36cc19 + bf26222 commit b98908f
Show file tree
Hide file tree
Showing 250 changed files with 682 additions and 5,272 deletions.
Binary file added B9741F37-05A5-4DB4-87C0-2848655113DF.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Alfred GIF Search

Search GIFs on [Tenor](https://tenor.com) from [Alfred](https://alfredapp.com).
Search for GIFs and animated stickers on [Tenor](https://tenor.com)
from [Alfred](https://alfredapp.com).

Here's an example of searching and inserting a GIF in a google doc:
![](demo-media/alfred-gif-search-with-drag-thumbnail.gif)

Animated stickers are also GIFs, but they typically tend to have
a transparent background. Here's an example of how animated stickers look like:
![](demo-media/alfred-gif-animated-stickers.gif)

### Installation
1. Download the [latest release](https://github.com/mr-pennyworth/alfred-gif/releases/latest/download/GIF.Search.alfredworkflow).
2. In Alfred, run `.setup-gif-search`.

### Usage
- In Alfred, enter `gif` keyword followed by search query.
- In Alfred, enter `gif` or `sticker` keyword followed by search query.
- Press ``.
- Use arrow keys or mouse to browse the GIFs.
- To copy the selected GIF to clipboard:
Expand All @@ -30,4 +35,4 @@ in this workflow, but rather just the way these browsers have
decided to handle GIFs.

**Both Chrome and Firefox support drag-n-drop**. If you use either
of these browsers, sorry, you gotta use the mouse!
of these browsers, sorry, you gotta use the mouse!
22 changes: 2 additions & 20 deletions alfred-gif-browser/AlfredGifBrowser.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@
D03EA8D325551E0400D3656E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03EA8D225551E0400D3656E /* main.swift */; };
D03EA8D725551E0700D3656E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D03EA8D625551E0700D3656E /* Assets.xcassets */; };
D03EA8FA255567A300D3656E /* AlfredWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03EA8F9255567A300D3656E /* AlfredWatcher.swift */; };
D03EA8FE255582BC00D3656E /* Swindler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03EA8FB255582BC00D3656E /* Swindler.framework */; };
D03EA8FF255582BC00D3656E /* Swindler.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D03EA8FB255582BC00D3656E /* Swindler.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D03EA900255582BC00D3656E /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03EA8FC255582BC00D3656E /* PromiseKit.framework */; };
D03EA901255582BC00D3656E /* PromiseKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D03EA8FC255582BC00D3656E /* PromiseKit.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D03EA902255582BC00D3656E /* AXSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03EA8FD255582BC00D3656E /* AXSwift.framework */; };
D03EA903255582BC00D3656E /* AXSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D03EA8FD255582BC00D3656E /* AXSwift.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D04F70D325586069008E17A4 /* scripts in Resources */ = {isa = PBXBuildFile; fileRef = D04F70D225586069008E17A4 /* scripts */; };
D04F70D5255893B3008E17A4 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04F70D4255893B3008E17A4 /* NSColorExtension.swift */; };
/* End PBXBuildFile section */
Expand All @@ -28,9 +22,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
D03EA8FF255582BC00D3656E /* Swindler.framework in Embed Frameworks */,
D03EA903255582BC00D3656E /* AXSwift.framework in Embed Frameworks */,
D03EA901255582BC00D3656E /* PromiseKit.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -44,9 +35,6 @@
D03EA8D625551E0700D3656E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D03EA8DB25551E0700D3656E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D03EA8F9255567A300D3656E /* AlfredWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlfredWatcher.swift; sourceTree = "<group>"; };
D03EA8FB255582BC00D3656E /* Swindler.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Swindler.framework; path = frameworks/Swindler.framework; sourceTree = "<group>"; };
D03EA8FC255582BC00D3656E /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = frameworks/PromiseKit.framework; sourceTree = "<group>"; };
D03EA8FD255582BC00D3656E /* AXSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AXSwift.framework; path = frameworks/AXSwift.framework; sourceTree = "<group>"; };
D04F70D225586069008E17A4 /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scripts; sourceTree = "<group>"; };
D04F70D4255893B3008E17A4 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand All @@ -56,9 +44,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D03EA8FE255582BC00D3656E /* Swindler.framework in Frameworks */,
D03EA902255582BC00D3656E /* AXSwift.framework in Frameworks */,
D03EA900255582BC00D3656E /* PromiseKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -99,9 +84,6 @@
D03EA8EE2555664C00D3656E /* Frameworks */ = {
isa = PBXGroup;
children = (
D03EA8FD255582BC00D3656E /* AXSwift.framework */,
D03EA8FC255582BC00D3656E /* PromiseKit.framework */,
D03EA8FB255582BC00D3656E /* Swindler.framework */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -317,7 +299,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.1.0;
OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = mr.pennyworth.AlfredGifBrowser;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -338,7 +320,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.1.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = mr.pennyworth.AlfredGifBrowser;
Expand Down
88 changes: 21 additions & 67 deletions alfred-gif-browser/AlfredGifBrowser/AlfredWatcher.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import AXSwift
import Cocoa
import Swindler
import PromiseKit
import CoreFoundation

typealias Dict = [String: Any]

class AlfredWatcher {
var swindler: Swindler.State!
var onDestroy: (() -> Void)!
var onDownArrow: (() -> Void)!
var onUpArrow: (() -> Void)!
var onRightArrow: (() -> Void)!
var onLeftArrow: (() -> Void)!
var setAlfredFrame: ((NSRect)-> Void)!

var mods: NSEvent.ModifierFlags = NSEvent.ModifierFlags()

Expand All @@ -25,27 +23,15 @@ class AlfredWatcher {
onDownArrowPressed: @escaping () -> Void,
onUpArrowPressed: @escaping () -> Void,
onRightArrowPressed: @escaping () -> Void,
onLeftArrowPressed: @escaping () -> Void
onLeftArrowPressed: @escaping () -> Void,
setAlfredFrame: @escaping (NSRect) -> Void
) {
self.onDestroy = onAlfredWindowDestroy
self.onDownArrow = onDownArrowPressed
self.onUpArrow = onUpArrowPressed
self.onRightArrow = onRightArrowPressed
self.onLeftArrow = onLeftArrowPressed

guard AXSwift.checkIsProcessTrusted(prompt: true) else {
NSLog("Not trusted as an AX process; please authorize and re-launch")
NSApp.terminate(self)
return
}

Swindler.initialize().done { state in
self.swindler = state
self.setupEventHandlers()
}.catch { error in
NSLog("Fatal error: failed to initialize Swindler: \(error)")
NSApp.terminate(self)
}
self.setAlfredFrame = setAlfredFrame

NSEvent.addGlobalMonitorForEvents(
matching: [NSEvent.EventTypeMask.keyDown],
Expand All @@ -72,57 +58,25 @@ class AlfredWatcher {
self.mods = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
}
)
}

private func isAlfredWindow(window: Window) -> Bool {
let bundle = window.application.bundleIdentifier ?? ""
let title = window.title.value
return (bundle == "com.runningwithcrayons.Alfred" && title == "Alfred")
}

private func setupEventHandlers() {
swindler.on { (event: WindowDestroyedEvent) in
if (self.isAlfredWindow(window: event.window)) {
NSLog("Alfred window destroyed")
self.onDestroy()
}
}
DistributedNotificationCenter.default().addObserver(
self,
selector: #selector(handleAlfredNotification),
name: NSNotification.Name(rawValue: "alfred.presssecretary"),
object: nil,
suspensionBehavior: .deliverImmediately
)
}

func alfredFrame() -> CGRect? {
if (swindler == nil) {
// when the application isn't already running, and the first call is
// by invoking the app specific url, swindler might not have been
// initialized by then. for that special case, we explicitly get
// alfred frame without relying on swindler.
let options = CGWindowListOption([.excludeDesktopElements, .optionOnScreenOnly])
let windowListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
let wli = windowListInfo as NSArray? as? [[String: AnyObject]]
if let alfredWindow = wli?.first(where: { windowInfo in
if let name = windowInfo["kCGWindowOwnerName"] as? String {
if (name == "Alfred") {
return true
} else {
return false
}
} else {
return false
}
}) {
if let bounds = alfredWindow["kCGWindowBounds"] {
let frame = CGRect.init(
dictionaryRepresentation: bounds as! CFDictionary
)
log("Non-Swindler frame: \(String(describing: frame))")
return frame
}
}

return nil
} else {
let alfredWindow = swindler.knownWindows.first(where: self.isAlfredWindow)!
let alfred = alfredWindow.frame
return alfred.value
@objc func handleAlfredNotification(notification: NSNotification) {
// log("\(notification)")
let notif = notification.userInfo! as! Dict
let notifType = notif["announcement"] as! String
if (notifType == "window.hidden") {
self.onDestroy()
} else if (notifType == "selection.changed") {
let frame = NSRectFromString(notif["windowframe"] as! String)
self.setAlfredFrame(frame)
}
}
}
2 changes: 1 addition & 1 deletion alfred-gif-browser/AlfredGifBrowser/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Mr. Pennyworth. All rights reserved.</string>
<string>Copyright © 2021 Mr. Pennyworth. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
Expand Down
69 changes: 19 additions & 50 deletions alfred-gif-browser/AlfredGifBrowser/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class GifDraggerWebView: WKWebView, NSDraggingSource {
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(
self.bounds,
contents: NSImage.init(contentsOf: selectedGif))
contents: NSImage.init(contentsOf: selectedGif)
)

self.beginDraggingSession(
with: [draggingItem],
Expand All @@ -41,6 +42,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
lazy var screenWidth: CGFloat = screen.frame.width
lazy var screenHeight: CGFloat = screen.frame.height

var alfredFrame: NSRect = NSRect()
let gifCacheDir: URL =
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library")
Expand All @@ -52,8 +54,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
lazy var selectedGif: URL = gifCacheDir.appendingPathComponent("selected.gif")
var selectedGifWebUrl: String = ""

var urls: [URL?] = []
var urlIdx = 0
var url: URL? = nil
var css = ""

let alfredWatcher: AlfredWatcher = AlfredWatcher()
Expand Down Expand Up @@ -91,28 +92,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return webview
}()

func setUrls(_ urlListJsonString: String) {
self.urlIdx = 0
let data = Data(urlListJsonString.utf8)
do {
let array = try JSONSerialization.jsonObject(with: data) as! [String]

// empty strings map to file URL equivalent to "./",
// which we later on decide to not render in render()
self.urls = array.map({ path in
if (path.starts(with: "/")) {
return URL(fileURLWithPath: path)
} else {
return URL(string: path)
}
})
render()
// puzzler: why would the following cause a SEGFAULT?
// that too never while running in xcode
// log("urls: \(self.urls)")
} catch {
log("Error: \(error)")
}
func setUrl(_ path: String) {
self.url = URL(fileURLWithPath: path)
render()
}

func mouseAtInWebviewViewport(x: CGFloat, y: CGFloat) {
Expand Down Expand Up @@ -156,7 +138,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
alfredWatcher.start(
onAlfredWindowDestroy: {
if (self.window.isVisible) {
self.urls = [nil]
self.url = nil
let modifiers = self.alfredWatcher.mods
if (modifiers.contains(.command)) {
let pb = NSPasteboard.general
Expand All @@ -175,7 +157,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
onDownArrowPressed: self.makeBrowseFunction("down"),
onUpArrowPressed: self.makeBrowseFunction("up"),
onRightArrowPressed: self.makeBrowseFunction("right"),
onLeftArrowPressed: self.makeBrowseFunction("left")
onLeftArrowPressed: self.makeBrowseFunction("left"),
setAlfredFrame: { self.alfredFrame = $0 }
)
}

Expand Down Expand Up @@ -248,28 +231,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

func render() {
if (self.urls.count == 0 || self.urls == [nil]) {
return
}

if let alfredFrame = self.alfredWatcher.alfredFrame() {
self.urlIdx = (self.urlIdx + self.urls.count) % self.urls.count
if let url = self.urls[self.urlIdx] {
log("Rendering URL at index: \(self.urlIdx): \(url)")
if (url.isFileURL) {
webview.loadFileURL(
injectCSS(fileUrl: url),
allowingReadAccessTo: url.deletingLastPathComponent()
)
} else {
webview.load(URLRequest(url: url))
}
webview.isHidden = false
showWindow(alfred: alfredFrame)
} else {
log("Hiding as no URL was provided at index: \(self.urlIdx)")
webview.isHidden = true
}
if let url = self.url {
webview.loadFileURL(
injectCSS(fileUrl: url),
allowingReadAccessTo: url.deletingLastPathComponent()
)
showWindow(alfred: alfredFrame)
} else {
self.window.orderOut(self)
}
}

Expand All @@ -284,7 +253,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
alpha: 1
)
readFile(named: param["cssFile"]!, then: { css in self.css = css })
readFile(named: param["specFile"]!, then: setUrls)
setUrl(param["gifHtml"]!)
default:
break
}
Expand Down
Loading

0 comments on commit b98908f

Please sign in to comment.