#!/bin/zsh # vim: sts=2 sw=2 et setopt no_unset warn_create_global zmodload zsh/system || exit $? typeset -g map=/run/display-container-map typeset -g scandir=/run/service typeset -g build=/run/xpra.service main() { typeset -g container client_display container=$1 shift || exit 100 [[ $container == */* ]] && exit 100 client_display=${${DISPLAY%%.*}#:} do_lock find_server_display ensure_services do_unlock start_services print_cookie } typeset -f -t main do_lock() { mkdir -p $map || exit $? touch $map/.lock || exit $? zsystem flock -f lockfd $map/.lock || exit $? } find_server_display() { typeset -g display local -a found allocated found=( $map/*=$container(N) ) if ! (($#found)); then # spawn a new Xpra session this container. allocated=( $map/*=*(N) ) allocated=( ${${allocated:t}%%=*} ) #for n in {${${DISPLAY%%.*}#:}00..${${DISPLAY%%.*}#:}99}; do for n in {100..199}; do if (( $allocated[(I)$n] == 0 )); then display=$n touch $map/$display=$container break fi done if [[ -z $display ]]; then exit 3 # All 99 displays occupied?! fi else display=${${found[1]:t}%%=*} fi } ensure_services() { typeset -ga new_services client_services typeset -g sv_name_xorg=xpra-xorg.$display typeset -g sv_name_server=xpra-server.$display typeset -g sv_name_client=xpra-client.$display.for.$client_display write_sv_definitions # if (($#new_services)); then # - s6-svscanctl -a /run/service # # TODO: wait for s6-supervises to start # fi } ### Color and text definition {{{1 typeset -g hl_fatal hl_reset # die messages if (( $terminfo[colors] >= 8 )); then hl_fatal='%F{red}%B'; hl_fatal=${(%)hl_fatal} hl_warn='%F{yellow}%B'; hl_warn=${(%)hl_warn} hl_reset='%b%f'; hl_reset=${(%)hl_reset} fi ### Utility functions {{{1 err_msg() { local first=$1 shift printf >&2 '%s%s%s%s\n' "$hl_fatal" "$first" "$hl_reset" "$*" } warn_msg() { local first=$1 shift printf >&2 '%s%s%s%s\n' "$hl_warn" "$first" "$hl_reset" "$*" } # helper that prints out stack, error message and exits die_ret() { set +x local ret n ret=$1 shift print -r - >&2 "${hl_fatal}Fatal$hl_reset error occurend in:" for n in {${#funcfiletrace}..1}; do printf >&2 '%d> %s (%s)\n' $n "$funcfiletrace[$n]" "$functrace[$n]" done printf >&2 '%s\n' "${hl_fatal}*$hl_reset $^@" exit $ret } die() { set +x die_ret 1 "$@" } die100() { # 100: wrong usage set +x die_ret 100 "$@" } die111() { # 111: system call failed set +x die_ret 111 "$@" } -() { # Run command and die on nonzero exitcode "$@" || die_ret $? "command failed with exitcode $?: ${(j: :)${(q)@}}" } # service builder sv_start() { (($+sv_name)) && \ die100 "sv_start: previous service definition ${(qqq)sv_name} wasn't ended properly" (( $# != 1 )) && die100 "sv_start: incorrect arguments" [[ $1 == */* ]] && die100 "sv_start: incorrect argument" typeset -g sv_name=$1 typeset -g sv_dir=$build/$1 - mkdir -p $sv_dir - touch $sv_dir/down } sv_end() { (($+sv_name)) || die100 "$0: no service definition started" (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}" (( $# != 0 )) && die100 "$0: incorrect arguments" if ! [[ -L $scandir/$sv_name ]]; then - s6-svlink -t 3000 $scandir $sv_dir # - s6-mkfifodir $sv_dir/event # - ln -s -T $sv_dir $scandir/$sv_name # new_services+=( $scandir/$sv_name ) fi unset sv_name sv_dir } sv_link_command() { (($+sv_name)) || die100 "$0: no service definition started" (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}" (( $# != 2 )) && die100 "$0: incorrect arguments" local script_path=$sv_dir/$1 - ln -s -f -T ${commands[$2]} $script_path } sv_write_lines() { (($+sv_name)) || die100 "$0: no service definition started" (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}" (( $# < 2 )) && die100 "$0: incorrect arguments" local file_path=$sv_dir/$1 shift printf '%s\n' >$file_path "$@" || \ die111 "Error writing to ${(qqq)file_path}" } sv_el_script() { (($+sv_name)) || die100 "$0: no service definition started" (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}" - sv_write_lines $1 '#!/bin/execlineb -P' "$@[2,-1]" - chmod +x $sv_dir/$1 } sv_notification_fd() { (($+sv_name)) || die100 "$0: no service definition started" (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}" (( $# != 1 )) && die100 "$0: incorrect arguments" - sv_write_lines notification-fd $1 } sv_env() { (($+sv_name)) || die100 "$0: no service definition started" (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}" (( $# < 2 )) && die100 "$0: incorrect arguments" - mkdir -p $sv_dir/env local k v for k v in "$@"; do [[ $k == */* ]] && die100 "$0: invalid env variable ${(qqq)k}" - sv_write_lines env/$k $v done } sv_mkdir() { (($+sv_name)) || die100 "$0: no service definition started" (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}" (( $# < 1 )) && die100 "$0: incorrect arguments" - mkdir -p $sv_dir/${^@} } # end service builder sv_common_finish() { local -a lines=( 'backtick -E up { s6-svstat -o up ../'${(qqq)sv_name_server}' }' 'backtick -E wantedup { s6-svstat -o wantedup ../'${(qqq)sv_name_server}' }' ## down this service if server is neither up nor wanted up 'if -n { if { $up } $wantedup }' # 'ifelse { eltest true == $up -o true == $wantedup } { }' 's6-svc -d .' ) - sv_el_script finish $lines } write_sv_xorg() { - sv_start $sv_name_xorg - sv_notification_fd 3 - sv_link_command run service.xpra-xorg.run - sv_common_finish - sv_env display_number $display - sv_end } write_sv_server() { - sv_start $sv_name_server - sv_el_script run \ xpra-server $display --displayfd=3 - sv_notification_fd 3 - sv_end } write_sv_xclipring() { local selection=$1 local name=xclipring-$selection.$display - sv_start $name - sv_el_script run \ "if { mkdir -p /run/xclipring/$selection/$display }" \ "in-xpra $display /usr/local/bin/xclipring" \ "-s ${selection:u}" \ "-c 32" \ "-d /run/xclipring/$selection/$display" - sv_end client_services+=( $name ) } write_sv_client() { - sv_start $sv_name_client - sv_el_script run \ 's6-envdir env' \ xpra-client "$display" \ "--title=\"[${container}] @title@ «s=@server-machine@ c=@client-machine@ d=$display w=@xid@\"" - sv_common_finish - sv_env DISPLAY $DISPLAY - sv_end client_services+=( $sv_name_client ) } write_sv_definitions() { - write_sv_xorg - write_sv_server - write_sv_client - write_sv_xclipring primary - write_sv_xclipring clipboard } do_unlock() { zsystem flock -u $lockfd unset lockfd } start_services() { - s6-svc -wU -T 5000 -o $scandir/$sv_name_server local s for s in $client_services; do - s6-svc -o $scandir/$s done } print_cookie() { local -a cookies # Obtain authentication cookie cookies=( $(xauth -f /run/Xauthority.$display list :$display ) ) (( $#cookies == 3 )) || exit 1 [[ $cookies[2] == MIT-MAGIC-COOKIE-1 ]] || exit 1 printf '%s\t%s\n' $display $cookies[3] } main "$@"