Jump to content

[WIP, POC] Spotlight like rich preview pane for alfred workflows

Recommended Posts

Link to better quality videos

Download the code and play around: https://github.com/mr-pennyworth/alfred-extra-pane



Q: What is it?

A: An app that workflow creators can add to their script filters


Q: What does it do?

A: It renders html from quicklookurl of every item in the json.


Q: How does it do it?

A: By intercepting the json and by monitoring up-arrow and down-arrow keypresses.


Q: How to add it to a workflow?

A: By adding it to the script filter. Here's an example (from the workflow in the above GIF): notice how everything remains the same, just that at the very end, json needs to be piped through the helper app

# Before:
items=$(curl '' --data "{ \"q\": \"$query\" }" | jq '.hits')
echo "{ \"items\": $items }"

# After:
items=$(curl '' --data "{ \"q\": \"$query\" }" | jq '.hits')
echo "{ \"items\": $items }" | 'AlfredExtraPane.app/Contents/Resources/scripts/alfred-extra-pane'


Q: Sounds great! Now tell me everything that's not working!

A: This is more of a proof-of-concept and very rough around the edges.

  • Things that are easily doable, but haven't been done yet (contributions welcome! :) )
    • change appearance automatically based on alfred's themeauto-theme.png
    • make other things configurable like dimensions
  • Things that seem doable, but quite difficult with my knowledge of macOS GUI programming (which is about a week)
    • let alfred remain horizontally-centered when the pane is not present, and when the pane appears, make the "alfred+pane" combination horizontally-centered (by moving both the pane and alfred window to left)
  • Things that seem doable, but require guessing about alfred's inner workings:
    • as @deanishe points out, alfred builds "uid-based-knowledge". that means if the returned json has an uid field, alfed can use that later to re-order items while displaying based on whether of them were previously actioned on. the knowledge is an sqlite database, so that's the easy part. the not-trivial part is to figure out how alfred sorts the items.
      • Workaround: if you want to use this tool in your workflow, don't add UIDs to your json. One perfect use case for this is the dictionary workflow in the GIF. You looking up a word in the dictionary is a very weak signal that the word is important (many times, it is actually a signal that it is now less likely that the word will be looked up)
      •   This is a GUESS based on LIMITED observation.
          sorting is based on
          1) how many times an item has been actioned (freq)
          2) latest timestamp of action               (timestamp)
          primarily sorted based on freq, ties are broken by timestamp
          special case:
          if the script filter has executed without an argument,
          and one of the resultant items has an entry in the latching table,
          the item goes to the top, irrespective of the above sorting.

        The above algorithm has been implemented and seems to match alfred's sorting.

  • Things that seem impossible to me:
    • take into account mouse scroll interactions. right now, when selected row changes because of a mouse hover, the pane doesn't update, and will continue to show the old preview. As mouse hovers over various rows, the pane updates correctly, as long as Alfred's results have not been scrolled using mouse.



Edited by Mr Pennyworth
Link to comment
6 hours ago, Mr Pennyworth said:

Things that seem impossible to me

This is a really interesting idea, but I think there’s a fundamental flaw: Alfred doesn’t always show results in the order they are in the JSON file.


If items have UIDs, Alfred will apply its “knowledge” and sort the results based on previous user behaviour.


If I understand the way your app works correctly, that means your previews will often be out of sync with the results if UIDs are used.

Link to comment

@deanishe @Andrew I totally get the idea that an HTML preview-pane isn't widely useful, and probably doesn't even fit into Alfred's core design philosophy. However, I think some workflows' UX could benefit considerably if they had such an option available.


This POC could suddenly become so much more polished and robust if there was a Distributed Notification for "result selected/highlighted" that external processes could subscribe to. The data for that notification would contain entire json for the highlighted result. Then, instead of manually tracking keypresses and mouse-movements like the tool does now, it could simply subscribe to such a notification. It would also remove the need to crudely guess how alfred's "knowledge" works.


I know it's quite unlikely that such a thing will be implemented, as I can already see so many reasons not to. I just wanted to pick your brain about what your reasoning would be...

  1. Performance: posting distributed notifications slows stuff down.
  2. Feature/code bloat: not worth adding to Alfred's codebase something that'll only be used by some obscure tool.
  3. Non-trivial implementation: would need time/refactoring disproportionate to usefulness.
  4. Undesirable extension: don't want to expose weird APIs.
  5. Priority: proposal sounds good, but there are just way too many things with greater priority that it'll be a very long time to come to this.
  6. Undesirable functionality: want to discourage such "alfred companion tools".

If I were the developer, my reason to not implement would've been some combination of the above. How about you?

Edited by Mr Pennyworth
Link to comment
2 minutes ago, deanishe said:

I don't think the distributed notifications are likely, for the reasons you give.

Yep. I tried to make that list exhaustive.
What I'm curious to know is which among the above are @Andrew's reasons.
(must admit I'm clinging onto the teeeny tiny hope that he replies "no reason not to... watch out for it in some future release". but short of that, it's just a good thing to know the "why" :) )

Link to comment

Hey! I forgot to chime in when I saw this originally - very impressive! Richer content natively within Alfred is something which has always been on the plan for the future, but this really does provide a solid stepping stone.


It wouldn't be a huge amount of effort to post a distributed notification for the quicklookurl, but due to a whole host of reasons (of which you outline some above), I wouldn't make Alfred post this by default.


Having said that, for fun, I wouldn't be adverse to make it a defaults write on Alfred's prefs just to see this working better, and see where you take it :)




Link to comment
8 minutes ago, Andrew said:

Native richer content natively within Alfred is something which has always been on the plan for the future

Yayy! Awesome to hear that!!


9 minutes ago, Andrew said:

Having said that, for fun, I wouldn't be adverse to make it a defaults write on Alfred's prefs just to see this working better

🎉🥳 you're the best!
Keeping my fingers crossed! 🤞🏼

Link to comment
On 11/20/2020 at 2:05 AM, Andrew said:

Having said that, for fun, I wouldn't be adverse to make it a defaults write on Alfred's prefs just to see this working better, and see where you take it :)

Hi @Andrew, I forgot to ask this last time!
How do I find out when this lands in Alfred?
Right now, after each pre-release build update, I run the following two commands and see if there's anything of interest there:

  • defaults read com.runningwithcrayons.Alfred
  • defaults read com.runningwithcrayons.Alfred-Preferences

Is that the correct way of checking?

Edited by Mr Pennyworth
Link to comment

@Mr Pennyworth If you update to 4.3 b1199, you can now set the following pref:


defaults write com.runningwithcrayons.Alfred experimental.presssecretary -bool YES


You'll need to restart Alfred after setting this preference, then subscribe to the distributed notification:




You'll get relatively detailed output, for example:


    announcement = "selection.changed";
    selection =     {
        objectuid = "D51F7B33-AE36-4A45-91FF-E983659833EF";
        quicklookurl = "file:///Applications/Safari.app/";
        resultuid = "user.workflow.1B09DD22-0FDA-43B0-88DE-FBC92B83DA70./Applications/Safari.app";
        subtext = "/Applications/Safari.app";
        title = Safari;
        workflowuid = "user.workflow.1B09DD22-0FDA-43B0-88DE-FBC92B83DA70";
    view = default;
    windowframe = "NSRect: {{921, 884}, {718, 259}}";


Announcement types are as follows:


window.shown       - The Alfred window has been shown on the screen
window.hidden      - The Alfred window has been hidden
context.changed    - The view has changed from e.g. the default results to file system navigation
selection.changed  - The selected item in Alfred has changed


The different contexts are as follows:



(note: actions and music don't post out selection notifications)


Important note: This feature is experimental, and there is no guarantee that it will remain in Alfred in the future. Having said that, if it's removed, it would likely be replaced with a more robust solution.


Let me know how you get on :)




Link to comment

@Andrew I'm so sorry this is likely such a n00b question!


I'm only seeing window.hidden notifs.

I don't seem to be able to catch the others...

The extra-pane was the first time I'm writing swift, (or any mac-related programming for that matter)
So likely it is something really dumb that I'm doing...


  forName: NSNotification.Name(rawValue: "alfred.presssecretary"),
  object: nil,
  queue: nil,
  using: { notification in


I just wanted to confirm that the notifications are posted with DistributedNotificationCenter.Options.deliverImmediately


I'm not really sure whether it is the poster's responsibility or the observer's...

Assuming the observer can ensure immediate delivery irrespective of how the poster posted the notifications, I tried this code:

#import <Foundation/Foundation.h>

static void callback(
  CFNotificationCenterRef center,
  void *observer,
  CFStringRef name_cf,
  const void *object,
  CFDictionaryRef userInfo
) {
    NSLog(@"event: %@", (__bridge NSString*)name_cf);
    NSLog(@"user info: %@", userInfo);
    NSLog(@"object: %@", (__bridge id)object);

int main(int argc, const char * argv[])
   NSString* name = @"alfred.presssecretary";
     (CFStringRef) name,
   [[NSRunLoop currentRunLoop] run];
   return 0;


The results are still the same, I only get the "window.hidden" notifs and none of the other types...

Link to comment

Thanks @Alfred0! :)


My most favorite use-case is actually something that I hadn't originally thought about:
"Search Google as You Type"


Google has become more and more of a question-answering-machine.
So many times, I just want a quick answer and am not looking at reading articles/blogs etc.
For those cases, the mode of operation is "search google -> look at result -> move on"


The extra pane fits so well for them!
And ofc, pressing enter would open the same page in browser if I want to interact with that page further.


@Andrew The above involved writing this script filter:


I would agree that writing such a script filter is a minimal-effort, no-hassle thing.
It does feel like a teeny-tiny duplication-of-function though, given that Alfred already has awesome web-searches.
Is there a way to access the URL the web-searches are building?
Or, would it be possible to expose those URLs through press-secretary?
Or, is that not desirable due to privacy concerns?


Also, is there a programmatic way of accessing the web-searches? cc: @deanishe @vitor
(that way, someone could programmatically build a workflow mirroring the web-searches and their keywords if they want)
I scoured through various plists and alfdbs, but couldn't find where they are stored...
Are they stored in some proprietary format / right inside the binary?
Or did I miss something obvious?


Having said all this, I must agree I don't really foresee much value in doing this for all web-searches because with the exception of google and wolframalpha, I would assume most of the searches are such that they require further interactions with results (like clicking a link, copying some text etc).



Here's a different (vertical) configuration I've been toying with:



Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Create New...