Mark Caudill

Tracking App Usage in Linux

I've been thinking a lot lately about the value that tracking personal data has the potential to add, and about the costs that often comes with it. For example, devices like Fitbits or Garmins can provide useful insight into your fitness routine. Apps like Google's Digital Wellbeing or Apple's Screen Time can help you understand where your time and attention are being spent. While I acknowledge the usefulness of these types of technologies, there's this lingering question hovering in the background about just who all has access to this data and how they might want to use it.

I don't know the answer to these questions but it's got me thinking about ways I might be able to get some of these potential benefits without having to trade so much privacy. In that vein, I decided it'd be interesting to see if I could track my own application usage on my laptop.

The first step was to see how I could get the process name of the active window (the window that has focus). It turns out the xprop command pretty much provides all the information we need.

Getting the active window is pretty easy.

$ xprop -root _NET_ACTIVE_WINDOW
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x360000a

I only want the ID though.

$ xprop -root _NET_ACTIVE_WINDOW | rev | cut -d' ' -f1 | rev
0x360000a

Note: when no window is active (i.e. you're on your desktop) this command returns 0x0

With another xprop command I can lookup the window ID and get the PID of the process that owns the window.

xprop -id 0x360000a _NET_WM_PID | rev | cut -d' ' -f1 | rev
4763

Getting the process name is then just a matter of using ps.

$ ps -q 4763 -o command=
/usr/lib/gnome-terminal/gnome-terminal-server

I don't want the whole path so I'll just wrap that in basename.

$ basename $(ps -q 4763 -o command=)
gnome-terminal-server

Then I put this all together in a script (Gist).

#!/bin/sh

DATABASE=${HOME}/.app-track.db
touch "${DATABASE}" && chmod 0600 "${DATABASE}"

active_window_id() {
    xprop -root _NET_ACTIVE_WINDOW | rev | cut -d' ' -f1 | rev
}

window_pid_by_id() {
    xprop -id ${1} _NET_WM_PID | rev | cut -d' ' -f1 | rev
}

process_name_by_pid() {
    basename $(ps -q "${1}" -o command=)
}

active_window_process() {
    id="$(active_window_id)"
    if [ "${id}" = "0x0" ]; then
        echo "Desktop"
    else
        process_name_by_pid "$(window_pid_by_id "${id}")"
    fi
}

timestamp() {
    date '+%s'
}

latest_entry() {
    tail -1 "${DATABASE}"
}

latest_process() {
    echo "$(latest_entry)" | cut -d',' -f2
}

add_entry() {
    echo "$(timestamp),${*}" >> "${DATABASE}"
}

while [ 1 ]; do
    t=$(timestamp)
    p="$(active_window_process)"
    [ "${p}" = "$(latest_process)" ] || add_entry "${p}"
    sleep 1
done

I'm not sure how much farther I'll take this. The next step would be to create a daemon or something to keep usage data. Then I could look at generating reports and seeing what other uses the data might have.