Skip to content

Commit

Permalink
Auto-type hotkeys only work inside Starbase
Browse files Browse the repository at this point in the history
  • Loading branch information
dbaumgarten committed Jan 4, 2021
1 parent d83340b commit f449369
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 11 deletions.
3 changes: 2 additions & 1 deletion docs/vscode-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ One key-problem when writing yolol-code in an external editor always was how to

***NOTE2***: Make sure to you really have focussed the chip when using the hotkey, otherwise vscode-yolol will send random keystrokes to the game, making your character do all sorts of weird stuff.

***NOTE3***: The hotkeys only work when a window called "Starbase" has the focus. This way the hotkeys shoul (in theory) not interfere with other applications.

If you (for whatever reason) want to disable the global hotkeys, you can do so in the vscode-settings under File->Preferences->Settings->search for 'yolol'->Hotkeys: Enable). Vscode needs to be restarted for changes to this setting to take effect.

## Inseting code into a chip
Open the .yolol-script you want to insert in vscode (it has to be the current active file). Go to Stabase's window and open the yolol-chip you want to fill. Unlock it, aim your cursor at it and click a line. Now press ```Ctrl+I```. Vscode will start to auto-type your code into the chip, starting at your current cursor-position.


## Erasing a chip
The auto-typing only works properly when the lines of the chip are empty before inserting code. Vscode-yolol can also automate this for you. Click a line on your chip and press ```Ctrl+D```. This will end the key-strokes Ctrl+A, Entf, Down 20 times, resulting in an empty chip, starting at the line you clicked. (If you clicked line 1, the chip is now completely empty)

Expand Down
32 changes: 29 additions & 3 deletions pkg/langserver/autotype_win.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
package langserver

import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"

"github.com/dbaumgarten/yodk/pkg/langserver/win32"
Expand Down Expand Up @@ -37,13 +39,37 @@ const typeDelay = 40 * time.Millisecond
// ListenForHotkeys listens for global hotkeys and dispatches the registered actions
func (ls *LangServer) ListenForHotkeys() {
go func() {
err := win32.ListenForHotkeys(nil, ls.hotkeyHandler, AutotypeHotkey, AutodeleteHotkey, AutooverwriteHotkey)
if err != nil {
fmt.Fprintf(os.Stderr, "Error when registering hotkeys: %s", err)
currentWindow := win32.GetForegroundWindow()
wg := sync.WaitGroup{}
var cancelHotkeyListening context.CancelFunc
var hotkeysRegistered = false
for {
if isStarbaseWindow(currentWindow) && !hotkeysRegistered {
ctx := context.Background()
ctx, cancelHotkeyListening = context.WithCancel(ctx)
hotkeysRegistered = true
go func() {
wg.Add(1)
err := win32.ListenForHotkeys(ctx, ls.hotkeyHandler, AutotypeHotkey, AutodeleteHotkey, AutooverwriteHotkey)
if err != nil {
fmt.Fprintf(os.Stderr, "Error when registering hotkeys: %s", err)
}
wg.Done()
}()
} else if hotkeysRegistered {
cancelHotkeyListening()
wg.Wait()
hotkeysRegistered = false
}
currentWindow = win32.WaitForWindowChange(nil)
}
}()
}

func isStarbaseWindow(name string) bool {
return name == "Starbase"
}

func (ls *LangServer) hotkeyHandler(hk win32.Hotkey) {
win32.SendInput(win32.KeyUpInput(win32.KeycodeCtrl))
win32.SendInput(win32.KeyUpInput(uint16(hk.KeyCode)))
Expand Down
41 changes: 34 additions & 7 deletions pkg/langserver/win32/hotkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"fmt"
"runtime"
"time"
"unsafe"
)

Expand All @@ -21,8 +22,9 @@ const (
)

var (
reghotkey = user32.MustFindProc("RegisterHotKey")
getmsg = user32.MustFindProc("GetMessageW")
reghotkey = user32.MustFindProc("RegisterHotKey")
unreghotkey = user32.MustFindProc("UnregisterHotKey")
getmsg = user32.MustFindProc("PeekMessageW")
)

// Hotkey represents a key-combination pressed by a user
Expand Down Expand Up @@ -66,6 +68,22 @@ type msg struct {
POINT struct{ X, Y int64 }
}

func registerHotkey(hk *Hotkey) error {
r1, _, err := reghotkey.Call(0, uintptr(hk.ID), uintptr(hk.Modifiers), uintptr(hk.KeyCode))
if r1 == 0 {
return err
}
return nil
}

func unregisterHotkey(hk *Hotkey) error {
r1, _, err := unreghotkey.Call(0, uintptr(hk.ID))
if r1 == 0 {
return err
}
return nil
}

// ListenForHotkeys registers an listens for the given global Hotkeys. If a hotkey is pressed, the hendler function is executed
// This function blocks, so it shoue have it's own goroutine
func ListenForHotkeys(ctx context.Context, handler HotkeyHandler, hotkeys ...*Hotkey) error {
Expand All @@ -74,21 +92,29 @@ func ListenForHotkeys(ctx context.Context, handler HotkeyHandler, hotkeys ...*Ho
defer runtime.UnlockOSThread()

hotkeymap := make(map[int16]*Hotkey)

// unregister all hotkeys when exiting
defer func() {
for _, v := range hotkeymap {
unregisterHotkey(v)
}
}()

// register all requested hotkeys
for _, v := range hotkeys {
hotkeymap[int16(v.ID)] = v
r1, _, err := reghotkey.Call(
0, uintptr(v.ID), uintptr(v.Modifiers), uintptr(v.KeyCode))
if r1 != 1 {
err := registerHotkey(v)
if err != nil {
return err
}
hotkeymap[int16(v.ID)] = v
}

for {
if ctx != nil && ctx.Err() != nil {
return nil
}
var msg = &msg{}
getmsg.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
getmsg.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0, 1)

// Registered id is in the WPARAM field:
if id := msg.WPARAM; id != 0 {
Expand All @@ -97,5 +123,6 @@ func ListenForHotkeys(ctx context.Context, handler HotkeyHandler, hotkeys ...*Ho
handler(*hk)
}
}
time.Sleep(50 * time.Millisecond)
}
}
46 changes: 46 additions & 0 deletions pkg/langserver/win32/window.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// +build windows

package win32

import (
"context"
"syscall"
"time"
"unsafe"
)

var (
forgrWindow = user32.MustFindProc("GetForegroundWindow")
windowText = user32.MustFindProc("GetWindowTextW")
windowTextLen = user32.MustFindProc("GetWindowTextLengthW")
)

// GetForegroundWindow returns the name of the window that currently has the user-focus
func GetForegroundWindow() string {
hwnd, _, _ := forgrWindow.Call()

if hwnd != 0 {
textlen, _, _ := windowTextLen.Call(hwnd)
buf := make([]uint16, textlen+1)
windowText.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), uintptr(textlen+1))
return syscall.UTF16ToString(buf)
}

return ""
}

// WaitForWindowChange blocks until the window with the user-focus changes (or ctx is cancelled). Returns the title of the new active window
func WaitForWindowChange(ctx context.Context) string {
oldWindow := GetForegroundWindow()
for {
time.Sleep(500 * time.Millisecond)
newWindow := GetForegroundWindow()
if newWindow != oldWindow {
return newWindow
}
if ctx != nil && ctx.Err() != nil {
return oldWindow
}

}
}

0 comments on commit f449369

Please sign in to comment.