Macro keyboard, Using a micro computer for second monitor - part 3
09 Oct 2023USB Switch and Barrier works great. But some times you just need to do some quick change, like volume or play/pause or next song etc. To not interrupt my current workflow or gaming session on my main computer I bought a 12 key with 2 rotary encoders macro keyboard.
The Macro keyboard
The macro keyboard features 12 programmable buttons and two rotary encoders with buttons. The rotary encoders will emulate a button press on each “click” when you turn the knob. You can bind one key press for each directon and a third key press for pushing down the knob.
There is a lot of different sellers of the same type of macro keyboard on Amazon. It was delivered in a no-name box and small folder with a QR code to download software, which did not work..
When connected to a Linux machine it’s identified as
Bus 001 Device 002: ID 1189:8890 Acer Communications & Multimedia CH57x
It will not work out of the box because the buttons are not programmed from factory.
Programming the Macro keyboard, (windows)
Unfortunately you have to use Windows to program the macro keyboard. But the programming will be stored in the device and will not require any software to use onced it is programmed. So you can program the device once to use some wierd hotkey combination you would not normally use. This way you can bind that wierd hotkey combination in your Desktop Environment. You can also program the buttons to use the standard Media hotkeys to control play/paus/next,volume etc.
Some screenshots from the software I used
When the device is connected you will se the current keybind in the top right fields above the “Download” and “Cancel” buttons.
To program the keyboard you select a button from the top left buttons and then you use the lower part to select what keybind will be sent to the computer when you press that button. The middle part is for the rotary encoders, there are 3 in the software but my keyboard only have two so the third is not used and will be ignored.
I don’t know what the layer feature is that is in the top right of the software. I first thought it was so you could use one key on the macro keyboard to switch between layers, but I have not found any keybind for that.
First view of the normal keyboard keys
To add ctrl/alt/shift/win key modifier to your keybind
Multimedia keys
Buttons for LED lights, I have not tested this but I assume there is some built in RGB Led that will illuminate the transparent acrylic glass on the keyboard.
Control mouse movement.
Delay, I have not tested this and I assume this will just add a global delay for the buttons.
My macro keyboard scripts
To be able to control the stuff I want on my secondary machine I have made some scripts. What I want to control is
Start/Stop Applications:
- Chrome
- Spotify
- Discord
Media control (play/pause/next) Volume control, mic mute button.
Media keys are easy, because I just bound the default media keys to some of the buttons and the same for main Volume. For the mic mute I just bound one of my hotkeys to do that in KDE.
My macro keyboard has two rotary encoders and I use one for Volume. The second one I will use for Application volume so I can control the volume for Spotify without affecting the other volumes.
Start/Stop applications
I did not just want a simple Start Spotify button, I want to be able to close it as well. So I made a run script for those applications I needed.
This is my script for starting or closing Google Chrome
#!/bin/bash
RUNNING=$(pidof chrome)
function notify() {
echo "$@"
notify-send -a "run_chrome.sh" "$@"
}
if [ "$RUNNING" != "" ]; then
notify "Chrome is running, closing Chrome"
PID=$(echo "$RUNNING" | cut -f 1 -d ' ')
while true; do
#echo "Trying to kill $PID"
#kill -TERM $PID
killall chrome
sleep 2
PIDLIST=$(pidof chrome)
PID=$(echo "$PIDLIST" | cut -f 1 -d ' ')
if [ "$PID" == "" ]; then
break;
fi
done
else
notify "Starting Google Chrome"
/usr/bin/google-chrome-stable
fi
This script will check if chrome is running using the pidof
command, and if it is running it will run killall chrome
and wait 2 seconds and check if it’s still running or not. If you run the script and it can’t find any chrome process it will start chrome.
I’m using notify-send
command to send notifications to my desktop environment so I know that the button press actually worked. So I know if the application doesn’t work I have to verify my script or something.
Application Volume controller
I don’t want to bind my application specific volume controller to one application, like Spotify for example. I want a bit more control and the rotary encoder has a built in push button so I want to use that one to switch application between Chrome and Spotify (and maybe more in the future).
So I just had to make another bash script for this, I’m using pactl
to control the volume and once again I’m using notify-send
so I know what’s happening in my script.
My script is also using KDE specific notification for application volume controller.
#!/bin/bash
# Application settings file
APP_CONF=$HOME/.config/app_volume.conf
# Default config just in case ~/.config/app_volume.conf doesn't have anything
APPS=( "spotify" )
# Cache for selected application name
APP_VOL_CONF=$HOME/.cache/app_volume.tmp.conf
# Source config file
if [ -f $APP_CONF ]; then
source $APP_CONF
fi
# Source cache for selected application
if [ -f $APP_VOL_CONF ]; then
source $APP_VOL_CONF
fi
# if no app_name is selected (in APP_VOL_CONF) select a default one
if [ "$app_name" == "" ]; then
echo "No default application"
app_name=${APPS[0]}
echo "Set default: $app_name"
fi
##############################
#
# notifyError
# Send error message to both console and using notify-send
#
function notifyError() {
echo "$@"
notify-send -a "app_volume.sh" "$@"
}
##############################
#
# parsePactl
# extract the current state and find the sink number from pactl
#
function parsePactl() {
SINK=''
while read line; do
sink_num_check=$(echo "$line" |sed -rn 's/^Sink Input #(.*)/\1/p')
if [ "$sink_num_check" != "" ]; then
#echo "SINK: $sink_num_check"
cur_sink=$sink_num_check
fi
volume_check=$(echo $line | awk '/Volume:/ {printf "%s", $5}' | sed 's/%//' )
if [ "$volume_check" != '' ]; then
#echo "VOL: $volume_check"
VOLUME=$volume_check
fi
app_name_check=$(echo "$line" |sed -rn 's/application.name = "([^"]*)"/\1/p')
if [ "$app_name_check" == "$app_name" ]; then
#echo "Found app"
SINK=$cur_sink
break
fi
done < <(pactl list sink-inputs | grep -e "Sink Input" -e application.name -e Volume:)
}
##############################
#
# get_volume
# Get the current volume for a specific sink
#
function get_volume() {
SINK="$1"
#echo "getVolume for sink $SINK"
if [ "$SINK" == "" ]; then
notifyError "No \$SINK defined for get_volume"
exit
fi
sink_found=''
while read line; do
sink_num_check=$(echo "$line" |sed -rn 's/^Sink Input #(.*)/\1/p')
#echo "sink_num_check: $sink_num_check"
if [ "$sink_num_check" == "$SINK" ] && [ "$sink_found" == '' ]; then
sink_found='true'
fi
#echo "sink_found=$sink_found"
if [ "$sink_found" != '' ]; then
#echo "line: $line"
volume_check=$(echo $line | awk '/Volume:/ {printf "%s", $5}' | sed 's/%//' )
if [ "$volume_check" != '' ]; then
echo "$volume_check"
break
fi
fi
done < <(pactl list sink-inputs)
}
##############################
#
# app_toggle
# Switch between applications for the volume controller
#
function app_toggle() {
SELECTED="$1"
if [ ! -v APPS[@] ]; then
notifyError "Failed to switch application. APPS array is not defined in $APP_CONF"
exit
fi
for i in ${!APPS[@]}; do
if [ "${APPS[$i]}" == "$SELECTED" ]; then
NEXT=$((i+1))
break;
fi
done
SIZE=${#APPS[@]}
if [ $NEXT -lt $SIZE ]; then
echo "Selected: ${APPS[$NEXT]}"
notify-send -u low -t 1500 --app-name="app_volume.sh" "Selected: ${APPS[$NEXT]}"
# Cache current application
echo "app_name=\"${APPS[$NEXT]}\"" > $APP_VOL_CONF
else
echo "Selected: ${APPS[0]}"
notify-send -u low -t 1500 --app-name="app_volume.sh" "Selected: ${APPS[0]}"
# Cache current application
echo "app_name=\"${APPS[0]}\"" > $APP_VOL_CONF
fi
}
#################################
#
# notify_volume
# send a notification of the current Application volume level
#
function notify_volume() {
case "$app_name" in
"Google Chrome")
qdbus org.freedesktop.Notifications /org/kde/osdService org.kde.osdService.mediaPlayerVolumeChanged $VOLUME "Google Chrome" google-chrome
;;
"spotify")
qdbus org.freedesktop.Notifications /org/kde/osdService org.kde.osdService.mediaPlayerVolumeChanged $VOLUME "Spotify" com.spotify.Client
;;
*)
qdbus org.freedesktop.Notifications /org/kde/osdService org.kde.osdService.mediaPlayerVolumeChanged $VOLUME "$app_name" speaker
;;
esac
# notify-send -i speaker \
# -a "App_Volume" \
# -h int:value:$1 \
# -u low \
# -t 1500 \
# -h string:x-canonical-private-synchronous:my-notification "Volume: $VOLUME%"
}
case "$1" in
"up")
parsePactl "$app_name"
echo "SINK: $SINK"
if [ "$SINK" == "" ]; then
notifyError "No sink for $app_name was found"
exit
fi
VOLUME=$(($VOLUME + 5))
pactl set-sink-input-volume $SINK +5%
notify_volume $VOLUME
;;
"down")
parsePactl "$app_name"
echo "SINK: $SINK"
if [ "$SINK" == "" ]; then
notifyError "No sink for $app_name was found"
exit
fi
VOLUME=$(($VOLUME - 5))
pactl set-sink-input-volume $SINK -5%
notify_volume $VOLUME
;;
"toggle")
app_toggle "$app_name"
;;
*)
echo "unknown command"
;;
esac