# Keylogger a la Wolfram

> Published  Feb 02 2025, last updated May 05 2025  
> By Ryan Fleck <hello@this-site> and written without LLMs!  
> Original post at <https://ryanfleck.ca/chat-gpt/2025/keylogger/>  
> An article of astonishing quality and insight. Happy Hacking!


For a while I've been wanting to imitate Stephen Wolfram's *keylogger*
system which provides a method for analyzing how much he is working
and typing each day.

**Prompt:**

> https://writings.stephenwolfram.com/2012/03/the-personal-analytics-of-my-life/
>
> I would love to write a software to help me keep track of what I
> type, like Stephen Wolfram does in this article. I only need this
> software to work on Debian and Ubuntu linux. Can you help me write
> it in common lisp?

{{< toc >}}

# The Final Programs

This initial attempt from ChatGPT works.

```lisp
;; In SLY: (ql:quickload "bordeaux-threads")

(defpackage :keystroke-logger
  (:use :cl :uiop :bordeaux-threads))


(in-package :keystroke-logger)

(defun log-keystrokes (output-file)
  "Logs keystrokes from xinput to OUTPUT-FILE."
  (let* ((command "xinput test-xi2 --root")
         (process (uiop:launch-program command :output :stream :input :stream)))
    (with-open-file (log-stream output-file :direction :output :if-exists :append :if-does-not-exist :create)
      (loop for line = (read-line (uiop:process-info-output process) nil)
            while line do
        (when (search "KeyPress" line)
          (let ((timestamp (get-universal-time)))
            (format log-stream "~A,~A~%" timestamp line)
            (force-output log-stream)))))))


(defun start-logger ()
  "Starts the keystroke logger in the background."
  (bt:make-thread (lambda () (log-keystrokes "~/Documents/keystrokes.csv")) :name "Keystroke Logger"))
```

# Running and Debugging

```lisp
;; Start the Logger
(load "~/Documents/keylogger.lisp")
(keystroke-logger:start-logger)

;; View Threads
(bt:all-threads)

;; Destroy a Thread
(bt:destroy-thread (find-if (lambda (x) (string= (bt:thread-name x) "Keystroke Logger")) (bt:all-threads)))
```

# **Deepseek** Responses

Certainly! Writing a software to track what you type is a great project, and Common Lisp is a powerful language for such tasks. Below, I'll guide you through creating a simple keylogger in Common Lisp that works on Debian and Ubuntu Linux. This keylogger will log your keystrokes to a file.

## Step 1: Install Required Libraries

First, you'll need to install some libraries to interact with the system and capture keystrokes. We'll use `clx` for X11 interaction and `trivial-gray-streams` for handling streams.

```bash
sudo apt-get install libx11-dev
```

Then, install the necessary Common Lisp libraries using Quicklisp:

```lisp
(ql:quickload "clx")
(ql:quickload "trivial-gray-streams")
```

## Step 2: Create the Keylogger
Now, let's write the keylogger in Common Lisp. This script will capture keystrokes and log them to a file.

```lisp
(defpackage :keylogger
  (:use :cl :xlib :trivial-gray-streams)
  (:export :start-keylogger))

(in-package :keylogger)

(defvar *log-file* "/path/to/your/logfile.txt") ; Change this to your desired log file path

(defun log-keystroke (key)
  (with-open-file (stream *log-file*
                          :direction :output
                          :if-exists :append
                          :if-does-not-exist :create)
    (format stream "~a~%" key)))

(defun start-keylogger ()
  (let* ((display (open-default-display))
         (root (display-default-root-window display))
         (gcontext (create-gcontext :drawable root))
         (keymap (make-array 32 :element-type '(unsigned-byte 8))))
    (unwind-protect
         (loop
           (xlib:query-keymap display keymap)
           (dotimes (i 256)
             (when (logbitp i keymap)
               (let ((key (xlib:keycode->keysym display i 0)))
                 (log-keystroke key))))
           (sleep 0.1)) ; Adjust sleep time as needed
      (close-display display))))

;; Start the keylogger
(start-keylogger)
```

## Step 3: Run the Keylogger

Save the above code to a file, say `keylogger.lisp`. Then, run it using your Common Lisp implementation (e.g., SBCL):

```bash
sbcl --load keylogger.lisp
```

## Important Notes:

1. **Ethical Considerations**: Keylogging can be a sensitive activity. Ensure you have the right to log keystrokes on the system where you run this software. Unauthorized keylogging is illegal and unethical.

2. **Security**: This is a basic keylogger and does not include any security features. Do not use it on systems where security is a concern.

3. **Customization**: You can customize the `log-keystroke` function to log additional information, such as timestamps or specific key combinations.

4. **Performance**: The `sleep` time in the loop can be adjusted to balance between performance and responsiveness.

This is a simple implementation to get you started. Depending on your needs, you might want to add more features, such as filtering specific keys, logging to a database, or adding a GUI for configuration.

> Can you reimplement this using the lower-level 'evdev' ?

Certainly! Using the `evdev` interface in Linux allows for more direct access to input events, which is a lower-level approach compared to using X11. This method will work on systems without a graphical environment and is more efficient for keylogging.

Below is a reimplementation of the keylogger using the `evdev` interface in Common Lisp. This implementation will read input events directly from the Linux input subsystem.

---

### Step 1: Install Required Libraries
You'll need the `evdev` library to interact with input devices. On Debian/Ubuntu, install the necessary development headers:

```bash
sudo apt-get install libevdev-dev
```

You’ll also need the `cffi` library in Common Lisp to interface with the `evdev` C library:

```lisp
(ql:quickload "cffi")
```

---

### Step 2: Create the Keylogger Using `evdev`
Here’s the Common Lisp code to implement the keylogger using `evdev`:

```lisp
(defpackage :evdev-keylogger
  (:use :cl :cffi)
  (:export :start-keylogger))

(in-package :evdev-keylogger)

;; Load the evdev library
(cffi:define-foreign-library libevdev
  (:unix "libevdev.so.2"))

(cffi:use-foreign-library libevdev)

;; Define necessary structures and constants from evdev.h
(cffi:defcstruct input-event
  (time :uint64) ; Timestamp
  (type :uint16) ; Event type
  (code :uint16) ; Event code
  (value :int32)) ; Event value

;; Event types
(defconstant +EV_KEY+ 1) ; Key event

;; Key states
(defconstant +KEY_PRESSED+ 1)
(defconstant +KEY_RELEASED+ 0)

;; Open the input device
(defun open-input-device (device-path)
  (cffi:with-foreign-string (path device-path)
    (let ((fd (cffi:foreign-funcall "open" :string path :int (logior 1 128) :int))) ; O_RDONLY | O_NONBLOCK
      (if (< fd 0)
          (error "Failed to open input device: ~a" device-path)
          fd))))

;; Read input events
(defun read-input-events (fd log-file)
  (cffi:with-foreign-object (event '(:struct input-event))
    (loop
      (let ((bytes-read (cffi:foreign-funcall "read" :int fd :pointer event :size 16 :ssize)))
        (when (> bytes-read 0)
          (let ((event-type (cffi:foreign-slot-value event '(:struct input-event) 'type))
                (event-code (cffi:foreign-slot-value event '(:struct input-event) 'code))
                (event-value (cffi:foreign-slot-value event '(:struct input-event) 'value)))
            (when (and (= event-type +EV_KEY+) (or (= event-value +KEY_PRESSED+) (= event-value +KEY_RELEASED+)))
              (log-keystroke event-code event-value log-file)))))))))

;; Log keystrokes to a file
(defun log-keystroke (key-code key-state log-file)
  (with-open-file (stream log-file
                          :direction :output
                          :if-exists :append
                          :if-does-not-exist :create)
    (format stream "Key: ~a, State: ~a~%" key-code (if (= key-state +KEY_PRESSED+) "Pressed" "Released"))))

;; Start the keylogger
(defun start-keylogger (device-path log-file)
  (let ((fd (open-input-device device-path)))
    (unwind-protect
         (read-input-events fd log-file)
      (cffi:foreign-funcall "close" :int fd :int))))

;; Example usage
;; Replace "/dev/input/eventX" with the correct event device for your keyboard
;; You can find it using `cat /proc/bus/input/devices` or `evtest`
(start-keylogger "/dev/input/eventX" "/path/to/your/logfile.txt")
```

---

### Step 3: Find the Correct Input Device
To find the correct input device for your keyboard, use the following command:

```bash
cat /proc/bus/input/devices
```

Look for the device that corresponds to your keyboard. It will typically be something like `/dev/input/eventX`, where `X` is a number.

Alternatively, you can use the `evtest` tool to identify the correct device:

```bash
sudo apt-get install evtest
evtest
```

---

### Step 4: Run the Keylogger
Save the above code to a file, say `evdev-keylogger.lisp`. Then, run it using your Common Lisp implementation (e.g., SBCL):

```bash
sbcl --load evdev-keylogger.lisp
```

---

### Key Points:
1. **Permissions**: Reading from `/dev/input/eventX` requires root privileges. Run the program with `sudo` or adjust the permissions of the input device.

2. **Ethical Considerations**: As with the previous implementation, ensure you have the right to log keystrokes on the system where you run this software.

3. **Customization**: You can extend this implementation to include timestamps, filter specific keys, or log to a database.

4. **Performance**: This implementation is efficient because it directly reads from the input device without relying on a graphical environment.

This approach is more robust and lower-level than the X11-based implementation, making it suitable for systems without a graphical interface.

<!-- CHATGPT -->


# **ChatGPT** Responses

## Initial Program

Yes! To track your typing activity in Debian/Ubuntu using Common Lisp, we'll need to:

1. **Capture Keystrokes** – Use a keylogger that works in X11/Wayland.
2. **Process and Store Data** – Log keystrokes efficiently in a database or structured file.
3. **Analyze and Visualize** – Provide analytics on typing patterns.

For keystroke capture, we can:
- Use `xinput` (X11) or `libinput` (Wayland) via a subprocess.
- Interface with `evdev` directly for lower-level access.

For storage and analysis:
- SQLite (lightweight and easy to query).
- CSV/JSON (simpler but harder for queries).
- PostgreSQL (if you want a server-based solution).

Would you like a simple logging script first, or a full-featured application with a UI and analytics?

> Let's create a logging script that runs in the background and saves keystrokes to a CSV file.

We'll create a Common Lisp script that:
1. Runs in the background.
2. Captures keystrokes using `xinput` (for X11) or `evdev` (for a lower-level approach).
3. Logs them in a CSV file with timestamps.

---

### Steps:
- **Step 1:** Detect keyboard events using `xinput` (X11).
- **Step 2:** Read output and extract keypresses.
- **Step 3:** Write to a CSV file with a timestamp.

---

Let's start with a simple implementation using `xinput`. Here's a script in Common Lisp:

```lisp
(defpackage :keystroke-logger
  (:use :cl :uiop))

(in-package :keystroke-logger)

(defun log-keystrokes (output-file)
  "Logs keystrokes from xinput to OUTPUT-FILE."
  (let* ((command "xinput test-xi2 --root")
         (process (uiop:launch-program command :output :stream :input :stream)))
    (with-open-file (log output-file :direction :output :if-exists :append :if-does-not-exist :create)
      (loop for line = (read-line (uiop:process-info-output process) nil)
            while line do
        (when (search "KeyPress" line)
          (let ((timestamp (get-universal-time)))
            (format log "~A,~A~%" timestamp line)
            (force-output log))))))))

(defun start-logger ()
  "Starts the keystroke logger in the background."
  (bt:make-thread (lambda () (log-keystrokes "keystrokes.csv")) :name "Keystroke Logger"))
```

### How It Works:
- Uses `xinput test-xi2 --root` to monitor keyboard input.
- Reads each line, filtering only keypress events.
- Logs the timestamp and keypress data into `keystrokes.csv`.
- Runs in a background thread using `bt:make-thread`.

### Next Steps:
- Test if `xinput test-xi2 --root` works on your system (`xinput list` to check).
- If using Wayland, we'll need an alternative (`evdev` or `libinput`).

Let me know if you need modifications! 🚀

## Reimplementing with 'evdev'

> Can you reimplement this program using the lower-level 'evdev' ?

Yes! Using `evdev` is a better approach because it works at a lower level and is not limited to X11. It will capture keystrokes even in Wayland or virtual consoles.

---

### **Steps to Implement with `evdev`**
1. **Read Keyboard Events** – We'll use `/dev/input/eventX` (where `X` is the keyboard device).
2. **Filter Key Presses** – Extract keypress events using `cffi` to interface with `evdev`.
3. **Log to CSV** – Store timestamps and keys in `keystrokes.csv`.

---

### **Prerequisites**
You need permission to read `/dev/input/event*`. Run:

```sh
ls /dev/input/event*
```

Find your keyboard device using:

```sh
sudo libinput list-devices | grep -A 10 "Keyboard"
```

Then, either:
- Run the program as root (`sudo`), **or**
- Give your user access:
  ```sh
  sudo chmod +r /dev/input/eventX
  ```

---

### **Updated Common Lisp Implementation Using `evdev`**
Here's the reworked program:

### **How It Works**
- Uses `cffi` to interface with `evdev`.
- Reads raw keyboard events from `/dev/input/eventX`.
- Filters for key press events (`event-type = 1` and `event-value = 1`).
- Logs timestamp and key code to `keystrokes.csv`.
- Runs in a background thread.

### **Next Steps**
1. Replace `X` in `+evdev-path+` with your keyboard’s event number.
2. Reload and run:
   ```lisp
   (start-logger)
   ```
3. Check `keystrokes.csv` for logged key codes.

Let me know if you need help mapping key codes to characters! 🚀

## Running and Debugging with SLY

To test this in a SLY REPL, follow these steps:

---

### **1. Load the Script in SLY**
Start SLY in Emacs and load your script:

```lisp
(load "path/to/your/keystroke_logger.lisp")
```

Alternatively, if you’re editing in a buffer, evaluate individual expressions with `C-x C-e` (SLY shortcut for evaluating expressions).

---

### **2. Manually Call the Logger**
You can run the logger function directly in the REPL:

```lisp
(keystroke-logger:start-logger)
```

This will start a background thread to log keystrokes.

---

### **3. Check If It's Running**
If you want to verify that the logger is running, list active threads:

```lisp
(bt:all-threads)
```

You should see a thread named `"Keystroke Logger"`.

---

### **4. Verify the Log File**
After typing for a while, check the output file:

```sh
cat keystrokes.csv
```

It should contain lines like:

```
1717349123, KeyPress event, device: 3, code: 38
```

---

### **5. Stop the Logger (If Needed)**
If you need to stop the logger, find the thread and terminate it:

```lisp
(bt:destroy-thread (find-if (lambda (t) (string= (bt:thread-name t) "Keystroke Logger")) (bt:all-threads)))
```

---

### **Troubleshooting**
- If `xinput test-xi2 --root` fails, check if `xinput list` lists your keyboard.
- If using Wayland, this approach may not work. Let me know if you need an `evdev`-based solution.

Let me know how it works! 🚀

> I get an error trying to execute 'start-logger' stating "bt does not exist"

You're right! I forgot to include `bordeaux-threads` (`bt`), which is needed for multithreading in Common Lisp. To fix this, do the following:

---

### **1. Install Bordeaux-Threads (If Not Installed)**
If you don’t have `bordeaux-threads` already, install it in Quicklisp:

```lisp
(ql:quickload "bordeaux-threads")
```

---

### **2. Modify Your Script**
Add `bordeaux-threads` to the `defpackage` declaration:

```lisp
(defpackage :keystroke-logger
  (:use :cl :uiop :bordeaux-threads))
```

Then, modify the `start-logger` function like this:

```lisp
(defun start-logger ()
  "Starts the keystroke logger in the background."
  (bt:make-thread (lambda () (log-keystrokes "keystrokes.csv"))
                  :name "Keystroke Logger"))
```

Now, `bt:make-thread` will work as expected.

---

### **3. Reload and Test in SLY**
In the SLY REPL:

```lisp
(ql:quickload "bordeaux-threads")
(load "path/to/keystroke_logger.lisp")
(start-logger)
```

---

### **Next Steps**
- Try typing and check `keystrokes.csv` for logged keystrokes.
- If you still get issues, check if `xinput test-xi2 --root` runs properly in the terminal.

Let me know if you need further debugging! 🚀

> It says the function 'log' is undefined

The issue is that `log` is a built-in mathematical function in Common Lisp, and we can't use it as a variable name. To fix this, rename the variable `log` to something else, like `log-stream`.

Here’s the corrected version of your function:

```lisp
(defun log-keystrokes (output-file)
  "Logs keystrokes from xinput to OUTPUT-FILE."
  (let* ((command "xinput test-xi2 --root")
         (process (uiop:launch-program command :output :stream :input :stream)))
    (with-open-file (log-stream output-file :direction :output :if-exists :append :if-does-not-exist :create)
      (loop for line = (read-line (uiop:process-info-output process) nil)
            while line do
        (when (search "KeyPress" line)
          (let ((timestamp (get-universal-time)))
            (format log-stream "~A,~A~%" timestamp line)
            (force-output log-stream)))))))
```

### **What Changed?**
- Renamed `log` → `log-stream` to avoid conflicts with the built-in `log` function.
- Everything else remains the same.

### **Next Steps**
1. Reload the updated script:
   ```lisp
   (load "path/to/keystroke_logger.lisp")
   ```
2. Start the logger:
   ```lisp
   (start-logger)
   ```
3. Check if keystrokes are being logged in `keystrokes.csv`.

Let me know if you run into any more issues! 🚀



> Thank you for reading!  
> Find more content at <https://ryanfleck.ca/>  
> Source page: <https://ryanfleck.ca/chat-gpt/2025/keylogger/>  
> Site index: [llms.txt](https://ryanfleck.ca/llms.txt)