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?
The Final Programs¶
This initial attempt from ChatGPT works.
;; 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¶
;; 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.
sudo apt-get install libx11-dev
Then, install the necessary Common Lisp libraries using Quicklisp:
(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.
(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):
sbcl --load keylogger.lisp
Important Notes:¶
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.
Security: This is a basic keylogger and does not include any security features. Do not use it on systems where security is a concern.
Customization: You can customize the
log-keystroke
function to log additional information, such as timestamps or specific key combinations.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:
sudo apt-get install libevdev-dev
You’ll also need the cffi
library in Common Lisp to interface with the evdev
C library:
(ql:quickload "cffi")
Step 2: Create the Keylogger Using evdev
¶
Here’s the Common Lisp code to implement the keylogger using evdev
:
(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:
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:
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):
sbcl --load evdev-keylogger.lisp
Key Points:¶
Permissions: Reading from
/dev/input/eventX
requires root privileges. Run the program withsudo
or adjust the permissions of the input device.Ethical Considerations: As with the previous implementation, ensure you have the right to log keystrokes on the system where you run this software.
Customization: You can extend this implementation to include timestamps, filter specific keys, or log to a database.
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 Responses¶
Initial Program¶
Yes! To track your typing activity in Debian/Ubuntu using Common Lisp, we’ll need to:
- Capture Keystrokes – Use a keylogger that works in X11/Wayland.
- Process and Store Data – Log keystrokes efficiently in a database or structured file.
- Analyze and Visualize – Provide analytics on typing patterns.
For keystroke capture, we can:
- Use
xinput
(X11) orlibinput
(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:
- Runs in the background.
- Captures keystrokes using
xinput
(for X11) orevdev
(for a lower-level approach). - 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:
(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
orlibinput
).
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
¶
- Read Keyboard Events – We’ll use
/dev/input/eventX
(whereX
is the keyboard device). - Filter Key Presses – Extract keypress events using
cffi
to interface withevdev
. - Log to CSV – Store timestamps and keys in
keystrokes.csv
.
Prerequisites¶
You need permission to read /dev/input/event*
. Run:
ls /dev/input/event*
Find your keyboard device using:
sudo libinput list-devices | grep -A 10 "Keyboard"
Then, either:
- Run the program as root (
sudo
), or - Give your user access:
sudo chmod +r /dev/input/eventX
Updated Common Lisp Implementation Using evdev
¶
Here’s the reworked program:
How It Works¶
- Uses
cffi
to interface withevdev
. - Reads raw keyboard events from
/dev/input/eventX
. - Filters for key press events (
event-type = 1
andevent-value = 1
). - Logs timestamp and key code to
keystrokes.csv
. - Runs in a background thread.
Next Steps¶
- Replace
X
in+evdev-path+
with your keyboard’s event number. - Reload and run:
(start-logger)
- 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:
(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:
(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:
(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:
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:
(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 ifxinput 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:
(ql:quickload "bordeaux-threads")
2. Modify Your Script¶
Add bordeaux-threads
to the defpackage
declaration:
(defpackage :keystroke-logger
(:use :cl :uiop :bordeaux-threads))
Then, modify the start-logger
function like this:
(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:
(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:
(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-inlog
function. - Everything else remains the same.
Next Steps¶
- Reload the updated script:
(load "path/to/keystroke_logger.lisp")
- Start the logger:
(start-logger)
- Check if keystrokes are being logged in
keystrokes.csv
.
Let me know if you run into any more issues! 🚀