Task

Send simulated keystrokes to a GUI window, or terminal.

You should specify whether the target may be externally created
(i.e., if the keystrokes are going to an application
other than the application that is creating them).




/* simulate_input_keyboard.wren */
 
import "random" for Random
 
var KeyPressMask    = 1 << 0
var ShiftMask       = 1 << 0
var ButtonPressMask = 1 << 2
var ExposureMask    = 1 << 15
var KeyPress        = 2
var ButtonPress     = 4
var Expose          = 12
var ClientMessage   = 33
 
var XK_q            = 0x0071
var XK_Escape       = 0xff1b
 
foreign class XGC {
    construct default(display, screenNumber) {}
}
 
// XEvent is a C union, not a struct, so we amalgamate the properties
foreign class XEvent {
    construct new() {}        // creates the union and returns a pointer to it
 
    foreign eventType         // gets type field, common to all union members
 
    foreign eventType=(et)    // sets type field
 
    foreign state=(st)        // sets xkey.state
 
    foreign keycode           // gets xkey.keycode
 
    foreign keycode=(kc)      // sets xkey.keycode
 
    foreign sameScreen=(ss)   // sets xkey.same_screen
 
    foreign dataL             // gets xclient.data.l (data is a union)
}
 
foreign class XDisplay {
    construct openDisplay(displayName) {}
 
    foreign defaultScreen()
 
    foreign rootWindow(screenNumber)
 
    foreign blackPixel(screenNumber)
 
    foreign whitePixel(screenNumber)
 
    foreign selectInput(w, eventMask)
 
    foreign mapWindow(w)
 
    foreign closeDisplay()
 
    foreign nextEvent(eventReturn)
 
    foreign createSimpleWindow(parent, x, y, width, height, borderWidth, border, background)
 
    foreign drawString(d, gc, x, y, string, length)
 
    foreign storeName(w, windowName)
 
    foreign flush()
 
    foreign internAtom(atomName, onlyIfExists)
 
    foreign setWMProtocols(w, protocols, count)
 
    foreign sendEvent(w, propogate, eventMask, eventSend)
 
    foreign destroyWindow(w)
}
 
class X {
   foreign static lookupKeysym(keyEvent, index)
 
   foreign static lookupString(eventStruct, bufferReturn, bytesBuffer, keysymReturn, statusInOut)
}
 
 /* open connection with the server */
var xd = XDisplay.openDisplay("")
if (xd == 0) {
    System.print("Cannot open display.")
    return
}
var s = xd.defaultScreen()
 
/* create window */
var w = xd.createSimpleWindow(xd.rootWindow(s), 10, 10, 350, 250, 1, xd.blackPixel(s), xd.whitePixel(s))
xd.storeName(w, "Simulate keystrokes")
 
/* select kind of events we are interested in */
xd.selectInput(w, ExposureMask | KeyPressMask | ButtonPressMask)
 
/* map (show) the window */
xd.mapWindow(w)
xd.flush()
 
/* default graphics context */
var gc = XGC.default(xd, s)
 
/* connect the close button in the window handle */
var wmDeleteWindow = xd.internAtom("WM_DELETE_WINDOW", true)
xd.setWMProtocols(w, [wmDeleteWindow], 1)
 
/* event loop */
var e = XEvent.new()
var rand = Random.new()
while (true) {
    xd.nextEvent(e)
    var et = e.eventType
    if (et == Expose) {
        /* draw or redraw the window */
        var msg1 = "Click in the window to generate"
        var msg2 = "a random key press event"
        xd.drawString(w, gc, 10, 20, msg1, msg1.count)
        xd.drawString(w, gc, 10, 35, msg2, msg2.count)
    } else if (et == ButtonPress) {
        System.print("\nButtonPress event received")
        /* manufacture a KeyPress event and send it to the window */
        var e2 = XEvent.new()
        e2.eventType = KeyPress
        e2.state = ShiftMask
        e2.keycode = 24 + rand.int(33)
        e2.sameScreen = true
        xd.sendEvent(w, true, KeyPressMask, e2)
    } else if (et == ClientMessage) {
        /* delete window event */
         if (e.dataL[0] == wmDeleteWindow) break
    } else if (et == KeyPress) {
        /* handle key press */
        System.print("\nKeyPress event received")
        System.print("> Keycode: %(e.keycode)")
        /* exit if q or escape are pressed */
        var keysym = X.lookupKeysym(e, 0)
        if (keysym == XK_q || keysym == XK_Escape) {
            break
        } else {
            var buffer = List.filled(2, 0)
            var nchars = X.lookupString(e, buffer, 2, 0, 0) // don't need the last 2 parameters
            if (nchars == 1) {
                var b = buffer[0]
                if (b < 0) b = 256 + b 
                System.print("> Latin-1: %(b)")
                if (b < 32 || (b >= 127 && b < 160)) {
                    System.print("> Key 'control character' pressed")
                } else {
                    System.print("> Key '%(String.fromByte(b))' pressed")
                }
            }
        }
    }
}
 
xd.destroyWindow(w)
 
/* close connection to server */
xd.closeDisplay()