#!/bin/sh # SPDX-License-Identifier: MIT # # picom-trans # Copyright (c) 2021, Subhaditya Nath # Based on previous works of Christopher Jeffrey # # # Conforming to POSIX-1.2007 # https://pubs.opengroup.org/onlinepubs/9699919799 # # Usage: # $ picom-trans [options] [+|-]opacity # By window id # $ picom-trans -w "$WINDOWID" 75 # By name # $ picom-trans -n "urxvt" 75 # By current window # $ picom-trans -c 75 # By selection # $ picom-trans 75 # $ picom-trans -s 75 # Increment current window 5% # $ picom-trans -c +5 # Delete current window's opacity # $ picom-trans -c --delete # Toggle current window's opacity between 90 and unset # $ picom-trans -c --toggle 90 # Reset all windows # $ picom-trans --reset # Save $0 now to print correct value while printing from functions. # Printing errormsgs from functions using "$0" prints the function name # instead of the executable name. EXE_NAME="$0" # Instead of printing the full path to this file (e.g. /usr/bin/picom-trans) # only print the base name (i.e. picom-trans) EXE_NAME="$(basename "$EXE_NAME")" print_usage() { #{{ echo "Usage: $EXE_NAME [options] [+|-]opacity" echo "" echo "Options:" echo " -h, --help Print this help message." echo " -o, --opacity OPACITY Specify the new opacity value in range 0-100 for the window. If" echo " prefixed with + or -, increment or decrement from the current" echo " opacity of the target window." echo "" echo "Actions:" echo " -g, --get Print current opacity of the target window." echo " -d, --delete Delete opacity of the target window." echo " -t, --toggle Toggle the target window's opacity, i.e. set if not already set" echo " and delete else." echo " -r, --reset Reset opacity for all windows." echo "" echo "Window Selection:" echo " -s, --select Select target window with mouse cursor. (DEFAULT)" echo " -c, --current Select the currently active window as target." echo " -n, --name WINDOW_NAME Specify and try to match a window name." echo " -w, --window WINDOW_ID Specify the window id of the target window." } #}} parse_args() { #{{ i=1 # because we start from "$1", not from "$0" while [ $i -le $# ] do #### [START] Convert GNU longopts to POSIX equivalents #### if [ "$1" = "--${1##--}" ] # check if $1 is a longopt then # Catch invalid options case "$1" in (--opacity=|--name=|--window=) echo "$EXE_NAME: option ${1%=} needs a value" >&2 exit 1;; (--opacity|--name|--window) test $i -eq $# \ && echo "$EXE_NAME: option $1 needs a value" >&2 \ && exit 1;; esac # Separate "--ARG=VAL" into "--ARG" "VAL" case "$1" in (--opacity=*|--name=*|--window=*) ARG="$(echo "$1" | sed -E 's/(--[^=]+)=.*$/\1/')" VAL="${1##${ARG}=}" shift && set -- "$ARG" "$VAL" "$@" esac # Turn into short form case "$1" in (--help|--opacity|--get|--delete|--toggle|--reset|--select|--current|--name|--window) ARG=${1#-} # remove one '-' from prefix ARG="$(echo "$ARG" | cut -c -2)" # get first two characters shift && set -- "$ARG" "$@" esac # If the argument still starts with --, it is an invalid argument case "$1" in (--*) echo "$EXE_NAME: illegal option $1" >&2 exit 1 esac fi #### [END] Convert GNU longopts to POSIX equivalents #### #### [START] Prepend '-o' to standalone opacity values #### # Iterate over every argument and check if it is an opacity without the -o # option in the previous argument. If so, then prepend the -o option. # e.g. Turn this - # picom-trans -c +10 -s # into this - # picom-trans -c -o +10 -s # # NOTE: Don't touch arguments that are preceded by -o, -w, or -n (i.e. the # options that take a value.) # e.g. This - # picom-trans -w 75 -o 90 # should NOT be turned into this - # picom-trans -w -o 75 -o 90 # We ensure this by checking the "$#"th (i.e. the last) argument. If # argument is an option that needs a value, we don't do anything to $1. # # NOTE: we are using printf because most echo implementations aren't # POSIX-compliant. For example, according to POSIX.1-2017, echo doesn't # support any options, so, # $ echo "-n" # should output - # -n # But it doesn't. It instead interprets the "-n" as the option -n, which, # in most implementations, means that the trailing newline should not be # printed. if echo "$1" | grep -qE '^[+-]?[[:digit:]]+%?$' && \ ! eval "printf '%s' \"\${$#}\"" | grep -q '^-[hdtrgsc]*[own]$' # NOTE: eval "printf '%s' \"\${$#}\"" means 'print the last argument' # NOTE: The letters inside the first square brackets (ie. hdtrgsc) are # the same as those in the getopts argument, minus those that are # followed by a ':' # NOTE: The letters inside the second square brackets (ie. own) are # the same as those in the getopts argument, minus those that are # NOT followed by a ':' then set -- "$@" "-o" i=$(( i + 1 )) fi #### [END] Prepend '-o' to standalone opacity values #### # Prepare for next iteration ARG="$1" shift && set -- "$@" "$ARG" i=$(( i + 1 )) done # NOTE: DO NOT ATTEMPT TO USE "$OPTIND" INSIDE THE getopts LOOP # - https://github.com/yshui/picom/pull/634#discussion_r654571535 # - https://www.mail-archive.com/austin-group-l%40opengroup.org/msg04112.html OPTIND=1 while getopts 'ho:dtrgsn:w:c' OPTION do case "$OPTION" in (h) print_usage; exit 0;; (o) target_opacity="$OPTARG";; (d) action=delete;; (t) action=toggle;; (r) action=reset;; (g) action=get;; (s) winidtype=; winid=;; (n) winidtype=-name; winid="$OPTARG";; (w) winidtype=-id; winid="$OPTARG";; (c) winidtype=-id; winid="$(get_focused_window_id)";; (\?) exit 1 esac done } #}} get_target_window_id() { #{{ # Get the output of xwininfo if test -z "$winidtype" then xwininfo_output="$(xwininfo -children -frame)" elif test "$winidtype" = "-name" then xwininfo_output="$(xwininfo -children -name "$winid")" elif test "$winidtype" = "-id" then # First, check if supplied window id is valid if ! echo "$winid" | grep -Eiq '^[[:space:]]*(0x[[:xdigit:]]+|[[:digit:]]+)[[:space:]]*$' then echo "Bad window ID" >&2 exit 1 fi xwininfo_output="$(xwininfo -children -id "$winid")" fi # Extract window id from xwininfo output winid="$(echo "$xwininfo_output" | sed -n 's/^xwininfo:.*: \(0x[[:xdigit:]]*\).*$/\1/p')" if test -z "$winid" then echo "Failed to find window" >&2 exit 1 fi # Make sure it's not root window if echo "$xwininfo_output" | grep -Fq "Parent window id: 0x0" then echo "Cannot set opacity on root window" >&2 exit 1 fi # If it's not the topmost window, get the topmost window if ! echo "$xwininfo_output" | grep -q 'Parent window id: 0x[[:xdigit:]]* (the root window)' then window_tree="$(xwininfo -root -tree)" if test -z "$window_tree" then echo "Failed to get root window tree" >&2 exit 1 fi # Find the highest ancestor of the target window winid="$(echo "$window_tree" \ | sed -n "/^\s*$winid/q;s/^ \(0x[[:xdigit:]]*\).*/\1/p" \ | tail -n 1)" if test -z "$winid" then echo "Failed to find window in window tree" >&2 exit 1 fi fi if test -z "$winid" then echo "Failed to find the highest parent window below root of the selected window" >&2 exit 1 fi echo "$winid" } #}} get_focused_window_id() { #{{ id="$(xprop -root -notype -f _NET_ACTIVE_WINDOW 32x '$0' _NET_ACTIVE_WINDOW)" echo "${id#_NET_ACTIVE_WINDOW}" } #}} get_current_opacity() { #{{ # Gets current opacity in the range 0-100 # Doesn't output anything if opacity isn't set cur="$(xprop -id "$winid" -notype -f _NET_WM_WINDOW_OPACITY 32c '$0' _NET_WM_WINDOW_OPACITY)" cur="${cur#_NET_WM_WINDOW_OPACITY}" cur="${cur%:*}" test -n "$cur" && cur=$(( cur * 100 / 0xffffffff )) echo "$cur" } #}} get_opacity() { #{{ cur="$(get_current_opacity)" test -z "$cur" && cur=100 # Unset opacity means fully opaque echo "$cur" exit 0 } #}} delete_opacity() { #{{ xprop -id "$winid" -remove _NET_WM_WINDOW_OPACITY exit 0 } #}} reset_opacity() # Reset opacity of all windows { #{{ for winid in $(xwininfo -root -tree | sed -n 's/^ \(0x[[:xdigit:]]*\).*/\1/p') do xprop -id "$winid" -remove _NET_WM_WINDOW_OPACITY 2>/dev/null done exit 0 } #}} set_opacity() { #{{ if ! echo "$target_opacity" | grep -qE '^[+-]?[[:digit:]]+%?$' then if test -z "$target_opacity" then echo "No opacity specified" >&2 else echo "Invalid opacity specified: $target_opacity" >&2 fi exit 1 fi # strip trailing '%' sign, if any target_opacity="${target_opacity%%%}" if echo "$target_opacity" | grep -q '^[+-]' then current_opacity="$(get_current_opacity)" test -z "$current_opacity" && current_opacity=100 target_opacity=$(( current_opacity + target_opacity )) fi test $target_opacity -lt 0 && target_opacity=0 test $target_opacity -gt 100 && target_opacity=100 target_opacity=$(( target_opacity * 0xffffffff / 100 )) xprop -id "$winid" -f _NET_WM_WINDOW_OPACITY 32c \ -set _NET_WM_WINDOW_OPACITY "$target_opacity" exit $? } #}} toggle_opacity() { #{{ # If opacity is currently set, unset it. # If opacity is currently unset, set opacity to the supplied value. If no # value is supplied, we default to 100%. if test -z "$(get_current_opacity)" then test -n "$target_opacity" || target_opacity=100 set_opacity else delete_opacity fi } #}} # Warn about rename of compton to picom case "$0" in *compton-trans*) echo "Warning: compton has been renamed, please use picom-trans instead" >&2;; esac # Check if both xwininfo and xprop are available if ! command -v xprop >/dev/null || ! command -v xwininfo >/dev/null then echo "The command xwininfo or xprop is not available. They might reside in a package named xwininfo, xprop, x11-utils, xorg-xprop, or xorg-xwininfo" >&2 exit 1 fi # No arguments given. Show help. if test $# -eq 0 then print_usage >&2 exit 1 fi # Variables # action is set to 'set' by default action=set winid= winidtype= target_opacity= # If there's only one argument, and it's a valid opacity # then take it as target_opacity. Else, parse all arguments. if test $# -eq 1 && echo "$1" | grep -qE '^[+-]?[[:digit:]]+%?$' then target_opacity=$1 shift else parse_args "$@" fi # reset_opacity doesn't need $winid case $action in (reset) reset_opacity;; esac # Any other action needs $winid # # NOTE: Do NOT change the order of winid= and winidtype= below # the output of get_target_window_id depends on $winidtype # # NOTE: If get_target_window_id returns with a non-zero $? # that must mean that some error occured. So, exit with that same $? # winid=$(get_target_window_id) || exit $? winidtype=-id case $action in (set) set_opacity;; (get) get_opacity;; (delete) delete_opacity;; (toggle) toggle_opacity;; esac # We should never reach this part of the file echo "This sentence shouldn't have been printed. Please file a bug report." >&2 exit 128 # vim:ft=sh:ts=4:sts=4:sw=2:et:fdm=marker:fmr=#{{,#}}:nowrap