Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
CerebroX
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Jira
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Monitor
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
OSS
Utilities
CerebroX
Commits
0b20e53f
Verified
Commit
0b20e53f
authored
5 months ago
by
Volker Schukai
Browse files
Options
Downloads
Patches
Plain Diff
feat: initial version
parent
a52ef0eb
No related branches found
No related tags found
No related merge requests found
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
main.go
+261
-0
261 additions, 0 deletions
main.go
shell.nix
+21
-0
21 additions, 0 deletions
shell.nix
with
282 additions
and
0 deletions
main.go
0 → 100644
+
261
−
0
View file @
0b20e53f
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
(
"
\n
Active 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
(
"
\n
Title 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)
}
}
}
This diff is collapsed.
Click to expand it.
shell.nix
0 → 100644
+
21
−
0
View file @
0b20e53f
{
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"
''
;
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment