24 April 2026
Controlling Tuxedo Laptop Power Profiles Without the GUI
How I replaced the Tuxedo Control Center desktop app with a small Bash script and a keyboard shortcut.
I run a Tuxedo laptop as my daily driver, and Tuxedo ships their own daemon, Tuxedo Control Center (tccd), for managing things like fan curves, power limits, and CPU frequency profiles. The problem: the only way to switch profiles is through a full desktop GUI application. Every time I wanted to drop into power-save mode for a meeting or ramp up to performance mode for a compile job, I had to open a window, click around, and close it.
That bothered me enough that I wrote TCC Bridge: a small Bash wrapper that exposes profile switching on the command line, and more usefully, via a keyboard shortcut in any window manager.
How tccd exposes its API
The daemon registers a D-Bus service at com.tuxedocomputers.tccd on the system bus. D-Bus is Linux's standard IPC mechanism: the same bus your notifications, network manager, and Bluetooth stack talk over. Services on D-Bus expose interfaces with methods you can call, and tccd is no different.
You can introspect the whole interface with gdbus:
gdbus introspect \
--system \
--dest com.tuxedocomputers.tccd \
--object-path /com/tuxedocomputers/tccd
In the output you'll find methods like GetProfilesJSON, GetActiveProfileJSON, and SetTempProfileById — everything you need without touching the GUI.

The wrapper
Since every D-Bus call follows the same pattern, a small helper avoids repetition and strips the shell-escaping noise from gdbus output:
BUS_ARGS="--system --dest com.tuxedocomputers.tccd --object-path /com/tuxedocomputers/tccd --method com.tuxedocomputers.tccd"
call_tcc() {
gdbus call $BUS_ARGS."$1" | sed "s/^('//;s/',)$//"
}
Listing profiles is then just a jq query on the JSON GetProfilesJSON returns:
list_profiles() {
local json=$(call_tcc "GetProfilesJSON")
echo "ID | Name"
echo "---|---"
echo "$json" | jq -r '.[] | "\(.id) | \(.name)"'
}
Switching is done with SetTempProfileById. The function also validates the switch succeeded and fires a desktop notification:
set_profile() {
local target_id="$1"
local all_json=$(call_tcc "GetProfilesJSON")
local target_name=$(echo "$all_json" | jq -r --arg id "$target_id" '.[] | select(.id == $id) | .name')
gdbus call $BUS_ARGS.SetTempProfileById "$target_id" > /dev/null
local active_name=$(call_tcc "GetActiveProfileJSON" | jq -r '.name')
if [[ "$active_name" != "$target_name" ]]; then
echo "Failed to switch profiles. Current profile is still: $active_name"
exit 1
fi
if command -v notify-send &> /dev/null; then
notify-send "Tuxedo Control Center" "Switched to profile: $target_name"
fi
}
The --next command (which I bind to a key) reads the current active profile ID, finds its position in the list, and wraps to the next one:
next_profile() {
local current_id=$(call_tcc "GetActiveProfileJSON" | jq -r '.id')
local all_json=$(call_tcc "GetProfilesJSON")
mapfile -t ids < <(echo "$all_json" | jq -r '.[].id')
for i in "${!ids[@]}"; do
if [[ "${ids[$i]}" == "$current_id" ]]; then
next_idx=$(( (i + 1) % ${#ids[@]} ))
set_profile "${ids[$next_idx]}"
return
fi
done
}
Profile IDs (not names) are used for the cycling logic — names can contain spaces, IDs don't. mapfile reads them cleanly into an array without any word-splitting headaches.
Binding to a key
In KDE, open System Settings → Shortcuts → Custom Shortcuts, create a new command shortcut, and point it at the script:
~/.local/bin/tuxedo_profile_control.sh --next
One keypress, a notification pops up for two seconds, done. No window, no mouse.
The same approach works in any environment that supports custom keyboard shortcuts bound to shell commands — Hyprland, Sway, i3, GNOME, and so on.
The script also supports --list to see all profiles, --current to show which one is active, and --set <ID> to switch to a specific profile directly.



What I learned
I knew D-Bus existed but had never actually used it directly before this project. A few things that surprised me:
- The
gdbusinterface is refreshingly straightforward once you know how to introspect the service tree - D-Bus type signatures are a bit arcane (you'll see things like
(a{sv})in method signatures) but you only need to handle the specific calls you care about - The system bus vs session bus distinction matters:
tccdruns as root and exposes itself on the system bus, not the per-user session bus
Sometimes the right answer is a shell script.
Source on GitHub.
// comments