Control Computer from mobile using Node JS
I am a lazy f***, and sometimes, while working on my laptop, I don’t feel like stretching my hand aaaaaallllll the way to the laptop. I use an app on my phone called KDE Connect. This is a pretty powerful app. It has a counterpart for the desktop and the android phone, and through it, you can control any device running KDE Connect in the same network. You can send files, run commands, control keyboard and mouse, control media player, sync notifications, share clipboard and what not! Do check it out!!
Today I want to try if I can write a similar software or not. Of course this is not going to be as feature rich as KDE Connect. I will just create a server which will run on my lappy, and through the web browser in my phone, I will be able to talk to the server – send keystrokes and control mouse.
First, let’s start with the easy one – sending keystrokes. Here’s the complete flow –
- The server listens on a port on the lappy.
- My phone connects to the server and asks for pairing.
- I authorize the pairing on the laptop. This is to prevent any random device in my network to send keystrokes.
- Server will establish a session with my phone. This is to prevent any other authorized device to randomly stroke my laptop in between. The server will check every 30 seconds for a ping from my phone. If there’s no ping in 30 seconds, it will assume the session is closed.
- My phone will send a keystroke, and the server will capture it, and emulate pressing the key on the laptop.
Let’s start!
For the server, I’ll use Node JS. And to emulate the keypress, I’ll use this package.
Setting Up
First create the directory and move into it –
And initialize npm –
Press enter through all the questions.
Next, install the required packages –
Write the code
Create a file called index.js, and write the below code –
Save it, and run it by
node index.js
You should see a message server up!!
. Let’s test it out. Open another terminal and run this command –
You should get a response pong
Now, let’s go to the real coding!
We will have four routes –
- /pair – to pair the devices
- /start – to start the session
- /key – to send the keystroke
- /ping – to send a ping every 30 seconds to keep the connection alive.
Let’s start with the first one. A post request to /pair
will produce a token and send it to the client. The client must include the token in subsequent requests. The token will be stored in the server to check for authentication. When a request comes, the server will show a notification to the user and according to the users choice, will accept or reject the notification.
Here’s a catch. For sending notifications, I will be using the freedesktop-notifications library on Linux. If you’re using Windows or Mac, you might want to change this part.
First we’ll install the library –
npm install --save freedesktop-notifications
Then in index.js
, we’ll require the libraries –
const crypto = require('crypto');
const notification = require('freedesktop-notifications');
crypto
will be used to generate a random token.
Then we need to keep a track of tokens. So we create an array of generated tokens –
const authorized_tokens = [];
And then add a new route for /pair
–
Let’s break it down!
We are first creating a boolean handled
to keep track of if the notification was handled or not. The reason will be clear soon.
Then we are creating the notification and using the name
parameter from the request body to show the notification. There are 2 (actually 3) actions –
- ok: We are naming it accept
- nope: We are naming it reject
- default: This will be emitted when the user clicks the notification itself. We will count this as rejection, because we are a-holes.
The action
event will be emitted if the user performs any of the three actions. If the user clicks the reject button or the notification itself, we are sending a response to the client letting them know about the rejection. We are also setting handled = true
.
If the user clicks accept, we are generating a random 16 bytes hex string, and checking if it’s present already in the array or not. Although the probability is pretty low, still, I am a mathematician, and “pretty low” for me doesn’t mean “never going to happen.” So we keep trying until we find a unique token. We put that in the array and send the same to the client. Also handled = true
Now the closed event is little wonky. If the user doesn’t click anything, but lets the notification expire, we’ll count that as rejection too, because we are a-holes.
The close
event will be emitted even if the notification is closed as a result of user clicking the button. This is where handled
comes in. If the notification is closed by timeout, or the user but handled
is false (which means user cancelled the notification), we got a rejection!!
Finally we are pushing the notification.
Let’s try.
Run the server as before, and run this command –
You should see a notification

If you click the accept button, you should see a response –
{"token":"d88f9175a422f137f12299d3cba86311","rejected":false}%
And if you click the reject button, or the close button, or let it expire, you’ll see a response –
{"token":null,"rejected":true}
Yay! That’s one down!
Here’s the whole of index.js
at this point –
The next route – /start
is pretty easy! The client just needs to send the token to the server and a session will be established. There are three things to check –
- Is a session already running? If yes, reject.
- Is the sent token valid? If not reject?
- We also need to store the token which currently is holding a session.
We create two variables –
And here’s the code for the /start
route –
This code is pretty obvious. We are just performing the 3 steps.
Let’s test it! First start the server and get a token by accepting the notification as stated earlier and copy the token.
This is the command we will use –
First try with an unauthorized token. You should get a response –
{"success":null,"error":"Not authorized"}
Then try with the valid token –
{"success":"Session established","error":null}
And now if you try again –
{"success":null,"error":"Already in a session"}
Beautiful! Now let’s move to the ping route. But first, we haven’t added the timeout in session yet. So let’s do that.
First we will create a timer object –
Then we modify the /start
route by adding the timeout after we establish a session –
This starts a timer to execute 30 seconds from the time we start the session. After 30 seconds, we close the session by setting in_session = false
and clearing current_session
value.
Let’s try. First establish a session as before, and wait for 30 seconds and you should see a message like –
Session with de179f5044c2decea5b583945df8b7d1 closed
And you’ll be able to start a session again!
Now, the ping route. This will be easy too. We will –
- Check if a session is running or not.
- Check if the token parameter matches with the current_session value or not.
- If it matches, reset the timer.
Here’s the code –
This one’s pretty easy too. Once we know the token matches, we are clearing the timeout and resetting it.
You should be able to try that out by establishing a session and hitting the /ping
route with your token.
Now let’s move to the real part!
The /key
route will be simple too. We will –
- First check if the token matches with
current_session
or not. - If it matches, we will take the
key
paramter, which should be a character. If it’s a string, we will take the first character because we are a-holes. - Then we send the keystroke.
Here’s the code –
It’s pretty self explanatory. So I’m not wasting time explaining it. Let’s move to the testing!
One thing you should remember, the sendLetter
method converts the letters into their corresponding keycode according to the keyboard layout. So if yur phone has some keys that your computer does not, you won’t be able to type that key. For example, I can’t type the British Pound symbol.
Quickly, pair, start session and run this command –
Replace the token with yours.
If you did correctly, you’ll see the A getting written in the terminal –

See the ‘A’ at the beginning? That was echoed by the server!
Here’s the entire file –
Writing the interface
We are almost done, now we just need to write the HTML interface where we will be inputting from our phone.
It will be simple. The interface will have –
- A text input to put the name.
- A button to send the pairing request.
- A textbox to write the inputs
In the script side, we will –
- Send the pair request.
- If accepted, start a session.
- Send whatever the user types
Create a file called index.html
and paste the following –
I know, it’s ugly as f***, but in later parts of this guide (if I write one, that is), we’ll make it into an app 😉
We’re also using JQuery because we’re not barbarians. Also, we’re currently just logging in the errors. We’ll later handle them.
The code is easy. We pair, and then start a session, and ping every 20 seconds to keep it alive.
When the input
event occurs on the textarea, we are taking its value and the last character would be our key which we send to the server.
Now we need a way to serve this file. We will serve this file from the /
route
Here's the full file in all its glory
Let’s test. First run this command to find out your computer’s internal IP address –
You’ll get an output like this –
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp2s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
link/ether 68:f7:28:eb:62:88 brd ff:ff:ff:ff:ff:ff
3: wlp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether e4:f8:9c:10:2b:79 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.100/24 brd 192.168.0.255 scope global dynamic noprefixroute wlp3s0
valid_lft 6398sec preferred_lft 6398sec
inet6 fe80::28c2:db52:8d85:21e/64 scope link noprefixroute
valid_lft forever preferred_lft forever
This will be different for you, but you’ll have to identify the common network to which both your computer and mobile is connected. For me it’s the Wi-Fi, which corresponds to the wlp3s0
interface, and the IP address is 192.168.0.100 (after inet). So I openthe browser on my phone and go to 192.168.0.100:3000 and I see the HTML page

Next I write the name, and click “Pair” and I get a notification on my laptop. I click Accept, and start writing in the textarea, and I get the output on my laptop. Neat, isn’t it?
(Hopefully) in the next issue, we will handle mouse events too! Or probably I’ll make it into an app.