├── .gitignore
├── Org Protocol Handler.app
└── Contents
│ ├── Info.plist
│ ├── MacOS
│ └── applet
│ ├── PkgInfo
│ └── Resources
│ ├── Scripts
│ └── main.scpt
│ ├── applet.icns
│ ├── applet.rsrc
│ ├── description.rtfd
│ └── TXT.rtf
│ └── parse.py
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleAllowMixedLocalizations
6 |
7 | CFBundleDevelopmentRegion
8 | English
9 | CFBundleExecutable
10 | applet
11 | CFBundleIconFile
12 | applet
13 | CFBundleIdentifier
14 | com.apple.ScriptEditor.id.Org-Protocol-Handler
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | Org Protocol Handler
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleSignature
24 | aplt
25 | CFBundleURLTypes
26 |
27 |
28 | CFBundleURLName
29 | EmacsClientCapture
30 | CFBundleURLSchemes
31 |
32 | org-protocol
33 |
34 |
35 |
36 | LSMinimumSystemVersionByArchitecture
37 |
38 | x86_64
39 | 10.6
40 |
41 | LSRequiresCarbon
42 |
43 | WindowState
44 |
45 | bundleDividerCollapsed
46 |
47 | bundlePositionOfDivider
48 | 0.0
49 | dividerCollapsed
50 |
51 | eventLogLevel
52 | 1
53 | name
54 | ScriptWindowState
55 | positionOfDivider
56 | 686
57 | savedFrame
58 | 717 95 1046 1007 0 0 2560 1417
59 | selectedTab
60 | log
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/MacOS/applet:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronbieber/org-protocol-handler/5f4927b7c6b07d553b4b792e0f0b54985d63bf8b/Org Protocol Handler.app/Contents/MacOS/applet
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/PkgInfo:
--------------------------------------------------------------------------------
1 | APPLaplt
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronbieber/org-protocol-handler/5f4927b7c6b07d553b4b792e0f0b54985d63bf8b/Org Protocol Handler.app/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/Resources/applet.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronbieber/org-protocol-handler/5f4927b7c6b07d553b4b792e0f0b54985d63bf8b/Org Protocol Handler.app/Contents/Resources/applet.icns
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/Resources/applet.rsrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronbieber/org-protocol-handler/5f4927b7c6b07d553b4b792e0f0b54985d63bf8b/Org Protocol Handler.app/Contents/Resources/applet.rsrc
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf600
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | {\*\expandedcolortbl;;}
5 | }
--------------------------------------------------------------------------------
/Org Protocol Handler.app/Contents/Resources/parse.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import absolute_import, print_function
4 |
5 | import os
6 | import subprocess
7 | import sys
8 |
9 | import six.moves
10 |
11 |
12 | def read_config():
13 | """Read and parse ~/.orgprotocol.ini if it exists."""
14 | ini_path = os.path.expanduser("~/.orgprotocol.ini")
15 | config = six.moves.configparser.ConfigParser()
16 | try:
17 | config.read([ini_path])
18 | except Exception:
19 | print("Error reading %s" % ini_path)
20 | return config
21 |
22 |
23 | def emacs_client_command(config):
24 | """Construct a list, each member of which is a part of the
25 | `emacsclient` command to be run by `subprocess` and used in
26 | main.scpt. Provides the default `emacsclient` executable if
27 | ~/.orgprotocol.ini doesn't exist."""
28 | path = emacsclient_path(config)
29 | options = emacsclient_options(config)
30 | cmd = path + options
31 | return cmd
32 |
33 |
34 | def emacsclient_path(config):
35 | """Get the configured path to `emacsclient`, or the default."""
36 | try:
37 | path = config.get("emacsclient", "path")
38 | except Exception:
39 | path = "/usr/local/bin/emacsclient"
40 | return [path]
41 |
42 |
43 | def emacsclient_options(config):
44 | """Unpack options from config file. Options are appeneded to the final
45 | `emacsclient` command. Returns an empty list if no options are specified.
46 | """
47 | try:
48 | return list(dict(config.items("options")).values())
49 | except Exception:
50 | return []
51 |
52 |
53 | def is_old_style_link(url):
54 | """Determine which version of org-protocol link this URL is.
55 |
56 | The 'old style' link looks like:
57 | org-protocol://capture://///
58 |
59 | While 'new style' links look like:
60 | org-protocol://capture?template=&url=&title=&body=
61 | """
62 | return url.count("://") == 2
63 |
64 |
65 | def get_new_style_title(url):
66 | """Get the title from a new style URL."""
67 | url_fragments = six.moves.urllib.parse.urlparse(url)
68 |
69 | # url_fragments is a 6 tuple; index 4 is the querystring
70 | if not len(url_fragments[4]):
71 | return ""
72 |
73 | qs_parts = six.moves.urllib.parse.parse_qs(url_fragments[4])
74 |
75 | if "title" in list(qs_parts.keys()) and len(qs_parts["title"]):
76 | return qs_parts["title"][0]
77 |
78 | return ""
79 |
80 |
81 | def get_old_style_title(url):
82 | """Get the title from an old style URL."""
83 | url_fragments = six.moves.urllib.parse.urlparse(url)
84 |
85 | path_parts = url_fragments[2].split("/")
86 | return six.moves.urllib.parse.unquote(path_parts[4])
87 |
88 |
89 | def get_title(url, is_old_style=True):
90 | """Get the title from an org-protocol URL."""
91 |
92 | if is_old_style:
93 | return get_old_style_title(url)
94 |
95 | return get_new_style_title(url)
96 |
97 |
98 | def main():
99 | if len(sys.argv) < 2:
100 | sys.exit(1)
101 |
102 | url = sys.argv[1]
103 | raw_url = six.moves.urllib.parse.unquote(url)
104 | config = read_config()
105 | cmd = emacs_client_command(config)
106 | cmd.append(raw_url)
107 | subprocess.check_output(cmd)
108 | print(get_title(url, is_old_style_link(url)))
109 |
110 |
111 | if __name__ == '__main__':
112 | main()
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Org Protocol Handler #
2 |
3 | The Org Protocol Handler is a small AppleScript/Python application that
4 | registers itself as the handler for all `org-protocol://` links.
5 |
6 | If your system wants to visit an `org-protocol://` link, it will pass the value
7 | to this application, which will urldecode it and pass it to `emacsclient`. The
8 | net effect is that you can use a bookmarklet to build an `org-protocol://` link
9 | in your browser for things like saving the current page as a link in Org and
10 | that bookmarket will launch and focus Emacs.
11 |
12 | ## Configuration ##
13 |
14 | In order for this application to work for you, you will need to be sure that it
15 | is pointed to the correct copy of `emacsclient` on your system.
16 |
17 | Out of the box, it will run `/usr/local/bin/emacsclient`, which should be the
18 | correct copy if you are using Emacs from Homebrew. If you are not, or if that
19 | path differs in some way, you will need to create a configuration file.
20 |
21 | Create a file called `~/.orgprotocol.ini`. Note the leading period in the
22 | filename. This file should simply contain these two lines:
23 |
24 | ```
25 | [emacsclient]
26 | path=/path/to/your/emacsclient
27 | ```
28 |
29 | ## How does it work? ##
30 |
31 | In OS X, any application that exists in your root `/Applications` directory and
32 | that contains a `CFBundleURLTypes` stanza in its `Info.plist` will be registered
33 | as a handler for the URL schemes described there.
34 |
35 | In the case of this application, the stanza looks like this:
36 |
37 | ```
38 | CFBundleURLTypes
39 |
40 |
41 | CFBundleURLName
42 | EmacsClientCapture
43 | CFBundleURLSchemes
44 |
45 | org-protocol
46 |
47 |
48 |
49 | ```
50 |
51 | In my understanding, the `CFBundleURLName` makes no difference at all, it just
52 | needs to be set to something. When any program on your system attempts to visit
53 | a URL beginning with `org-protocol://`, OS X will launch this application and
54 | hand it the URL through some known OS X API mechanism.
55 |
56 | In AppleScript, accessing that data is quite simple, you need only define an `on
57 | open location` subroutine that accepts the whole URL as a string and then you
58 | can do what you like with it.
59 |
60 | This application is designed to accept an `org-protocol://` URL and it simply
61 | launches `emacsclient` and hands that URL to it. This enables your
62 | already-running Emacs server to be triggered by `org-protocol://` links from
63 | anywhere on your system.
64 |
65 | Neither the `emacsclient` program nor Emacs itself will decode the URL; it
66 | expects to receive a raw URL with spaces and all, but the URL will need to be
67 | encoded to preserve spaces (especially) when the handler is called by your
68 | browser, so this handler uses a small Python script to decode the URL and also
69 | extract the page title from it so we can display a notification.
70 |
71 | ## Why? ##
72 |
73 | In short, so that you can save things from your web browser directly into Org
74 | capture templates or simply save the links to pages for later insertion into Org
75 | documents.
76 |
--------------------------------------------------------------------------------