Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Menu for choosing screen orientation on a tablet computer

Screenshot of Choose Screen Orientation

Overview

I found some great sources on the Internet for how to set up a menu for choosing screen orientation on a tablet computer that doesn't have gyros or other sensors to orient the screen "up." My solution involves a number of scripts, a .desktop file, and some config entries in a few spots.

Design

I wanted a menu, with arrows, where the user selects the arrow that points up. So the current screen orientation will affect the chosen value. Research on the Internet showed that I will need to rotate the tablet input and optionally the wallpaper. My environment uses fluxbox, so the architecture will hardcode in fluxbox controls but obviously this can be adapted as needed.

Dependencies

  • yad
  • xrandr
  • xinput
  • fbsetbg (optional)
  • mktrayicon (optional)

Files involved

I placed all the scripts in /usr/local/bin. The desktop file is placed in /usr/share/applications.

Files modified

  • /etc/rc.local
  • ~/.fluxbox/keys

New files

  • rotate-menu.sh
  • rotate.sh
  • rotate-wallpaper.sh
  • rotate-trayicon.sh
  • rotate-trayicon.desktop
Rotate menu

The main GUI is in rotate-menu.sh. This script uses yad (because it had way fewer dependencies than zenity) to generate a simple window with buttons. The chosen orientation is calculated relative to the current orientation, and the true orientation is passed to the Rotate script.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/bin/sh
# File: rotate-menu.sh
# Locations:
#    /usr/local/bin/
# Author: bgstack15
# Startdate: 2021-10-20 09:50
# SPDX-License-Identifier: GPL-3.0
# Title: Menu for Choosing Screen Orientation on Thinkpad X230
# Purpose: Make it easy to rotate screen
# Usage: ./rotate-menu.sh
#    or press the "screen rotate" button
# Dependencies: yad, xrandr
#    In /etc/rc.local: "setkeycodes 6c 132"
#    In ~/.fluxbox/keys "140 :Exec /usr/local/bin/rotate-menu.sh"
# Reference:
#    https://forums.linuxmint.com/viewtopic.php?t=110395
#    https://forum.thinkpads.com/viewtopic.php?t=108785
#    https://wiki.archlinux.org/title/HiDPI#GDK_3_(GTK_3)
# Improve:
# Documentation:
#    Chose yad because zenity wanted 121MB of dependencies and yad needed no additional dependencies.
#    YES, the fluxbox and rc.local keycodes are not the same. I don't know why they need to be different to work.

rotate_script=/usr/local/bin/rotate.sh
order="left,normal,right,inverted,left,normal,right"

current_orientation="$( xrandr -q --verbose | grep 'connected' | grep -o  -E '\) (normal|left|inverted|right) \(' | grep -o -E '(normal|left|inverted|right)' )"
echo "Currently facing: ${current_orientation}"

new_orientation() {
   # call: new_orientation ${orientation} ${NUMBER}
   # where number is [0-3] and we want to move to the orientation adjacent to old orientation
   first="$( echo "${order}" | tr ',' '\n' | awk -v "or=${1}" '$0 ~ or && a==0 {a=1;print NR}' )"
   #echo "first=${first}"
   echo "${order}" | tr ',' '\n' | awk -v "add=${2}" -v "or=${first}" 'NR==(add+or){print;}'
}

GDK_SCALE=2 yad --form --center --buttons-layout=center --window-icon=display --title='Change screen orientation' --align=center --field='Which direction should be up?:LBL' --button='!go-previous:152' --button='!up:150' --button='!down:151' --button='!go-next:153' 1>/dev/null 2>&1
result=$?
#echo "${result}"
case "${result}" in
   150) echo "pressed button up" ; new="$( new_orientation ${current_orientation} 0 )" ;;
   151) echo "pressed button down" ; new="$( new_orientation ${current_orientation} 2 )" ;;
   152) echo "pressed button left" ; new="$( new_orientation ${current_orientation} 3 )" ;;
   153) echo "pressed button right" ; new="$( new_orientation ${current_orientation} 1 )" ;;
   *) echo "invalid response: ${result}" ;;
esac

echo "new rotation should be ${new}"
"${rotate_script}" "${new}"
Rotate

The actual rotation logic is stored in a separate script, rotate.sh. Decoupling the UI from the functions is always useful, particularly for someone who wants to run arbitary rotation commands. This script rotates the X screen, and also the stylus, eraser (other end of the stylus?), and touch inputs. Without the input rotations, hilarity can ensue. You should try it sometime just to see what it's like.

#/bin/sh
# File: rotate.sh
# Locations:
#    /usr/local/bin/
# Author: bgstack15
# Startdate: 2021-10-20
# SPDX-License-Identifier: GPL-3.0
# Title: Rotate display and inputs
# Purpose: Rotates X display and also the inputs
# History:
# Usage:
#    rotate.sh [left|right|normal|inverted]
#    Called from rotate-menu.sh, which calculates which of the directions to use.
# Reference:
#    https://forums.linuxmint.com/viewtopic.php?t=110395
# Improve:
# Dependencies:
#    xinput

test -z "${DISPLAY}" && { echo "Fatal! Need DISPLAY set. Aborted." 1>&2 ; exit 1 ; }
test -z "${ROTATE_WALLPAPER_SCRIPT}" && ROTATE_WALLPAPER_SCRIPT=/usr/local/bin/rotate-wallpaper.sh
orientation="${1}"

case "${orientation}" in
   normal) wacom_orientation="none" ;;
   inverted) wacom_orientation="half" ;;
   left) wacom_orientation="ccw" ;;
   right) wacom_orientation="cw" ;;
   *) echo "Invalid orientation ${orientation}. Aborted." 1>&2 ; exit 1 ;;
esac

# collect stylus, touch, and eraser IDs
xi="$( xinput list )"
stylus="$( echo "${xi}" | awk '/stylus.*slave.*pointer/{print}' | awk -F'=' '{print $2}' | awk '{print $1}' )"
touch="$( echo "${xi}" | awk '/touch.*slave.*pointer/{print}' | awk -F'=' '{print $2}' | awk '{print $1}' )"
eraser="$( echo "${xi}" | awk '/eraser.*slave.*pointer/{print}' | awk -F'=' '{print $2}' | awk '{print $1}' )"

# MAIN
xrandr -o "${orientation}"
"${ROTATE_WALLPAPER_SCRIPT}"
xsetwacom set "${stylus}" rotate "${wacom_orientation}"
xsetwacom set "${touch}" rotate "${wacom_orientation}"
xsetwacom set "${eraser}" rotate "${wacom_orientation}"
Rotate wallpaper

For additional aesthetic value, I decided to build a script that rotates the wallaper. The admin can establish symlinks in directory /etc/installed/wallpapers, named the same as the orientations that X reports (normal, inverted, left, right). The symlinks can point to whatever you want.

$ cd /etc/installed/wallpapers
$ ln -s wallpaper_wide.jpg normal
$ ln -s wallpaper_wide.jpg inverted
$ ln -s wallpaper_tall.jpg left
$ ln -s wallpaper_tall.jpg right

The script rotate-wallpaper.sh uses these symlinks.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/sh
# File: rotate-wallpaper.sh
# Locations:
#    /usr/local/bin/
# Author: bgstack15
# Startdate: 2021-10-20 22:23
# SPDX-License-Identifier: GPL-3.0
# Title: Choose Wallpaper for Current Orientation
# Purpose: Choose wallpapers from the symlinks in /etc/installed/wallpapers/
# History:
# Usage:
#    rotate-wallpaper.sh
#    Called by ~/.fluxbox/startup or manually, or from rotate-menu.sh
# Reference:
#    rotate-menu.sh
# Improve:
# Dependencies:
#    xrandr, fbsetbg
#    symlinks named same as xrandr orientations: normal, right, inverted, left
# Documentation:
current_orientation="$( xrandr -q --verbose | grep 'connected' | grep -o  -E '\) (normal|left|inverted|right) \(' | grep -o -E '(normal|left|inverted|right)' )"
echo "Currently facing: ${current_orientation}"

case "${current_orientation}" in
   normal|inverted|left|right) fbsetbg -f /etc/installed/wallpapers/"${current_orientation}" ;;
   *) echo "Unknown orientation ${current_orientation}! Aborted." ; exit 1 ;;
esac
Tray icon

The configurations described below set up the hardware "Screen rotate" button to trigger rotate-menu.sh. My hardware doesn't always register presses of the button, so I wanted to add a software button. This script, rotate-trayicon.sh, uses mktrayicon to make a simple icon that runs the main GUI when clicked.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
# File: rotate-trayicon.sh
# Locations:
#    /usr/local/bin/
# Author: bgstack15
# Startdate: 2021-10-21 11:47
# SPDX-License-Identifier: GPL-3.0
# Title: Tray icon for rotate-menu
# Purpose: Display the display icon in the system tray, because the "rotate screen" physical button is not always registered.
# History:
# Usage:
#    rotate-trayicon.sh
#    Can be called from ~/.fluxbox/startup
# References:
# Improve:
# Dependencies:
#    mktrayicon
fifo="${XDG_RUNTIME_DIR:-/tmp}/$$.icon"
mkfifo "${fifo}"
mktrayicon "${fifo}" &
echo "i display" >> "${fifo}"
echo "m Exit,echo 'q'>> ${fifo};sleep 2;rm ${fifo};" >> "${fifo}"
echo "c /usr/local/bin/rotate-menu.sh" >> "${fifo}"
echo "s" >> "${fifo}"
Tray icon menu entry

In case the trayicon needs to be started from the application menu, here is my rotate-trayicon.desktop file.

[Desktop Entry]
Comment=Tray icon for asking user the desired screen orientation
Exec=rotate-trayicon.sh
Categories=Utility;TrayIcon;
GenericName=Display orientation helper tray icon
Icon=display
Keywords=display;rotate;
Name=Rotate-menu tray icon
Terminal=false
Type=Application

Configurations

To take advantage of my specific hardware's rotate-screen button, I had to assign the scancode to a keycode. References [1][1] and [2][2] are great for describing how to assign a keycode to a scancode (what happens when you press a button). If you need a reminder, use xev(1) and showkey(1).

Once you know the scancode for your button, set up the setkeycode command in /etc/rc.local.

setkeycodes 6c 132

And then in ~/.fluxbox/keys I used this directive.

140 :Exec /usr/local/bin/rotate-menu.sh

Yes, for some reason the setkeycode and code that Fluxbox uses are not the same. I don't know why, unless one of them is not base10 or something. But I got it to work, some of the time anyway.

References

  1. Tablet PC rotation HOW TO - Linux Mint Forums
  2. [Guide] Setting up Tablet Screen Rotation with Linux - Thinkpads Forum
  3. HiDPI - ArchWiki#GDK

Comments