diff --git a/Cargo.toml b/Cargo.toml
index 17f1b4b..1291649 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,4 +4,6 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
+anyhow = "1.0.86"
 midir = "0.10.0"
+thiserror = "1.0.63"
diff --git a/src/main.rs b/src/main.rs
index e7a11a9..a6f105b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,59 @@
+use std::io::stdin;
+use std::io::Read;
+
+use anyhow::{anyhow, Result};
+use midir::{MidiInput, MidiInputPort};
+
 fn main() {
-    println!("Hello, world!");
+    match run() {
+        Ok(_) => {}
+        Err(err) => {
+            println!("Ended with error: {}", err);
+        }
+    }
 }
+
+fn run() -> Result<()> {
+    let midi_in = MidiInput::new("keyboard")?;
+
+    let midi_device = find_first_midi_device(&midi_in)?;
+    let port_name = midi_in.port_name(&midi_device)?;
+
+    let _midi_connection = match midi_in.connect(&midi_device, &port_name, handle_midi_event, ()) {
+        Ok(m) => m,
+        Err(err) => return Err(anyhow!("failed to connect to device: {}", err)),
+    };
+
+    // TODO: instead, listen for a signal like control-d
+    let mut buf = String::new();
+    stdin().read_line(&mut buf)?;
+
+    Ok(())
+}
+
+fn handle_midi_event(timestamp: u64, message: &[u8], _extra_data: &mut ()) {
+    println!("{timestamp} > {message:?}")
+}
+
+/// Finds the first MIDI input port which corresponds to a physical MIDI device
+/// connected to this computer.
+fn find_first_midi_device(midi_in: &MidiInput) -> Result<MidiInputPort> {
+    let ports = midi_in.ports();
+
+    let mut physical_ports = ports.iter().filter(|p| {
+        midi_in
+            .port_name(p)
+            .map_or(false, |n| !n.starts_with("Midi Through:"))
+    });
+
+    physical_ports
+        .next()
+        .cloned()
+        .ok_or(anyhow!("no physical MIDI devices found"))
+}
+
+// "Midi Through" is automatically created bya the snd-seq-dummy kernel module.
+// We can ignore it, since it's not a physical MIDI device, which is what we
+// are looking for.
+//
+// Source: https://www.reddit.com/r/linuxaudio/comments/jsrl31/comment/gc16qwu/