How I turned my PS4 controller into a keyboard
Our developer Tommy Svensson has been trying to find the most comfortable way to code during Home Office.

Since going fully remote because of the virus I’ve spent countless hours trying to find the most optimal way to sit in front of the computer. I’ve tried it all. Sitting on the floor, in the sofa, lying in bed. The one thing, all of these not-at-the-desk solutions have in common, is this: the keyboard and mouse make them unergonomic and uncomfortable.
This is not a new idea I have. Imagine being able to lounge back in the sofa with a PS4 controller while at work? Sure, there are chorded keyboards out there and they may be more optimized than my DIY project. But if you wanna save your cents and use something you already have at home, look no further; I have just the solution for you!
Initially I tried creating an Electron app, but had some major issues getting the libraries to work with each other and I didn’t find the framework to be very straightforward. Then I remembered Go, the language I’ve been learning for the past two years on and off. What better language to do a desktop application in than Go!
Want to recreate what I did?
Let’s start off by making a simple “Hello world” application. I assume you’ve already installed Go. Create a file main.go
and write the following:
package mainimport "fmt"func main() {
fmt.Println("Hello world")
}
Run by typing go run main.go
in the terminal.
Next, we will need the Gobot framework to be able to read the controller inputs. The package requires SDL2, so let’s install it with:
brew install sdl2
Once that’s done, install Gobot with the following command:
go get -d -u gobot.io/x/gobot/...
Good, now we need to set up and connect our adaptor and driver. We add in some listeners for two of the buttons in a function called work
and then we make a robot by connecting it all together and finally run the Start()
method on our robot.
package mainimport (
"fmt" "gobot.io/x/gobot"
"gobot.io/x/gobot/platforms/joystick"
)func main() {
joystickAdaptor := joystick.NewAdaptor()
stick := joystick.NewDriver(joystickAdaptor, "dualshock4") work := func() {
stick.On(joystick.SquarePress, func(data interface{}) {
fmt.Println("square_press")
})
stick.On(joystick.SquarePress, func(data interface{}) {
fmt.Println("square_release")
})
stick.On(joystick.TrianglePress, func(data interface{}) {
fmt.Println("triangle_press")
})
stick.On(joystick.TriangleRelease, func(data interface{}) {
fmt.Println("triangle_release")
})
} robot := gobot.NewRobot("joystickBot",
[]gobot.Connection{joystickAdaptor},
[]gobot.Device{stick},
work,
) robot.Start()
}
If you now plug in your PlayStation DualShock 4 controller, run go run main.go
and press either square or triangle. You should receive some messages saying if you are pressing or releasing the button.
Next step is to control your keyboard with Go code. We will do this using Robotgo. Get the package with the command:
go get github.com/go-vgo/robotgo
Add it to your imports:
import (
"fmt" **"github.com/go-vgo/robotgo"**
"gobot.io/x/gobot"
"gobot.io/x/gobot/platforms/joystick"
)
Now, all we need to translate a button-press on your PS4 controller into a keyboard key-press, is this:
stick.On(joystick.SquarePress, func(data interface{}) {
robotgo.KeyTap("a")
})
Run the app and press square and you should see the letter a
being written in the command line. For macOS users, you may have to go into your privacy settings/accessibility and allow the terminal to control your computer.
There is also another method robotgo.TypeStr()
. If you use this, you can put in full strings and it will command your computer to write these out, which will come in handy later as you will see. I've actually chosen to use this method for everything except keys like shift
, alt
, enter
, etc. The reason being, that if I wanna use shift to make uppercase letters, I need to transform the original string with the letter into uppercase using strings.toUpper()
. And since the a
key is always lowercase, robotgo.KeyTap("A")
won't work.
Let’s make our app a little more sophisticated. We want to be able to use the L2 and R2 triggers on our PS4 controller as modifiers so that we can write different letters depending on which trigger is held. We also want to use our L1 trigger as shift
so that we can write in uppercase.
We’ll start by creating a Mods
struct above the main function. It contains bools, that will determine, if the triggers are held down or not. We'll call L1 caps
since that will be its only functionality.
type Mods struct {
l2 bool
r2 bool
caps bool
}
Next, we add a OutputKeyboard
method on it, that checks, which mods are held down, and gives the appropriate key.
First, import the strings package:
import (
"fmt"
**"strings"** "github.com/go-vgo/robotgo"
"gobot.io/x/gobot"
"gobot.io/x/gobot/platforms/joystick"
)func (m *Mods) OutputKeyboard(l2 string, r2 string, noMod string) {
if m.caps {
l2 = strings.ToUpper(l2)
r2 = strings.ToUpper(r2)
noMod = strings.ToUpper(noMod)
} if m.l2 {
robotgo.TypeStr(l2)
return
}
if m.r2 {
robotgo.TypeStr(r2)
return
}
robotgo.TypeStr(noMod)
}
Create a new reference to it in a variable inside main()
:
mods := &Mods{false, false, false}
Next we’ll listen to trigger presses and releases to set the mods to true or false. Add to the work
function inside main()
:
stick.On(joystick.L2Press, func(data interface{}) {
mods.l2 = true
})
stick.On(joystick.L2Release, func(data interface{}) {
mods.l2 = false
})
stick.On(joystick.R2Press, func(data interface{}) {
mods.r2 = true
})
stick.On(joystick.R2Release, func(data interface{}) {
mods.r2 = false
})
stick.On(joystick.L1Press, func(data interface{}) {
mods.caps = true
})
stick.On(joystick.L1Release, func(data interface{}) {
mods.caps = false
})
Finally we react to the button releases themselves. Replace what you have in the work
function with this:
stick.On(joystick.XRelease, func(data interface{}) {
mods.OutputKeyboard("e", "i", "a")
})
stick.On(joystick.SquareRelease, func(data interface{}) {
mods.TypeString("f", "j", "b")
})
stick.On(joystick.TriangleRelease, func(data interface{}) {
mods.TypeString("g", "k", "c")
})
stick.On(joystick.CircleRelease, func(data interface{}) {
mods.TypeString("h", "l", "d")
})
Now you should be able to write the letter “a” through l on your PS4 controller. The finished code should look like below. Happy typing :)
package mainimport (
"strings" "github.com/go-vgo/robotgo"
"gobot.io/x/gobot"
"gobot.io/x/gobot/platforms/joystick"
)type Mods struct {
l2 bool
r2 bool
caps bool
}func (m *Mods) OutputKeyboard(l2 string, r2 string, noMod string) {
if m.caps {
l2 = strings.ToUpper(l2)
r2 = strings.ToUpper(r2)
noMod = strings.ToUpper(noMod)
} if m.l2 {
robotgo.TypeStr(l2)
return
}
if m.r2 {
robotgo.TypeStr(r2)
return
}
robotgo.TypeStr(noMod)
}func main() {
joystickAdaptor := joystick.NewAdaptor()
stick := joystick.NewDriver(joystickAdaptor, "dualshock4") mods := &Mods{false, false, false} work := func() {
stick.On(joystick.XRelease, func(data interface{}) {
mods.OutputKeyboard("e", "i", "a")
})
stick.On(joystick.SquareRelease, func(data interface{}) {
mods.OutputKeyboard("f", "j", "b")
})
stick.On(joystick.TriangleRelease, func(data interface{}) {
mods.OutputKeyboard("g", "k", "c")
})
stick.On(joystick.CircleRelease, func(data interface{}) {
mods.OutputKeyboard("h", "l", "d")
}) stick.On(joystick.L2Press, func(data interface{}) {
mods.l2 = true
})
stick.On(joystick.L2Release, func(data interface{}) {
mods.l2 = false
})
stick.On(joystick.R2Press, func(data interface{}) {
mods.r2 = true
})
stick.On(joystick.R2Release, func(data interface{}) {
mods.r2 = false
})
stick.On(joystick.L1Press, func(data interface{}) {
mods.caps = true
})
stick.On(joystick.L1Release, func(data interface{}) {
mods.caps = false
})
} robot := gobot.NewRobot("joystickBot",
[]gobot.Connection{joystickAdaptor},
[]gobot.Device{stick},
work,
) robot.Start()
}
(NOTE: At the time of this writing, at least my DualShock 4 controller’s buttons do not correspond with the methods of Gobot’s joystick package. I have sent in a pull request to Gobot to fix this, but it might not be fixed when you read this article. If not, you will have to do some trial and error and find out which button corresponds to which joystick method.)
Choosing your buttons
Choosing which buttons should correspond to which letters was anything but easy, and as I’m writing out this sentence on my gamepad, I look back at the many times I scrapped the bindings I had and had to relearn what little motor memory I had gathered.
My first version made everything much easier to remember, but it also made it very hard to type fast. It scrolled through the alphabet as you held down buttons. Another downside was that you had to look at the terminal to see which letter you were currently on.
Another method I tried was having helper words with unique letters laid out at different parts of the gamepad, but that didn’t make me really memorize the letters. I just memorized the words themselves.
I finally landed on making the most frequent letters the most accessible (meaning no mods or buttons that are hard to reach) and then simply lay them out on random buttons. The plan is to first learn a few really well, then adding a few more and keep going until I’ve learned the whole alphabet. To determine which letters are most common in English text, I used a list made by Samuel Morse (1791–1982) that he used to make Morse Code.
I’m still very slow but I’m getting there. Had to write my own little quiz program to test my skills:

My next idea is to have different button combinations generate certain keywords that pop up frequently in JavaScript, such as:
const, let, function, () =>, return, map, forEach, filter, reduce, some, any, find, findIndex
My hope is that once I get over the hurdle of writing out words with the gamepad, these types of commands will make me work even faster than I could on a keyboard. The sky’s the limit!
To follow my progress and get some ideas of button combinations that can be used, please check out my project on Github:
Tommy is one of our most versatile engineers, spanning both frontend and backend development, always dabbling with new tech and new programming languages.
diesdas.digital is a studio for strategy, design and code in Berlin, featuring a multidisciplinary team of designers, developers and strategists. We create tailor-made digital solutions with an agile mindset and a smile on our faces. Let’s work together!
Curious to learn more? We’re also on Twitter, Facebook, Instagram and we highly recommend our Tumblr. You could also subscribe to this publication to get notified when new posts are published! That’s all. Over & out! 🛰