Wednesday, 27 January 2016

Per-PC font sizes, etc

My home network has NFS homedirs, and so it doesn't matter which computer or laptop family members log into. They get all their files.

Now, one of the PC's has a 40 inch monitor, and users prefer a text-scaling-factor of 2 when using that PC.

Changing their personal settings means changing it back again; fortunately a system-wide default of 2 can be set on that PC, and apply to all users that don't override the system value.

Edit the text-scaling-factor in the schema:
sudo nano /usr/share/glib-2.0/schemas/org.gnome.desktop.interface.gschema.xml

and rebuild the schema:
sudo glib-compile-schemas /usr/share/glib-2.0/schemas


And then to reset the users custom value (if any):

gsettings reset org.gnome.desktop.interface text-scaling-factor

But doing the same for org.cinnamon.desktop as I do for org.gnome.desktop


Also default-zoom-level to larger and default-use-tighter-layouts to true, in /usr/share/glib-2.0/schemas/org.nemo.gschema.xml

Bash: setting and testing $?

You want to set $? in bash

?() {
  return ${1:-$?}

and then:

$ \? 2
$ echo $?

You also want to test $?:

$ \? 2
$ \? && echo yes $? || echo no $?
no 2 

which is simpler than
$ test $? = 0 && echo yes $? || echo no $?
no 1 
which also replaces $? with the result of the test.

But how about this extended form that allows you to run a command while preserving (or forcing) the return code?

?() {
  set -- $? "$@"
  if test "$#" -le 2 -a -z "${2//[0-9]}"
  then return ${2:-$1}
  else "${@:2}"
       return $1


$ tar -xzf "$tar"
$ \? rm -fr "$tar"

which leaves $? set to the result of the tar extraction.

Of course a command which is purely numeric with no arguments is mistaken for an exit code.

Note the use of: set -- $? "$@" as function arguments are which is the only lexical scoped variables in bash.

Timing bash commands

Try this, it runs a simple command with arguments and puts the times $real $user $sys and preserves the exit code. It also does not fork subshells or trample on any variables except real user sys, and does not otherwise interfere with the running of the script

timer () {
  { time { "$@" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $?
  read -d "" _ real _ user _ sys _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1


  timer find /bin /sbin /usr rm /tmp/
  echo $real $user $sys

note: it only times a simple command, not any part of a pipeline (all parts of which are run in a sub-shell).

This version allows you to specify as $1 $2 $3 the name of the variables that should receive the 3 times:

timer () {
  { time { "${@:4}" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $? "$@"
  read -d "" _ "$2" _ "$3" _ "$4" _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1


  timer r u s find /bin /sbin /usr rm /tmp/
  echo $r $u $s

and may be useful if it ends up being called recursively, to avoid trampling on times; but then r u s etc should be declared local in their use.

is a way of specifying a temporary file name that will not be trampled on until after this function exits.

Shared here:

Filtering stderr

This helper function will take $1 as a simple command to be used on stderr, on the rest of the command. The filtered output is emitted on stderr.

e.g. stderr "sed -e s/^/tar: /" tar -xvzf -

The function is short, but obscure, making use of a few tricks

stderr() {
  { set -- $_ "$@" ; } {_}>&1

  { eval '"${@:3}"' "$1>&-" ; } 2>&1 >&${1} | eval '$2' ">&2" "$1>&-" 

  set -- $1 ${PIPESTATUS[0]} "${@:2}"
  eval "exec $1>&-"
  return $2

An explanation is here:

stderr() {
  { set -- $_ "$@" ; } {_}>&1

The first line uses the temporary variable _ (underscore), which cannot generally be relied upon, but is safe enough in this context. This variable is used to avoid this helper leaving any imprint. Trampling on variables or declaring any local variables could affect destroy transparency and potentially affect the rest of the script.

So _ becomes a copy of stdout; and then inside the { ... } we update the function arguments so that this copy of stdout is now argument 1.

The function arguments are the only lexically scoped variables in bash. We can set them here in this function knowing that they will not have any other affect anywhere else.

So $1 now refers to a copy of stdout, $2 is now the filter to be applied to stderr, and "${@:3}" ($3 and onwards) is the command to be filtered.

We want to run the command with the $1 copy of standard out closed, in case the command spawns other processes that might inherit this copy and leave it open. Its a private copy, and as bash doesn't support close-on-exec we must close it.

We want to do this: "${@:3}" $1>&- but bash can't take a parameter variable on the left hand side of a redirector (not even as ${!1}) so we must use eval. We put the command in single quotes to prevent it being interpolated at all prior to eval, but the redirector is in double quotes so that the interpolated string is passed to eval; thus: eval '"${@:3}"' "$1>&-"

We want to run this command with stdout passed to the spare copy we made in $1  because we will redirect stderr to stdout to be fed into the filter. This redirection specification is: 2>&1 >&${1} (variables are allowed on the right hand side of a redirector).

However we can't append these redirectors to the previous one which already closed $1, so we use a brace scope { ... ; }  in which $1 is closed.

This gives us so far: { eval '"${@:3}"' "$1>&-" ; } 2>&1 >&${1} which has stdout going to our copy of stdout, and stderr going to actual stdout ready to pipe to the next stage.

The next stage also wants to close $1 for the same reason as before, and is:
eval '$2' ">&2" "$1>&-"

So the whole invocation is:
{ eval '"${@:3}"' "$1>&-" ; } 2>&1 >&${1} | eval '$2' ">&2" "$1>&-"

We now want to close $1  for the rest of the script without losing the exit code. As we have finished calling other commands we could save $? in a local variable, but I use the function arguments again to save as $2. Note that PIPESTATUS[0] holds the result of the first stage of the pipeline.

  set -- $1 ${PIPESTATUS[0]} "${@:2}"
  eval "exec $1>&-"
  return $2

And there it is.

Monday, 18 January 2016

Check status of entiire bash pipeline

My useful answer here:

pipestatus() {
  local S=(${PIPESTATUS[*]})

  if test -n "$*"
  then test "$*" = "${S[*]}"
  else ! [[ "${S[@]}" =~ [^0\ ] ]]

Note that S is not set to ("${PIPESTATUS[@]}"); this is so that we can re-create an array if PIPESTATUS is passed as a string, like this:

PIPE_STATUS="${PIPESTATUS[*]}" pipestatus

because an array cannot be passed in that fashion. Why would anyone want to do that? Probably not directly, but other helper commands just and also may want to preserve PIPESTATUS as best as possible to permit an: also pipestatus combination.

Usage examples:

1. get_bad_things must succeed, but it should produce no output; but we want to see output that it does produce

get_bad_things | grep '^'
pipeinfo 0 1 || return

2: all pipeline must succeed

thing | something -q | thingy
pipeinfo || return

Thursday, 7 January 2016

Switch hdmi audio when TV is off using cec-client

I have a HDMI TV used as a computer monitor.

When the TV is turned on, the audio plays through the TV, so that the TV remote volume control works.

When  the TV is turned off, I want the audio to play through analog audio out so that I can still hear it.

I use the PulseEIGHT CEC controller and cec-client program to talk to the TV.

I have cec-client run as root under SOCAT to broadcast cec-data by UDP.

I have another SOCAT process running as the logged in user to pick up the data, monitor it for TV on/off status and move the playing audio streams.

It works, but is very rough.

In rc.local: /usr/local/bin/cec -s &

In gnome/cinnamon startup programs: /usr/local/bin/cec

And then the script:

#! /bin/bash

MYADDR=8 # because it is. I should probably read this from the cec-client output 
# 0 and 1 and the audio device indexes associated with the device names
# later I will normalise these too, but "pacmd list" will show yours.
ext() {
  pacmd set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo
  for input in $( pacmd list-sink-inputs | sed -e 's/index: //;t;d' )
  do pacmd move-sink-input $input 1

hdmi() {
  pacmd set-default-sink alsa_output.pci-0000_01_00.1.hdmi-stereo-extra1
  for input in $( pacmd list-sink-inputs | sed -e 's/index: //;t;d' )
  do pacmd move-sink-input $input 0

cec_client() {
  socat -u UDP4-RECV:4224,reuseaddr - | while read type _ _ dir rest
  do # echo "$type    $dir    :$rest"
     case "$rest" in
       "0f:36") echo "POWER OFF" ; ext ;;
       "(0): power status changed"*"to 'on'") echo "POWER ON" ; hdmi ;;
#       *"($MYADDR) as inactive source"*) echo INACTIVE ; ext ;;
#       *"($MYADDR) the active source"*) echo ACTIVATED ; hdmi ;;
#       *active*) echo "$type    $dir    $rest" ;;
# mmkeys-mate2mpris2

cec_server() {
  read hostname _ < <( hostname )
  hex_hostname=$( echo -n "${hostname%.*}" | od -tx1 | sed -e '1!d;s/0* //' )

  <<< "tx 80 47 $hex_hostname" exec -a CEC socat -u -L/tmp/cec \
      EXEC:'cec-client -t p -p 2' UDP-DATAGRAM:,broadcast &


main() {
  if test "$1" = "-s"
  then cec_server
  else cec_client

main "$@"