TLDR: An Electron app pops up a window periodically reminding me to rest my eyes. It’s controlled with a systemd unit.
Recently I’ve been getting screen related headaches.
Some solutions to this problem via American Academy of Ophthalmology (AAO):
A solution that the Academy likes less:
To make the head hurts go away I’ve started reminding myself to blink, using eye drops, and adjusting my screen brightness to match ambient lighting.
Now I want to remind myself to follow the “20-20-20” rule.
I’ll do this by popping up a full-screen window every twenty minutes reminding me to rest my eyes. The window will be a small app for Electron
- a framework for developing GUI’s using Javascript.
The app is called catnap
and will eventually feature pixelated images of napping cats. Here we’ll just focus on a bare-bones version with some text.
My project structure:
/catnap/
- /app/ (contains Electron app files)
- /bin/ (contains a Bash script to periodically run the Electron app)
- /etc/ (contains a systemd unit file to run the Bash script and restart it when it fails)
- .gitignore
Setup the directory structure and track with Git (mkdir
options -p
to create parent directories even if they don’t exist and -v
to tell us what’s happening):
mkdir -pv catnap/app catnap/bin catnap/etc && git init && touch .gitignore && echo "app/node_modules" > .gitignore
Electron is a minimal Chromium
browser that you can control using Javascript
.
Chromium
is part of the source code for the Chrome
browser - it includes user interface code, the Blink
rendering engine, and the V8 Javascript engine
.
Electron uses Chromium
to create a web browser that mimics traditional desktop applications. You can use it to present users with desktop Graphical User Interfaces (GUIs)
that you develop using web technologies (Javascsript
, HTML
, CSS
) rather than the system’s languages (C/C++/Ojective C/Visual Basic/etc
).
An Electron application is setup like a Node.js
application but it runs a local browser instead of a web server.
The material in Writing Your First Electron App Guide is mostly sufficient for catnap
. All we need to do is pop up a window and display some HTML.
Inside the app
directory, create a package.json
:
{
"name": "catnap",
"version": "1.0.0",
"description": "An app for resting your eyes and napping with cats",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"repository": "https://github.com/MerkleBros/catnap",
"author": "Patrick McCarver",
"devDependencies": {},
"dependencies": {
"electron": "^8.2.3"
}
}
Electron looks at main:
in package.json
to decide how to start the app. If main
is not provided then it will also try to find and run an index.js
just like Node.js
does.
app/main.js
is below:
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
const filePath = path.join('file://', __dirname, './index.html')
let win = new BrowserWindow({ frame: false, fullscreen: true })
win.on('close', () => { win = null })
win.loadURL(filePath)
win.show()
return win;
}
function start() {
let win = createWindow();
setTimeout(() => {
win.close()
}, 20000)
}
app.whenReady().then(start)
Electron provides the BrowserWindow
object for opening new browser windows. The createWindow()
function finds the index.html
file to put in the new window, initializes the browser window (fullscreen and without a frame, meaning no toolbars, URL bar, etc), sets up a close
listener, loads the HTML into the browser window, and finally shows the window.
The start()
function is called after the app is finished initializing. It calls createWindow()
and then waits 20 seconds before closing the window.
The HTML that the window will show is in app/index.html
:
<html>
<body style="background-color: slategrey">
<div
style="color: white; font-size: 3.5vw; position: absolute; top: 50%; left: 50%;
transform: translate(-50%,-50%);">
Rest your eyes awhile.
</div>
</body>
</html>
Install Electron with npm install
and run the app using npm start
. A full-screen window will pop up for twenty seconds and then close:
We can use a Bash script to run the app periodically.
In catnap/bin
run touch catnap.sh && chmod u+x catnap.sh
to create a Bash script and make it executable.
Inside of catnap.sh
, run a while loop that sleeps for twenty minutes (1200 seconds) and then launches the Electron app. Be sure to update the cd
path to where your app is:
#! /usr/bin/env bash
set -Ceuo pipefail
while true
do
(cd "$HOME/recurse/catnap/app/" && npm start)
sleep 1200;
done
Run the script with ./catnap.sh
. The Electron app will open again, and after it closes you’ll see that the process is still running (and taking up the terminal). If we waited twenty minutes it would open the Electron app again.
Let’s run the process as a daemon
- a background process - using systemctl
(the systemd
service controller). On Linux the systemd
tool is used to manage processes on system startup and afterwards. Here’s another blog post explaining systemd.
We’ll create a user service
unit to manage the Bash script we just wrote. It will restart the script if it fails, and we can enable the script to run on startup under the systemd target default.target
. Place the service
file in catnap/etc/catnapd.service
(catnapd
for catnap daemon
).
[Unit]
Description=Run the catnap app
After=default.target
[Service]
Restart=on-failure
RemainAfterExit=yes
ExecStart=/home/patrick/recurse/catnap/bin/catnap.sh
[Install]
WantedBy=default.target
Update ExcStart=
to the path where you saved your catnap.sh
script:
User services for systemd are stored in ~/.config/systemd/user/
.
Copy catnapd.service
there (cp catnapd.service ~/.config/systemd/user/
) and run systemctl --user daemon-reload
to have systemd
find the service.
Run the service using systemctl --user start catnapd
and the Electron app should start again. Enable the service to start automatically on startup using systemctl --user enable catnapd
.
Now catnap.sh
is running in the background and managed by systemd.
You can stop the service any time using systemctl --user stop catnapd
and disable it from running on startup using systemctl --user disable catnapd
.
Now we have a minimal Electron app to remind us to relax our eyes, a Bash script to launch that app every twenty minutes, and a systemd unit to start/stop the Bash script and enable/disable it on system startup.
Next time we’ll add configuration files for user preferences like how often and how long to rest our eyes, track some state like total cat naps taken, and add an override to close the window early in case we’re doing something important.