• Introducing TouchKey

    Short version

    TouchKey is a small Mac application that lives on your status bar and on the Control Strip of your TouchBar (if you Mac has one).

    You can download TouchKey here, try it for a week, and activate it for USD 5.99 if you find it useful.

    You can configure TouchKey to send keyboard shortcuts to any running app, or to a Safari tab matching a given URL.

    TouchKey was born out of necessity. In this time when we are all working remotely, I found myself using Zoom, Slack, and Google Meet for conferencing, and I wanted a quick way to mute any of those tools quickly, from the TouchBar or the status bar.

    TouchKey status bar menu


    When researching for TouchBar, I found several competing alternatives, like Mutify, or Mic Drop.

    These apps work by muting the actual microphone in your Mac, something more akin to a hardware switch.

    TouchKey, on the other hand, is definitely a software switch. The main advantage I see (when used for muting) is that your peers at the call can quickly see your mute status, which turns out to be useful if you tend to start speaking without realising you are muted.

    Also, since TouchKey is a generic tool, you can configure it to send any keyboard shortcut to any running app. Do you open a new private Window Safari often? You can configure that shortcut.

    Slightly Longer version

    Ever since I got my first Mac, I was fascinated with small apps that were almost invisible for the user, but did a useful task.

    I remember back then (when the tool was Project Builder and not Xcode) trying to understand how to develop those small utilities, but failing both to understand basic Cocoa concepts but also to come up with a useful idea on what to develop.

    The combination of working a lot from home lately, and having conference calls using several different tools finally made it for me: I needed a global mute button on my Mac.

    I tried developing TouchKey with SwiftUI, but quickly found a few bugs that ended up being showstoppers, so mid-development I switched to the old and trusty AppKit, the same I’ve been scared more than 10 years ago.

  • Publish from Collected Notes to GitHub Pages

    My blog is a statically generated Jekyll site hosted on GitHub pages.

    While I do use Collected Notes to post some short things, I haven’t migrated my blog there since it will break all my past permalinks. However, I do love the simplicity to both write and publish quickly from any device that Collected Notes gives me.

    In order to keep the best of both worlds, I created a small GitHub Action, that I can trigger from my phone using Shortcuts, that will quickly import a note in Collected to a post in GitHub Pages.

    You can check the code here

    The important part is this shell script:

    curl --location $.json > note.json
    date_prefix=`date +%Y-%m-%d`
    markdown_name=${date_prefix}-`jq -r '.path' note.json`.markdown
    title=`jq -r '.title' note.json`
    cat <<EOF > ${post_path}  
    title: ${title}
    date: `date --iso-8601=seconds`
    `jq -r '.body' note.json`

    In order to run it, you have to:

    1. Create the GitHub Action.
    2. Create a Personal Token in GitHub.
    3. Import this Shortcut (or create your own!)
    4. From your iOS device, share a note in CN and choose the Shortcut.
    5. Check your new post in your blog!
  • Useful SQL queries for integrating GitLab in Metabase

    Recently I started playing with Metabase to create interesting visualizations and have the teams quickly check how we are working.

    One of the main tools we use is GitLab, so I needed the ability to query it. To be on the safe side, I configured the backup job to create a read-only mirror of the DB, and have Metabase access it.

    However, as soon as I started trying to query it, I found that the table structure used in GitLab was difficult to use for quick visualizations: while the unit our users know is their project, projects reside in a namespace, and namespaces can be nested. Also, since GitLab supports forks, you may end up with several projects that you want to aggregate stats of in order to reflect accurate numbers.

    To solve this, I created a few helper queries using PostgreSQL CTEs1.

    Here’s a dump of the queries I found useful, and some brief explanation of what they do.

    -- flat_namespaces - for a leaf_id, obtain the full name of the namespace where it is
    WITH RECURSIVE flat_namespaces AS (
        SELECT CONCAT(name) AS names, id, parent_id, id AS leaf_id
        FROM namespaces
        WHERE parent_id IS NULL
        SELECT CONCAT(flat_namespaces.names, ' / ', namespaces.name) AS names, namespaces.id, namespaces.parent_id, flat_namespaces.leaf_id
        FROM namespaces
        INNER JOIN flat_namespaces ON namespaces.id = flat_namespaces.parent_id
    -- root_projects - searches for the root project based on a candidate fork id
    , root_projects(root_project_id, fork_project_id) AS (
        ( -- project with no forks
            SELECT id AS root_project_id, id AS fork_project_id
            FROM projects
            WHERE NOT EXISTS (
                SELECT 1
                FROM fork_network_members
                WHERE (forked_from_project_id = projects.id OR project_id = projects.id)
        ( -- project that has forks (parent)
            SELECT DISTINCT forked_from_project_id as root_project_id, forked_from_project_id as fork_project_id
            FROM fork_network_members
        ( -- project that is a fork
            SELECT DISTINCT forked_from_project_id AS root_project_id, project_id AS fork_project_id
            FROM fork_network_members
            WHERE forked_from_project_id IS NOT NULL
    project_with_full_namespace(full_name, project_id) AS (
        SELECT CONCAT(flat_namespaces.names, ' / ', projects.name), root_projects.fork_project_id AS project_id
        FROM root_projects
        INNER JOIN projects ON projects.id = root_projects.root_project_id
        INNER JOIN flat_namespaces ON projects.namespace_id = flat_namespaces.leaf_id
    FROM project_with_full_namespace

    Let’s start from the top:

    flat_namespaces will return a table containing the concatenated names, the parent namespace id, and the leaf namespace id.

    So, if you have a namespace at /a/b/c, where the IDs are 1, 2, and 3 respectively, and you query it with a WHERE clause of leaf_id = 3, you’ll get a/b/c as the names value.

    root_projects will return a pair of root_project_id and fork_project_id. This takes into account the three different possibilities that might exists:

    • Projects with no forks.
    • Projects with forks.
    • Forks of other projects.

    Basically, with a fork_project_id you can get the corresponding root_project_id. If you couple this with flat_namespaces, for any given project ID on your GitLab installation, you can grab the root_project_id with the “full path” of namespaces as a string.

    Finally, project_with_full_namespace uses the previously defined CTEs to create the “full name” of the project and all its parent namespaces, similar to what you see in GitLab’s URLs. It’s important to note that this query will ignore forks, as it uses root_projects to navigate the fork tree, but this is what we needed for our dashboards.

    1. Common Table Structure 

  • HomeKit Accessory Simulator for iOS 10

    If you are developing for HomeKit, you will probably want to use the HomeKit Accessory Simulator.

    To download it, we are instructed to search for “Hardware IO Tools for Xcode”. However, in Xcode 8, this package changed its name to “Additional Tools for Xcode”. If you are looking for the latest supported HomeKit accesory types, search for the latest version.

  • Shopster 1.2 Released

    After reviving it, Shopster 1.2 is now available on the App Store. Go get it for free!

  • 1
  • 2