laptopctl
laptopctl is a tiny bespoke control panel for my bespoke laptop setup.

This is the window that pops up when I press Win+C on my keyboard, short
for Control. The six buttons, each with its own keybind, control tiny matters
related to portability.
My main computer is a laptop, a gaming laptop at that, and an old gaming laptop
at that*, so power management is important.
By pressing c on my keyboard, or the upper right button, the CPU
energy bias is toggled between “performance” (0), “balanced” (7), and
“power saving”. Honestly, I don’t feel it having much of an effect, but it’s
there.
c’s more loved sibling is f, the lower right button controlling the CPU
clock frequency range. Its label shows the current frequency, flanked by the
minimum and maximum. Clicking the button or its hotkey selects between the
maximum frequencies of 1GHz, 2GHz and 4GHz – you can actually feel the
difference between them, both in performance and battery usage.
The other pair of buttons are ext1 and ext2, hotkeyed to 1 and 2. These
control the mounting of two external HDDs I use. I don’t remember why I did it
that way, but they are mounted via systemd .mount units, and this button runs
either systemctl start or systemctl stop. Deluge (the torrent client, d)
both doesn’t like being started with the files it’s seeding gone, nor does it
enjoy them being slid from under itself, and it makes unmounting impossible
because it makes the drive “busy”, so it gets a button too.
Last one is w, controlling the killswitch for Wi-Fi. It’s useful to force the
computer to use cabled Ethernet, and not distract itself with the flaky wireless.
All in all, this makes for an “unplugging sequence” of dwffcc12q, and
cf12wdq when I’m back home. Pretty convenient, especially since it was a
series of commands before.
The implementation is very simple, it’s a Python script based on tkinter that draws the buttons and runs commands when they are pressed. It’s bound to the key combination in xfce’s Keyboard settings, under Application Shortcuts. Quite simple, and reasonably effective. If you care for hacked together Python, with heavy use of subprocesses and regex parsing, here is the listing. It uses OOP and inheritance (!!), so it starts out very ugly but the final 20 lines are somewhat pretty at least.
Listing
#!/bin/env python3
import tkinter as tk
from tkinter import ttk
import subprocess
import json
import re
root = tk.Tk()
root.title("laptopctl")
root.attributes("-topmost", True)
frm = ttk.Frame(root, padding=10)
frm.focus_set()
def kbind(evt, fn):
def a(*args):
fn()
root.bind_class('.', evt, a)
kbind('q', root.destroy)
kbind('<Escape>', root.destroy)
frm.pack(expand=1)
class Button:
def state(self):
raise NotImplementedError('notimpl')
def change(self):
raise NotImplementedError('notimpl')
def __init__(self, frm, c, r, bind):
self.s = tk.StringVar()
self.s.set('...')
self.b = ttk.Button(frm, command=self.toggle, textvariable=self.s)
self.b.grid(column=c, row=r)
self.b['command'] = self.toggle
self.frm = frm
kbind(bind, self.toggle)
self.frm.after(0, self.update)
def update(self):
try:
self.s.set(self.state())
except Exception as e:
self.s.set('E:'+str(e))
self.frm.after(5000, self.update)
def toggle(self):
try:
self.change()
except Exception as e:
self.s.set('E:'+str(e))
self.frm.after(1000, self.update)
else:
self.s.set('OK')
self.frm.after(200, self.update)
class Rfkill(Button):
last = '_'
def state(self):
p = subprocess.run(
'rfkill -o SOFT -r -n list wlan'.split(' '),
capture_output=True
)
s = p.stdout.decode().strip()
self.last = s
return 'wifi: '+s
def change(self):
tab = {'blocked': 'unblock', 'unblocked': 'block'}
n = tab.get(self.last)
if n is None:
raise Exception('unk state: '+self.last)
subprocess.run(['sudo', 'rfkill', n, 'wlan'])
self.last = '_'
class SysdService(Button):
on = -1
name = 'ChangeMe'
prettyname = None
sudo = False
user = False
def state(self):
cmd = ['systemctl', 'is-active', '--user' if self.user else None, self.name]
p = subprocess.run(
filter(lambda x: x is not None, cmd),
capture_output=True
)
state = p.stdout.decode().strip()
self.on = p.returncode == 0
return (self.prettyname or self.name)+': '+state+('' if self.on else '!')
def change(self):
acts = {True: 'stop', False: 'start'}
act = acts.get(self.on)
if act is None:
return
cmd = ['pkexec' if self.sudo else None, 'systemctl', act, '--user' if self.user else None, self.name]
subprocess.Popen(
filter(lambda x: x is not None, cmd),
stdin=None, stdout=None, stderr=None, close_fds=True
)
class Cpu(Button):
last = -1
def state(self):
p = subprocess.run('sudo cpupower info -b'.split(' '),
capture_output=True)
m = re.search(r'perf-bias: (\d+)', p.stdout.decode())
if not m:
self.last = -1
return 'cpu: unk'
self.last = int(m.group(1))
desc = {0: 'perf', 7: 'bal', 15: 'pow'}
return 'cpu: %d (%s)' % (self.last, desc.get(self.last, '?'))
def change(self):
if self.last == -1:
raise Exception('unk state: '+self.last)
tab = {0: 7, 7: 15, 15: 0}
n = tab.get(self.last, 15)
subprocess.run(['sudo', 'cpupower', 'set', '-b', str(n)])
class Cpuf(Button):
last = -1
freqs = {'4.00 G': 4, '2.00 G': 2, '1.00 G': 1, '1000 M': 1}
def state(self):
p = subprocess.run('cpupower frequency-info'.split(' '),
capture_output=True)
s = p.stdout.decode()
r = re.search(r'within ([0-9.]+ .)Hz and ([0-9.]+ .)Hz', s)
mn, mx = '?', '?'
if r:
mn, mx = r.group(1), r.group(2)
self.last = self.freqs.get(mx, -1)
c = re.search(r'current CPU frequency: ([0-9.]+ .)Hz', s)
cur = '?'
if c:
cur = c.group(1)
return ':'.join([x.replace(' ', '') for x in (mn, cur, mx)])
def change(self):
tab = {4: 2, 2:1, 1:4}
n = tab.get(self.last, 2)
subprocess.run(['sudo', 'cpupower', 'frequency-set', '-u', f'{n}G'])
class Ext1(SysdService):
name = 'mnt-ext.mount'
prettyname = 'ext1'
class Ext2(SysdService):
name = 'mnt-ext2.mount'
prettyname = 'ext2'
class Deluge(SysdService):
name = 'deluge'
user = True
Rfkill(frm, 0, 0, 'w') # for wifi
Deluge(frm, 1, 0, 'd')
Cpu(frm, 2, 0, 'c')
Cpuf(frm, 2, 1, 'f')
Ext1(frm, 0, 1, '1')
Ext2(frm, 1, 1, '2')
root.mainloop()
* None of the performance, none of the battery life – a compromise so great the UN might weep.