diff --git a/main.go b/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..c4e5f5ba4432002de0b118a47832c3d4385c9b72
--- /dev/null
+++ b/main.go
@@ -0,0 +1,261 @@
+package main
+
+/*
+#cgo LDFLAGS: -lX11
+#include <X11/Xlib.h>
+#include <stdlib.h>
+*/
+import "C"
+
+import (
+	"fmt"
+	"math"
+	"os"
+	"strings"
+	"time"
+	"unsafe"
+)
+
+var (
+	// --- X / Windowing ---
+	disp            *C.Display
+	root            C.Window
+	netActiveWindow C.Atom
+	netWmName       C.Atom
+
+	// Aktuell aktives Fenster
+	currentActiveWindow C.Window
+
+	// --- Zähler für Eingaben ---
+	keyPressCount int     // Anzahl Tastendrücke
+	mouseDistance float64 // Summe der zurückgelegten Maus-Distanz (in Pixel)
+	lastX, lastY  int     // letztes Maus-Event, um Distanz zu berechnen
+	haveLastPos   bool    // ob wir schon einmal eine Mausposition haben
+
+	// --- CSV-Log ---
+	csvFile *os.File
+)
+
+// getActiveWindow liest das aktive Fenster aus _NET_ACTIVE_WINDOW
+func getActiveWindow(disp *C.Display, root C.Window, atom C.Atom) C.Window {
+	var actualType C.Atom
+	var actualFormat C.int
+	var nItems, bytesAfter C.ulong
+	var prop *C.uchar
+
+	status := C.XGetWindowProperty(
+		disp,
+		root,
+		atom,
+		0, 1,
+		C.False,
+		C.AnyPropertyType,
+		&actualType, &actualFormat, &nItems, &bytesAfter, &prop,
+	)
+	if status != C.Success || prop == nil {
+		return 0
+	}
+	defer C.XFree(unsafe.Pointer(prop))
+
+	return *(*C.Window)(unsafe.Pointer(prop))
+}
+
+// getNetWmName liest den UTF-8 Fenstertitel aus _NET_WM_NAME
+func getNetWmName(disp *C.Display, window C.Window, atom C.Atom) string {
+	if window == 0 {
+		return "(no window)"
+	}
+
+	var actualType C.Atom
+	var actualFormat C.int
+	var nItems, bytesAfter C.ulong
+	var prop *C.uchar
+
+	status := C.XGetWindowProperty(
+		disp,
+		window,
+		atom,
+		0, 1<<20, // Bis zu ~1 MB Zeichen
+		C.False,
+		C.AnyPropertyType,
+		&actualType, &actualFormat, &nItems, &bytesAfter, &prop,
+	)
+	if status != C.Success || prop == nil {
+		return "(unknown)"
+	}
+	defer C.XFree(unsafe.Pointer(prop))
+
+	return C.GoString((*C.char)(unsafe.Pointer(prop)))
+}
+
+// parseTabTitle extrahiert den Teil vor " - " als simplen Tab-Namen
+func parseTabTitle(fullTitle string) string {
+	parts := strings.SplitN(fullTitle, " - ", 2)
+	if len(parts) == 2 {
+		return parts[0]
+	}
+	return fullTitle
+}
+
+// resetInputStats setzt Zähler für Tastatur/Maus zurück
+func resetInputStats() {
+	keyPressCount = 0
+	mouseDistance = 0
+	haveLastPos = false
+}
+
+// logToCSV schreibt einen Eintrag in die CSV-Datei.
+// Anschließend werden Key/Mauszähler zurückgesetzt (d. h. gezählt wird immer
+// "seit dem letzten Eintrag").
+func logToCSV(win C.Window, fullTitle, tabTitle string) {
+	timestamp := time.Now().Format("2006-01-02 15:04:05")
+
+	// Zeile bauen. Ggf. CSV escaping anpassen, hier minimal.
+	// Spalten: Timestamp,WindowHex,WindowTitle,Tab,KeyPresses,MouseDistance
+	line := fmt.Sprintf(
+		"%s,0x%x,\"%s\",\"%s\",%d,%.2f\n",
+		timestamp, win, fullTitle, tabTitle,
+		keyPressCount, mouseDistance,
+	)
+
+	_, err := csvFile.WriteString(line)
+	if err != nil {
+		fmt.Printf("Fehler beim Schreiben in CSV: %v\n", err)
+	}
+	// zur Sicherheit direkt flushen
+	csvFile.Sync()
+
+	// Danach Zähler zurücksetzen
+	resetInputStats()
+}
+
+func main() {
+	// X Display öffnen
+	disp = C.XOpenDisplay(nil)
+	if disp == nil {
+		panic("Cannot open display")
+	}
+	defer C.XCloseDisplay(disp)
+
+	// Root-Fenster
+	root = C.XDefaultRootWindow(disp)
+
+	// CSV-Datei öffnen (oder erstellen) zum Anhängen
+	var err error
+	csvFile, err = os.OpenFile("data.csv", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+	if err != nil {
+		panic(err)
+	}
+	defer csvFile.Close()
+
+	// Header nur schreiben, wenn Datei leer war
+	info, _ := csvFile.Stat()
+	if info.Size() == 0 {
+		csvFile.WriteString("Timestamp,WindowHex,WindowTitle,Tab,KeyPresses,MouseDistance\n")
+	}
+
+	// Atoms abfragen
+	netActiveWindow = C.XInternAtom(disp, C.CString("_NET_ACTIVE_WINDOW"), C.True)
+	netWmName = C.XInternAtom(disp, C.CString("_NET_WM_NAME"), C.True)
+	if netActiveWindow == 0 || netWmName == 0 {
+		panic("Could not retrieve EWMH atoms")
+	}
+
+	// Auf dem Root-Fenster:
+	//  - PropertyChangeMask => _NET_ACTIVE_WINDOW ändert sich
+	//  - KeyPressMask, PointerMotionMask => Tastendrücke / Mausbewegung
+	//    (ACHTUNG: KeyPressMask / PointerMotionMask am Root-Fenster
+	//     funktioniert nur, wenn der WM die Events "durchlässt"
+	//     oder du ein globales Keyboard/Mausgrab machst.
+	//     Je nach WM/Umgebung klappt das ggf. nicht perfekt.)
+
+	eventMask := C.long(C.PropertyChangeMask | C.KeyPressMask | C.PointerMotionMask)
+	C.XSelectInput(disp, root, eventMask)
+
+	//	eventMask := (C.PropertyChangeMask |
+	//		C.KeyPressMask |
+	//		C.PointerMotionMask)
+	//	C.XSelectInput(disp, root, eventMask)
+
+	// aktuelles aktives Fenster ermitteln
+	currentActiveWindow = getActiveWindow(disp, root, netActiveWindow)
+
+	// Erstes Logging, falls wir schon ein aktives Fenster haben
+	if currentActiveWindow != 0 {
+		// Für Titeländerungen in diesem Fenster: Auch hier Eventmask setzen
+		// (PropertyChangeMask => _NET_WM_NAME).
+		C.XSelectInput(disp, currentActiveWindow, C.PropertyChangeMask)
+
+		title := getNetWmName(disp, currentActiveWindow, netWmName)
+		tab := parseTabTitle(title)
+		fmt.Printf("Initially active: 0x%x - %s\n", currentActiveWindow, title)
+
+		// Log in CSV + Reset
+		logToCSV(currentActiveWindow, title, tab)
+	}
+
+	fmt.Println("Starte Event-Loop (Fenster/Tab + KeyPress + Mausbewegung) ...")
+
+	// Hauptschleife
+	var ev C.XEvent
+	for {
+		C.XNextEvent(disp, &ev)
+		evType := (*C.XAnyEvent)(unsafe.Pointer(&ev))._type
+
+		switch evType {
+		// 1) TITLE- oder ACTIVE-Window-Änderungen
+		case C.PropertyNotify:
+			xprop := (*C.XPropertyEvent)(unsafe.Pointer(&ev))
+
+			// a) _NET_ACTIVE_WINDOW am Root: Neues Fenster aktiv
+			if xprop.window == root && xprop.atom == netActiveWindow {
+				newActive := getActiveWindow(disp, root, netActiveWindow)
+				if newActive != currentActiveWindow {
+					currentActiveWindow = newActive
+					fmt.Printf("\nActive window changed: 0x%x\n", currentActiveWindow)
+
+					// Jetzt am neuen aktiven Fenster: PropertyChangeMask (Titeländerungen)
+					C.XSelectInput(disp, currentActiveWindow, C.PropertyChangeMask)
+
+					title := getNetWmName(disp, currentActiveWindow, netWmName)
+					tab := parseTabTitle(title)
+					fmt.Printf("Title: %s\n=> Tab: %s\n", title, tab)
+
+					logToCSV(currentActiveWindow, title, tab)
+				}
+
+				// b) _NET_WM_NAME am aktuell aktiven Fenster => Titel/Tab-Wechsel
+			} else if xprop.window == currentActiveWindow && xprop.atom == netWmName {
+				title := getNetWmName(disp, currentActiveWindow, netWmName)
+				tab := parseTabTitle(title)
+				fmt.Printf("\nTitle changed on active window (0x%x): %s\n=> Tab: %s\n",
+					currentActiveWindow, title, tab)
+
+				logToCSV(currentActiveWindow, title, tab)
+			}
+
+		// 2) Tastendrücke
+		case C.KeyPress:
+			keyPressCount++
+
+		// 3) Mausbewegung
+		case C.MotionNotify:
+			motion := (*C.XMotionEvent)(unsafe.Pointer(&ev))
+			x := int(motion.x_root)
+			y := int(motion.y_root)
+			if haveLastPos {
+				dx := float64(x - lastX)
+				dy := float64(y - lastY)
+				mouseDistance += math.Sqrt(dx*dx + dy*dy)
+			} else {
+				haveLastPos = true
+			}
+			lastX = x
+			lastY = y
+
+		default:
+			// Andere Events ignorieren
+			// fmt.Printf("Unhandled event type: %d\n", evType)
+		}
+	}
+}
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000000000000000000000000000000000000..90817b58e185eba5f56171446559cfd3c0d582e5
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,21 @@
+{ pkgs ? import <nixpkgs> {} }:
+
+pkgs.mkShell {
+  buildInputs = [
+    pkgs.xorg.libX11
+    pkgs.go
+    pkgs.libuiohook
+    pkgs.gcc
+  ];
+
+  shellHook = ''
+    export CGO_ENABLED=1
+    export CGO_CFLAGS="-I${pkgs.xorg.libX11.dev}/include"
+    export CGO_LDFLAGS="-L${pkgs.xorg.libX11.out}/lib"
+ 
+    export CGO_CFLAGS="''${CGO_CFLAGS} -I${pkgs.libuiohook}/include"
+    export CGO_LDFLAGS="''${CGO_LDFLAGS} -L${pkgs.libuiohook}/lib"
+
+  '';
+}
+