Skip to content

Instantly share code, notes, and snippets.

@SCP002
Last active March 15, 2022 22:02
Show Gist options
  • Save SCP002/1b7fd91a519a2dc60fc5b179f90472b6 to your computer and use it in GitHub Desktop.
Save SCP002/1b7fd91a519a2dc60fc5b179f90472b6 to your computer and use it in GitHub Desktop.
Golang: Write a message to the current (see comments for a method for any console) console's input on Windows.
// Used in https://github.com/SCP002/terminator.
// For POSIX, see: https://gist.github.com/SCP002/c7c3bf4aafd3e32e0dc0aa65dda2bf14.
package main
import (
"errors"
"os"
"unsafe"
"golang.org/x/sys/windows"
)
// Windows types.
//
// Inspired by https://github.com/Azure/go-ansiterm/blob/master/winterm/api.go.
type (
// https://docs.microsoft.com/en-us/windows/console/input-record-str.
inputRecord struct {
eventType uint16
keyEvent keyEventRecord
}
// https://docs.microsoft.com/en-us/windows/console/key-event-record-str.
keyEventRecord struct {
keyDown int32
repeatCount uint16
virtualKeyCode uint16
virtualScanCode uint16
unicodeChar uint16
controlKeyState uint32
}
)
// Windows constants.
const (
// https://docs.microsoft.com/en-us/windows/console/input-record-str#members.
keyEvent uint16 = 0x0001
)
func main() {
err := Write("Hello, World!\r\n")
if err != nil {
panic(err)
}
}
// Write writes a message to the current console's input.
//
// To write to other console, see the implementation in:
//
// https://github.com/SCP002/terminator/blob/main/internal/proxy/answer/send.go.
func Write(msg string) error {
k32 := windows.NewLazyDLL("kernel32.dll")
inpRecList, err := StrToInputRecords(msg)
if err != nil {
return errors.New("Failed to convert string message to an array of inputRecord.")
}
k32Proc := k32.NewProc("WriteConsoleInputW")
var written uint32 = 0
var toWrite uint32 = uint32(len(inpRecList))
r1, _, _ := k32Proc.Call(
os.Stdin.Fd(),
// Actually passing the whole slice. Must be [0] due the way syscall works.
uintptr(unsafe.Pointer(&inpRecList[0])),
uintptr(toWrite),
// A pointer to the number of input records actually written. Not using it (placeholder).
uintptr(unsafe.Pointer(&written)),
)
if r1 == 0 {
return errors.New("Failed to write an array of inputRecord to the current console's input.")
}
return nil
}
// StrToInputRecords converts a string into a slice of inputRecord, see:
//
// https://docs.microsoft.com/en-us/windows/console/input-record-str.
func StrToInputRecords(msg string) ([]inputRecord, error) {
records := []inputRecord{}
utf16chars, err := windows.UTF16FromString(msg)
if err != nil {
return records, err
}
for _, char := range utf16chars {
record := inputRecord{
eventType: keyEvent,
keyEvent: keyEventRecord{
// 1 = TRUE, the key is pressed. Can omit key release events.
keyDown: 1,
repeatCount: 1,
virtualKeyCode: 0,
virtualScanCode: 0,
unicodeChar: char,
controlKeyState: 0,
},
}
records = append(records, record)
}
return records, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment