Passing array as args (robotgo keytoggle)

I am trying to toggle a key with modifiers using robotgo. The function I am calling is KeyToggle; for me takes something like robotgo.KeyToggle(“a”, “up”, “alt”, “cmd”).

I have the code below that compiles and builds an array of strings, but I don’t know how to expand this in Go - I have tried using slices (as shown) but that fails at runtime. My previous approach was to call KeyToggle for each of the modifiers separately, but this is unreliable in applying the modifers

I’m hoping I’m missing some obvious way to do this. I might otherwise need to unpack the array or rely on KeyToggle allowing for empty strings :frowning: which seems a dangerous approach.
Thank you in advance - Andy

func press_release(key keyJSON) {
	modifiers := []string{}
	if key.Shift {
		modifiers = append(modifiers, "shift")
	}
	if key.Ctrl {
		modifiers = append(modifiers, "ctrl")
	}
	if key.Alt {
		modifiers = append(modifiers, "alt")
	}
	state := "up"
	if key.Press {
		state = "down"
	}
	robotgo.KeyToggle(key.Key, state, modifiers[:])
}

I spotted an earlier error using “control” and needed to use “ctrl”, which fixes my original issue.

I still don’t know how to do this using a simpler slice way - my code is now

func press_release(key keyJSON) {
	modifiers := []string{}
	if key.Shift {
		modifiers = append(modifiers, "shift")
	}
	if key.Ctrl {
		modifiers = append(modifiers, "ctrl")
	}
	if key.Alt {
		modifiers = append(modifiers, "alt")
	}
	state := "up"
	if key.Press { // press modifiers before pressing key
		state = "down"
		press_release_modifiers(modifiers, state)
	}
	robotgo.KeyToggle(key.Key, state)
	if !key.Press { // release modifiers after releasing key
		press_release_modifiers(modifiers, state)
	}
}

Where press_release_modifiers calls keyToggle for any key modifiers…

I don’t really see a problem with your code. Is it that you are trying to reduce the repetitive nature of your if key.Shift { checks? You could use reflection here. Something like this:

type keyJSON struct {
	Key   string
	Shift bool `key:"shift"`
	Ctrl  bool `key:"ctrl"`
	Alt   bool `key:"alt"`
	Press bool
}

func (k keyJSON) toKeyArgs() []string {
	// We will build up a list of modifiers based on our struct values
	modifiers := []string{}
	// Get the type and value of the instance
	v := reflect.ValueOf(k)
	t := v.Type()

	// Iterate over all available fields
	for i := 0; i < t.NumField(); i++ {
		// Get the field type info (for name and tag)
		fieldType := t.Field(i)

		// Get the field value info (for IsZero check)
		fieldValue := v.Field(i)

		// Check for the existence of our "key" tag
		keyTag := fieldType.Tag.Get("key")

		// Check if the field's value is its zero value
		isZero := fieldValue.IsZero()

		// We have a non-zero value and a key tag so add to our modifiers
		if !isZero && len(keyTag) > 0 {
			modifiers = append(modifiers, keyTag)
		}
	}
	return modifiers
}

Then to use it:

func main() {
	key := keyJSON{
		Key:   "enter",
		Shift: true,
	}
	fmt.Println(key.Key, key.toKeyArgs())
	key = keyJSON{
		Key:   "ctrl",
		Alt:   true,
		Shift: true,
	}
	fmt.Println(key.Key, key.toKeyArgs())
}
// Output:
// enter [shift]
// ctrl [shift alt]

But I don’t know if that is easier to reason about / maintain. You can run / modify this on the playground:

Sorry - I have obviously been unclear - I was trying to pass the arguments to KeyToggle in one go.

i.e. Robotgo allows this call KeyToggle('tab', 'down', 'alt') - and this will press the target and modifier virtual keys in the right order, in one call. Also ‘up’ could be passed to release in the right order.

My current working solution instead calls KeyToggle multiple times - in a different order based on whether ‘down’ or ‘up’ is passed. So the modifier keys (e.g. ‘alt’) are pressed down before the target key, or released ‘up’ after the target key is released.

But I don’t know how to get Go to compile the request to KeyToggle('tab', 'down', modifiers), where modifiers can be 0 to 3 strings.

I hope this makes a little more sense - Andy

1 Like

I was going to type something up but I’ve been busy with work this morning. But this is exactly what you need to grok to figure out your solution.

Unfortunately I can’t change the KeyToggle parameters - they are in an external package with signature of func robotgo.KeyToggle(key string, args ...interface{}) error

Below is a compiling version that runs then throws interface conversion: interface {} is []string, not string, i.e. it’s expecting a string not []string. I’m not even sure this should compile?!

I think I’m going to have to give up on this and use my less simple way - since that actually works :expressionless:

Thank you for the feedback - Andy

func press_release(key keyJSON) {
	state := "up"
	if key.Press { // press modifiers before pressing key
		state = "down"
	}
	modifiers := []string{}
	modifiers = append(modifiers, state)
	if key.Shift {
		modifiers = append(modifiers, "shift")
	}
	if key.Ctrl {
		modifiers = append(modifiers, "ctrl")
	}
	if key.Alt {
		modifiers = append(modifiers, "alt")
	}
	robotgo.KeyToggle(key.Key, modifiers)
}

I solved it - by using any (interface{}) instead of string - also had to take a slice and pass that expanded - there’s a Go playground sample here Go Playground - The Go Programming Language and my code below.

func press_release(key keyJSON) {
	modifiers := []any{} // REPLACED STRING
	state := "up"
	if key.Press {
		state = "down"
	}
	modifiers = append(modifiers, state)
	if key.Shift {
		modifiers = append(modifiers, "shift")
	}
	if key.Ctrl {
		modifiers = append(modifiers, "ctrl")
	}
	if key.Alt {
		modifiers = append(modifiers, "alt")
	}
	robotgo.KeyToggle(key.Key, modifiers[:]...) // USING SLICE EXPANDED
}

Thank you for all the feedback - I would have given up otherwise - Andy

1 Like

Why do you use reslicing with `modifiers`?

It was the only way I managed to get it to compile and run without an error :expressionless:

Reslicing is `[:]` after your slice, it re-creates ptr to underlaying data. Nothing will happen if you remove it

If you mean putting robotgo.KeyToggle(key.Key, modifiers), this fails at runtime

2025/11/12 11:24:07 http: panic serving 127.0.0.1:52146: interface conversion: interface {} is []interface {}, not string
goroutine 22 [running]:
net/http.(*conn).serve.func1()
	C:/Program Files/Go/src/net/http/server.go:1943 +0x14b
panic({0x7ff788015920?, 0xc00027aff0?})
	C:/Program Files/Go/src/runtime/panic.go:783 +0x136
github.com/go-vgo/robotgo.ToStrings({0xc0002953b8, 0x1, 0x1})
	C:/Users/Andy Stratton/go/pkg/mod/github.com/go-vgo/robotgo@v0.110.8/key.go:467 +0x245
github.com/go-vgo/robotgo.KeyToggle({0x7ff7880e76b0, 0x1}, {0xc0002953b8, 0x1, 0x1})
	C:/Users/Andy Stratton/go/pkg/mod/github.com/go-vgo/robotgo@v0.110.8/key.go:562 +0x531
quando/internal/server/devices/keyboard.press_release({{0x7ff7880e76b0, 0x1}, 0x1, 0x0, 0x1, 0x0})
	c:/Users/Andy Stratton/Dropbox/projects/quando/internal/server/devices/keyboard/keyboard.go:50 +0x425

I didn’t mean that. I said you can type modifiers… without any modifiers[:]…

1 Like

Ok - immediately realised you meant robotgo.KeyToggle(key.Key, modifiers...)

yes - this works :slight_smile:

It’s been a long journey to get to where I am now