Importing all of your orgmode notes into Apple Notes for mobile access.

Over the years, I’ve built up quite a collection of notes as Org mode text files. So far, it has proven to be the most expressive and the most robust note-taking modality out of a long list of candidates that I’ve tried.

Note-taking using Org mode has one big drawback however: Mobile accessibility.

In other words, consulting one’s org mode notes database from a mobile device is painful. This should not be the case; notes should be always and instantly available, even on mobile.

In this blog post, I show you how to import your complete org mode notes database into Apple Notes, including typesetting and attachments, using the org mode publishing functionality.

To be clear: Org mode on the desktop remains my primary note-taking system. The goal of importing all of my notes into Apple Notes is only to have my personal knowledge base accessible from my mobile devices.

End result

After configuring and running org-publish, and then importing the whole directory of exported HTML files and attachments into Apple Notes on macOS, your notes will look like one of the two examples below: First macOS, then iOS on the phone.

Note the Emacs-supplied syntax highlighting, and the inline image.

If you import these to your icloud account (the default) the notes will be available on all of your iOS mobile devices.

These imported notes are fully indexed, and hence searchable from all of your devices.

An example note in Apple Notes on macOS.
The same note as above, but now in Apple Notes on iOS.

org-publish configuration

In order to make this happen, we make use of the org-publish functionality. We also configure one or two Apple Notes-specific changes to improve rendering.

Add the configuration below to your init.el.

There are two publish targets: One for the org files (called pkb4000 below), and one for all of the attachments (called pkb4000-static below).

As an aside, pkb4000 is short for Personal Knowledge Base 4000. I chose the name as a joke, as this synced directory of org mode files felt like just the Nth in a long series of knowledge base iterations. Little did I know how well this one would stick.

Remember to change both the :base-directory properties to the top-level directory of your notes database. :publishing-directory is anywhere convenient where you would like to store the published HTML files and attachments.

;; disable author + date + validate link at end of HTML exports
(setq org-html-postamble nil)

(defun org-html-publish-to-html-for-apple-notes (plist filename pub-dir)
  "Convert blank lines to <br /> and remove <h1> titles."
  ;; temporarily configure export to convert math to images because
  ;; apple notes obviously can't use mathjax (the default)
  (let* ((org-html-with-latex 'imagemagick)
          (org-publish-org-to 'html filename
                              (concat "." (or (plist-get plist :html-extension)
                              plist pub-dir)))
    ;; 1. apple notes handles <p> paras badly, so we have to replace all blank
    ;;    lines (which the orgmode export accurately leaves for us) with
    ;;    <br /> tags to get apple notes to actually render blank lines between
    ;;    paragraphs
    ;; 2. remove large h1 with title, as apple notes already adds <title> as
    ;; the note title
     (format "sed -i \"\" -e 's/^$/<br \\/>/' -e 's/<h1 class=\"title\">.*<\\/h1>$//' %s"

(setq org-publish-project-alist
         :base-directory "~/Dropbox/notes/pkb4000/"
         :publishing-directory "~/Downloads/pkb4000"
         :recursive t
         :publishing-function org-html-publish-to-html-for-apple-notes
         :section-numbers nil
         :with-toc nil)
         :base-directory "~/Dropbox/notes/pkb4000/"
         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
         :publishing-directory "~/Downloads/pkb4000/"
         :recursive t
         :publishing-function org-publish-attachment

Publish your database and import to Apple Notes

Start the process by M-x org-publish, at which point Emacs will ask you to select a target. You have to org-publish both of the targets.

If you have a large database, Emacs might report errors in your org files. Fix these, and restart the process. org-publish caches its output, so re-runs should not take that long.

After a successful publish, import the whole :publishing-directory into Apple Notes on macOS by selecting File | Import from the menu.

Apple Notes will create a new Imported Notes folder containing your whole notes hierarchy.

This process can be easily repeated when you want to refresh the database on your Apple Notes, but unfortunately Notes will create a new Imported Notes N folder.

Alternatively, you can re-publish, and then import just changed HTML files one by one, after which you’ll have to move them back into the correct Apple Notes folder.


This solves the problem of being able to search rapidly and consult your whole org mode notes database using your iOS mobile device.

However, it does not yet solve the problem of importing Apple Notes you create on the mobile device back into Orgmode. This is something one could consider trying to solve using AppleScript.

Whatever the case may be, this is still a nice improvement over my previous workflow!

Bonus round: Convert orgmode buffer to Apple Note using AppleScript

Before I tried org-publish, I worked on some emacs-lisp and AppleScript to convert the current org mode buffer to HTML, and then to inject that into Apple Notes using Apple Script.

I am posting this here in case it might be useful to someone. However it is NOT required for the org-publish workflow described above.

This assumes that you have an orgmode folder.

Although far more humble than org-publish, this code will only create a new note if it does not exist yet. If the note already exists, it will simply update its contents to the current org file.

(defun string-utils-escape-double-quotes (str-val)
  "Return STR-VAL with every double-quote escaped with backslash."
    (replace-regexp-in-string "\"" "\\\\\"" str-val)))

(defun string-utils-escape-backslash (str-val)
  "Return STR-VAL with every backslash escaped with an additional backslash."
    (replace-regexp-in-string "\\\\" "\\\\\\\\" str-val)))

(setq as-tmpl "set TITLE to \"%s\"
set NBODY to \"%s\"
tell application \"Notes\"
        tell folder \"orgmode\"
                if not (note named TITLE exists) then
                        make new note with properties {name:TITLE}
                end if
                set body of note TITLE to NBODY
        end tell
end tell")

(defun oan-export ()
  (let ((title (file-name-base (buffer-file-name))))
    (with-current-buffer (org-export-to-buffer 'html "*orgmode-to-apple-notes*")
      (let ((body (string-utils-escape-double-quotes
                   (string-utils-escape-backslash (buffer-string)))))
        ;; install title + body into template above and send to notes
        (do-applescript (format as-tmpl title body))
        ;; get rid of temp orgmode-to-apple-notes buffer


If you want to run OpenGL 3.2+ apps in a Windows guest, AVOID Parallels 13 and buy VMWare Fusion 10 instead.

TL;DR: Parallels Desktop 13 only supports OpenGL 3.2 on an extremely limited subset of mostly games. VMWare Fusion 10 has full OpenGL 3.3 support. In my case, this made the difference between being able to work on a VTK-based client project (VMWare Fusion 👍👍) or NOT being able to work the project (Parallels 👎👎).

I bought a Parallels Desktop Pro 13 subscription to be able to do Linux and Windows development on my MacBook Pro.

Although PD is extremely well done otherwise, they seem to have been dragging their feet with rolling out full OpenGL 3.2 support, as can be seen in a number of threads on their forums, e.g. here, here and here.

For a client project, we are currently working on a cross-platform VTK-based app which targets Windows as its main platform. I was looking forward to using my Parallels Windows 10 guest to test the prototype out. Unfortunately, when trying to run a simple VTK sample I was greeted with this error message:

Warning: In ..\Rendering\OpenGL2\vtkOpenGLRenderWindow.cxx, line 647
vtkWin32OpenGLRenderWindow (0000018AAABA44B0): VTK is designed to work
with OpenGL version 3.2 but it appears it has been given a context
that does not support 3.2. VTK will run in a compatibility mode
designed to work with earlier versions of OpenGL but some features may
not work.

The app then reproducibly crashes hard.

Further investigation with the OpenGL Extension Viewer showed that there were three different OpenGL renderers: Two of them are OpenGL 2.1 capable, and one is 3.2 capable. However, as a user, you can’t decide which app gets which renderer.

Further digging, also with the glewinfo app, reveals that Parallels only very selectively supports certain games and apps. See the list at the end of this knowledge base article.

I logged a support issue re the VTK 7 and later OpenGL 3.2+ requirement. The bug was acknowledged, and Parallels confirmed that it was now on their backlog, but that they could give no indication of when this would be available.

Client projects really can’t wait for this, so based on good reviews of the OpenGL support in VMWare Fusion 10, I made use of their Cyber Monday special to purchase a license for the Pro version.

After installation, I imported the VM from Parallels Desktop:

… after which I was greeted with the VM configuration screen where I could configure the 3D support:

After which I could boot up the same Windows VM and run the VTK sample app without any issues whatsoever:

So there you go: If you need good OpenGL support in your VMs, prefer VMWare Fusion over Parallels Desktop.

Driving the Dell U2713HM at 2650×1440 from the HDMI output of the HyperDrive USB-c dock with macOS SwitchResX

In a post from 2014, I showed how to drive the sub-standard HDMI input of the Dell U2713HM 27″ UltraSharp at a resolution of 2560×1440 from the HDMI 1.3 output of a Linux-running laptop.

Fast forward 3 years, and I found myself having to drive the exact same monitor at its native resolution via its (sub-standard) HDMI input from a 2017 MacBook Pro through the brilliant HyperDrive USB-C dock.

(Apple, USB-C is nice, but you really pushed it too far this time.)

Fortunately, using a shareware tool called SwitchResX and information from one of the comments on my previous post, this is possible.

Although one can import Linux ModeLine timings into SwitchResX, the previous timings refused to work. It looks like this is due to macOS refusing to apply monitor timings which exceed the EDID-reported maximum pixel clock of 170 MHz.

Fortunately, SwitchResX is able to generate new timings for reduced blanking (this is crucial to be able to drive this monitor at its full resolution in spite of its HDMI port technically not supporting this) given the resolution and the refresh rate.

Setting the refresh rate to 42 yields a pixel clock of 162 MHz. This screenshot shows you how:

43Hz was probably also possible, but 42 is the answer to life the universe and everything, so there’s that.

To be able to setup these custom timings, you have to disable System Integrity Protection temporarily by booting into the recovery partition.