#!/usr/bin/wish 
#
# xorriso-tcltk
# Copyright (C) 2012 - 2026
# Thomas Schmitt <scdbackup@gmx.net>, libburnia project.
# Provided under BSD license: Use, modify, and distribute as you like.
#
# This is mainly a proof of concept for xorriso serving under a frontend.
# It exercises several fundamental gestures of communication:
# - connecting via two named pipes
# - sending commands
# - receiving replies
# - inquiring the xorriso message sieve
# - using the xorriso parsing service
#
# It also may serve as educational frontend to xorriso which tells by
# its message window how to operate xorriso by commands and what it will
# reply.

# Note that any other language than Tcl/Tk could be used, if it only can
# do i/o via standard input and standard output or via named pipes.
# Further it has to perform integer arithmetics and string manipulations.
# And, well, a graphical widget set would be nice.


set own_version "1.5.8"

# Minimum version of xorriso to be used as backend process.
# Older versions of xorriso do not offer commands -msg_op and -launch_frontend
set min_xorriso_version "1.2.6"


proc print_usage {argv0} {
  puts stderr "Usage:"
  puts stderr "  $argv0 \[options\]"
  puts stderr "Options:"
  puts stderr " All options must be given with two dashes (\"--option\") in"
  puts stderr " order to distinguish them from any options of the Tcl shell."
  puts stderr " --help"
  puts stderr "     Print this text and exit."
  puts stderr " --stdio"
  puts stderr "     Establish connection to xorriso via stdin and stdout."
  puts stderr "     E.g. when letting xorriso start this frontend program:"
  puts stderr "       xorriso -launch_frontend \$(which xorriso-tcltk) --stdio --"
  puts stderr " --named_pipes cmd_fifo reply_fifo"
  puts stderr "     Establish connection to a xorriso process started by:"
  puts stderr "       xorriso -dialog on <cmd_fifo >reply_fifo"
  puts stderr "     which is then ready for a run of:"
  puts stderr "       xorriso-tcltk --named_pipes cmd_fifo reply_fifo"
  puts stderr "     It is important that the parent of xorriso and of this"
  puts stderr "     tcl/tk frontend opens the named pipe for commands before"
  puts stderr "     it opens the named pipe for replies. This avoids deadlock."
  puts stderr " --silent_start"
  puts stderr "     Do not issue the start message xorriso-tcltk-version."
  puts stderr "     This works only if --silent_start is the first argument."
  puts stderr " --no_extract"
  puts stderr "     Do not allow extraction of files from ISO filesystem to"
  puts stderr "     hard disk. This is not revokable during the program run."
  puts stderr " --no_bwidget"
  puts stderr "     Do not try to load the Tcl/Tk package BWidget which is"
  puts stderr "     a prerequisite for the \"/\" file browser buttons."
  puts stderr " --geometry {+|-}X{+|-}Y"
  puts stderr "     Sets the position of the main window."
  puts stderr " --click_to_focus"
  puts stderr "     Chooses that input fields and list boxes get the keyboard"
  puts stderr "     focus only when being clicked by the mouse. (Default)"
  puts stderr " --auto_focus"
  puts stderr "     Chooses that the keyboard focus is where the mouse"
  puts stderr "     pointer is."
  puts stderr " --pipe_log_file path"
  puts stderr "     Set a file address for logging of xorriso commands and"
  puts stderr "     reply messages and enable this logging."
  puts stderr "     The log lines will be appended. Path \"-\" means stderr."
  puts stderr " --script_log_file path"
  puts stderr "     Set a file address for logging of essential xorriso"
  puts stderr "     commands and enable this logging."
  puts stderr "     The log lines will be appended. Path \"-\" means stderr."
  puts stderr " --script_log_all_commands"
  puts stderr "     With logging of commands log non-essential commands too."
  puts stderr " --use_command_move"
  puts stderr "     Use xorriso command -move for the \"Move to:\" button"
  puts stderr "     if xorriso version is >= 1.2.8"
  puts stderr " --use_command_mv"
  puts stderr "     Use xorriso command -mv for the \"Move to:\" button."
  puts stderr ""
  puts stderr "If neither --stdio nor --named_pipes is given, then this script"
  puts stderr "will try to locate itself in the filesystem and start a xorriso"
  puts stderr "run that launches it again."
  puts stderr ""
  puts stderr "In the running GUI, click with the rightmost mouse button on"
  puts stderr "any GUI element to get its particular help text."
  puts stderr ""
}


# ------------------------------- the frontend ----------------------------
#
# Connects to a xorriso process, sends commands, receives replies,
# prepares replies for GUI

# Connection to xorriso
set cmd_conn ""
set reply_conn ""

# The addresses of the named pipes, if such are used (see option -named_pipe)
set cmd_pipe_adr ""
set reply_pipe_adr ""

# The command to send (or the command most recently sent)
set cmdline ""
# Whether to clear the cmdline after sending
set cmdline_clear true
# Command counter
set cmd_sent 0
# Current -mark synchronization text
set mark_count 0

# Results of most recent await_all_replies 
set info_list ""
set info_count 0
set emerging_info ""
set result_list ""
set result_count 0
set emerging_result ""

# Highest severities encountered in total and with most recent command
set highest_total_sev ALL
set highest_total_sev_msg ""
set highest_cmd_sev ALL
set highest_cmd_sev_msg ""
# This one registers like highest_cmd_sev with threshold ALL
set highest_seen_cmd_sev ALL

# State of last read_sieve command
set sieve_ret 0

# How many texts to pass with one parse_bulk command
set bulk_parse_max_chunk 200
# Parse parameters
set bulk_parse_prefix ""
set bulk_parse_separators ""
set bulk_parse_max_words ""
set bulk_parse_flag ""
# The announced number of texts to parse
set bulk_parse_num_texts ""

# Whether to complain on stderr about broken pipes.
# This may be expected when xorriso is being shut down by this frontend.
set expect_broken_pipes "0"

# Whether to use command -move rather than -mv. Possible since xorriso-1.2.8.
set use_command_move 1

# Whether to enable -hardlinks mode "on". Too slow before xorriso-1.3.0.
set use_command_hardlinks_on 1


# Local copies of xorriso state

# Addresses of drives (or image files) as shown by their text fields in the GUI
set outdev_adr ""
set indev_adr ""

# Addresses of drives (or image files) as set in xorriso (after inquire_dev)
set eff_outdev_adr ""
set eff_indev_adr ""

# Whether the medium is blank, appendable, closed, missing
set indev_medium_status "missing"
set outdev_medium_status "missing"

# What kind of medium is presented by the drive
set indev_profile ""
set outdev_profile ""

# List of known drive addresses
set devlist ""

# Intermediate storage for messages until the GUI is up with .msglist box
set pre_msglist ""

# Whether overwriting of files in the ISO model is allowed
set overwrite_iso_files 1

# If overwrite_iso_files is 1: Whether overwriting of ISO directories is allowed
set overwrite_iso_dirs 0

# Whether overwriting of files on disk is allowed
set overwrite_disk_files 0

# The file where to log commands and replies for debugging purposes
set debug_log_file ""
set debug_log_conn stderr

# Whether to log all commands and replies to the debug_log_file 
set debug_logging 0

# The result of the most recent isofs_ls run
set isofs_ls_result ""

# The result of the most recent localfs_ls run
set localfs_ls_result ""

# The communication channel where to log files (if it is not the empty text)
set cmd_log_conn ""

# The address under which cmd_log_conn was opened
set cmd_log_target ""

# Whether to log essential commands: 0=off , 1=no extract , 2=with extract
set cmd_logging_mode 0

# Whether to log all commands if cmd_logging_mode is 1: 0=off , 1=on
set cmd_logging_all 0

# The last logged -cd path. Used to prevent redundant logging of -cd.
set recent_cd_path ""

# The file address and the channel for xorriso command script execution
set execute_script_adr ""
set execute_script_conn ""

# Whether extraction to disk shall be allowed in scripts
set script_with_osirrox 0

# Whether extraction to disk is allowed
set osirrox_allowed 1


# xorriso specific constants

# List of severities (gets later overridden by -msg_op list_sev -)
set xorriso_severity_list {
  ALL DEBUG UPDATE NOTE HINT WARNING SORRY MISHAP FAILURE FATAL ABORT NEVER
}
set scan_event_threshold HINT


# --------- Communication between frontend and xorriso ----------


# Open the connection to a pair of named pipes. Program option -named_pipes
#
proc init_frontend_named_pipes {cmd_pipe reply_pipe} {
  global cmd_conn
  global reply_conn

  set cmd_conn [open $cmd_pipe w]
  set reply_conn [open $reply_pipe r]

  # Note: dissuaded flags would be necessary for opening idle fifo
  # set reply_conn [open $reply_pipe {RDONLY NONBLOCK}]

}


# Send a command line to the xorriso process. Do not wait for reply.
#
proc send_async_cmd {cmd} {
  global cmd_sent cmd_conn debug_logging debug_log_conn

  display_busy 1
  log_command $cmd 0

  debug_log_puts \
         "     =============================================================="
  debug_log_puts "       $cmd"
  display_msg "======>  $cmd" 
  incr cmd_sent
  puts $cmd_conn $cmd
  flush $cmd_conn
}


# Send a command line and a -mark command to xorriso. Wait until the
# mark message confirms that all command output has been received.
#
proc send_marked_cmd {cmd} {
  global cmd_conn mark_count

  send_async_cmd $cmd
  incr mark_count
  set mark_cmd "-mark $mark_count"
  debug_log_puts "       $mark_cmd"
  puts $cmd_conn $mark_cmd
  flush $cmd_conn
  await_all_replies
}


# Send a command and make it a candidate for the log script
#
proc send_loggable_cmd {cmd} {
  log_command $cmd 1
  send_marked_cmd $cmd
}


# Send a command that shall not be displayed in the message log
#
proc send_silent_cmd {cmd} {
  set disp_en_mem [set_display_msg 0]
  send_marked_cmd $cmd
  set_display_msg $disp_en_mem
}


# Wait for the currently pending mark message to arrive.
# Buffer all received result lines and info messages.
#
proc await_all_replies {} {
  global reply_conn mark_count result_count result_list
  global info_count info_list expect_broken_pipes
  global .busy_text 

  clear_reply_lists

  while {1} {
    set ret [gets $reply_conn line]
    if {$ret < 0} {
      if {$expect_broken_pipes != 1} {
        puts stderr "EOF at reply pipe"
      }
  break
    }
    debug_log_puts $line
    if {[string range $line 0 0] == "M"} {
       if {[string range $line 5 end] == $mark_count} {
  break
       } else {
         # outdated mark message
  continue
       }
    }

    de_pkt_line $line
  }

  display_busy 0
}


# Decode -pkt_output format to complete lines and buffer them.
#
proc de_pkt_line {line} {
  global info_list
  global info_count
  global emerging_info
  global result_list
  global result_count
  global emerging_result

  # Distinguish R and I
  set ch [string range $line 0 0]

  set payload [string range $line 5 end]
  if {$ch == "R"} {
    set emerging_result "$emerging_result$payload"
  } else { if {$ch == "I"} {
    set emerging_info "$emerging_info$payload"
  } else {
    return ""
  }}

  # if line end : add to list
  if {[string range $line 2 2] == "1"} {
    if {$ch == "R"} {
      lappend result_list $emerging_result
      incr result_count
      display_msg $emerging_result
      set emerging_result ""
    } else {
      lappend info_list $emerging_info
      incr info_count
      display_msg $emerging_info
      scan_info_for_event $emerging_info
      set emerging_info ""
    }
  }
}


# Search in the decoded info messages for the most severe event reports.
#
proc scan_info_for_event {line} {
  global highest_total_sev highest_total_sev_msg
  global highest_cmd_sev highest_cmd_sev_msg highest_seen_cmd_sev
  global scan_event_threshold
  global display_msg_enabled

  # check for word : CAPS : text ...
  set ret [regexp {[a-z][a-z]*[ ]*: [A-Z][A-Z]* :} $line]
      
  if {$ret != 1} {return ""}

  # retrieve severity
  set pos [string first ":" $line]
  set sev [string range $line [expr $pos+2] end]
  set pos [string first ":" $sev]
  set sev [string range $sev 0 [expr $pos-2]];

  if {[compare_sev $sev $highest_seen_cmd_sev] > 0} {
    set highest_seen_cmd_sev $sev
  }

  if {[compare_sev $sev $scan_event_threshold] < 0} {return ""}

  if {$display_msg_enabled == 0} {
    set display_msg_enabled 1
    display_msg $line
    set display_msg_enabled 0
  }
  if {[compare_sev $sev $highest_total_sev] >= 0} {
    set highest_total_sev $sev
    set highest_total_sev_msg [escape_newline $line 0]
  }
  if {[compare_sev $sev $highest_cmd_sev] >= 0} {
    set highest_cmd_sev $sev
    set highest_cmd_sev_msg [escape_newline $line 0]
  }
}


# Unpack the output format of -msg_op read_sieve into a result_list
# of strings which each hold one parsed word.
#
proc de_sieve {} {
  global result_list
  global sieve_ret
  
  set sieve_ret [lindex $result_list 0]
  set sieve_result_count [lindex $result_list 1]

  set payload ""
  set sieve_result_count 0
  for {set i 2} {$i < [llength $result_list]} {incr i} {
    set line ""
    set num_lines [lindex $result_list $i]
    for {set j 0} {$j < $num_lines} {incr j} {
      incr i
      set line "$line[lindex $result_list $i]"
      if {$j < $num_lines - 1} {
        set line "$line\n"
      } else {
        lappend payload $line
        incr sieve_result_count  
      }  
    }
  }
  set result_list $payload
  set result_count $sieve_result_count
}


# Alternative to proc await_all_replies. It waits for a line at one of the
# three channels and displays all lines which it receives before that line.
# Used before this frontend had the opportunity to set up xorriso by commands
# like -pkt_output "on".
#
proc wait_for_msg {prefix channel} {
  global reply_conn

  if {$channel == "M"} {
    set channel_prefix "M:0: "
  } else {
    set channel_prefix "$channel:1: "
  }

  set prefix_l [string length $prefix]
  while {1} {

    # >>> Have a timeout

    set ret [gets $reply_conn line]
    if {$ret < 0} {
  break
    }
    debug_log_puts $line
    if {[string length $line] < $prefix_l} {
      display_msg $line
  continue
    }
    if {[string range $line 0 [expr $prefix_l-1]] == $prefix} {
      return [string range $line $prefix_l end]
    }
    if {[string length $line] >= [expr $prefix_l+5]} {
      if {[string range $line 0 4] == $channel_prefix} {
        if {[string range $line 5 [expr $prefix_l+4]] == $prefix} {
          return [string range $line [expr $prefix_l+5] end] 
        }
      }
    }
    display_msg $line
  }
}


# Reset the buffer for result lines and info messages.
#
proc clear_reply_lists {} {
  global info_list
  global info_count
  global emerging_info
  global result_list
  global result_count
  global emerging_result

  set info_list ""
  set info_count 0
  set emerging_info ""
  set result_list ""
  set result_count 0
  set emerging_result ""
}


# Reset the register of the most severe event for command sequences.
# Typically this is done before executing the commands of a procedure
# that is triggered by the user interface.
#
proc reset_highest_cmd_sev {} {
  global highest_cmd_sev highest_cmd_sev_msg highest_seen_cmd_sev

  set highest_cmd_sev ALL
  set highest_cmd_sev_msg ""
  set highest_seen_cmd_sev ALL
}


# Clear the recordings of the xorriso message sieve.
#
proc clear_sieve {} {
  send_silent_cmd "-msg_op clear_sieve -"
}


# Obtain a recorded item from the xorriso message sieve.
#
proc read_sieve {name} {
  send_silent_cmd "-msg_op read_sieve '$name'"
  de_sieve
}


# ------- Inquiring xorriso status -------


# Get more information about drive that was inquired by recent -toc_of.
#
proc obtain_drive_info {dev} {
  global result_list
  global sieve_ret
  global indev_medium_status outdev_medium_status
  global indev_profile outdev_profile

  set line ""

  if {$dev == "in"} {
    set indev_medium_status "missing"
    set indev_profile ""
  } else {
    set outdev_medium_status "missing"
    set outdev_profile ""
  }
  read_sieve "Media status :"
  if {$sieve_ret > 0} {
    set reply [lindex $result_list 0]
    foreach i {blank appendable closed} {
      if {[string first $i $reply] != -1} {
        set line "$i "
        if {$dev == "in"} {
          set indev_medium_status $i
        } else {
          set outdev_medium_status $i
        }
    break
      }
    }
  }
  read_sieve "Media current:"
  if {$sieve_ret > 0} {
    set profile [lindex $result_list 0]
    if {$profile == "is not recognizable"} {
      set profile "no recognizable medium"
      set line "$line$profile"
      return $line
    } else {
      set line "$line$profile, "
      if {$dev == "in"} {
        set indev_profile $profile
      } else {
        set outdev_profile $profile
      }
    }
  }
  read_sieve "Media summary:"
  if {$sieve_ret > 0} {
    set line "$line[lindex $result_list 0] sessions, "
    if {$dev == "in"} {
      set line "$line[lindex $result_list 2] used"
    } else {
      set line "$line[lindex $result_list 3] free"
    }
  }
  return $line
}


# Inquire whether changes of the ISO image are pending.
# This is a precondition for writing the session. Vice versa pending changes
# block a change of the input drive or the program end.
#
proc changes_are_pending {} {
  global result_count result_list

  send_silent_cmd "-changes_pending show_status"
  if {$result_count >= 1} {
    if {[lindex $result_list 0] == "-changes_pending no"} {
      return "0"
    }
    return "1"
  }
  return ""
}


# Inquire whether an ISO image model has been created inside xorriso.
# This is a precondition for inserting files into the ISO tree model.
#
proc assert_iso_image {with_msg} {
  global highest_seen_cmd_sev scan_event_threshold

  set highest_seen_cmd_sev ""
  set set_mem $scan_event_threshold
  set scan_event_threshold "FATAL"
  send_silent_cmd "-lsd / --"
  set scan_event_threshold $set_mem
  if {[compare_sev $highest_seen_cmd_sev "FAILURE"] >= 0} {
    if {$with_msg == 1} {
      xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : First you need to create or load an ISO image by selecting a drive or an image file"
    }
    return "0"
  }
  return "1"
}


# Obtain the list of possible event severity names, sorted in ascending order
#
proc inquire_severity_list {} {
  global xorriso_severity_list
  global result_list

  set disp_en_mem [set_display_msg 0]
  send_marked_cmd "-msg_op list_sev -"
  set_display_msg $disp_en_mem
  if {[lindex $result_list 0] != ""} {
    set xorriso_severity_list [split [lindex $result_list 0] " "]
  }
}


# Parse-by-xorriso handler function for proc inquire_dev
#
proc set_inquired_dev {} {
  global result_list eff_indev_adr eff_outdev_adr

  if {[llength $result_list] < 2} {return ""} 
  set what [lindex $result_list 0]
  if {$what == "-dev" || $what == "-indev"} {
    set eff_indev_adr [lindex $result_list 1]
  }
  if {$what == "-dev" || $what == "-outdev"} {
    set eff_outdev_adr [lindex $result_list 1]
  }
}


# Inquire -indev and -outdev from xorriso and install in eff_indev_adr
# and eff_outdev_adr.
# (This could be done by -toc_of like in proc refresh_indev. But here
#  i demonstrate the use of command -status and parsing its result by
#  help of xorriso.)
#
proc inquire_dev {} {
  set disp_en_mem [set_display_msg 0]
  send_marked_cmd "-status -dev"
  handle_result_list set_inquired_dev "''" "''" 2 0
  set_display_msg $disp_en_mem
  update idletasks
  return ""
}


# Inquire -indev and -outdev from xorriso and install in indev_adr
# and outdev_adr.
#
proc update_dev_var {} {
  global result_list eff_indev_adr eff_outdev_adr indev_adr outdev_adr

  inquire_dev
  set indev_adr $eff_indev_adr
  set outdev_adr $eff_outdev_adr
}


# Parse-by-xorriso handler function for proc isofs_ls
#
proc isofs_ls_handler {} {
  global result_list isofs_ls_result

  if {[lindex $result_list 0] == "total"} {return ""}
  lappend isofs_ls_result \
       "[string range [lindex $result_list 0] 0 0] [lindex $result_list 8]"
}


# Produce a list of all files in a directory of the ISO model
#
proc isofs_ls {dir} {
  global isofs_ls_result

  set isofs_ls_result ""
  set disp_en_mem [set_display_msg 0]
  send_marked_cmd "-lsl [make_text_shellsafe $dir]"
  handle_result_list isofs_ls_handler "''" "''" 0 0
  set_display_msg $disp_en_mem
  return $isofs_ls_result
}


# Tells the file type of an absolute path in the ISO model.
# Indicator characters like with ls -l. Empty text means non existing file.
#
proc isofs_filetype {path} {
  global result_list result_count scan_event_threshold

  set scan_event_mem $scan_event_threshold
  set scan_event_threshold "SORRY"
  send_silent_cmd "-lsdl [make_text_shellsafe $path]"
  set scan_event_threshold $scan_event_mem
  if {$result_count < 1} {return ""} 
  return [string range [lindex $result_list 0] 0 0]
}


# Inspection of hard disk is done via xorriso.
# The xorriso commands have the advantage to be always available and to
# need no unescaping. On the other hand, shell and tcl lstat would be
# faster with large directories.

# Parse-by-xorriso handler function for proc localfs_ls
#
proc localfs_ls_handler {} {
  global result_list localfs_ls_result

  if {[lindex $result_list 0] == "total"} {return ""}
  lappend localfs_ls_result \
       "[string range [lindex $result_list 0] 0 0] [lindex $result_list 8]"
}


# Return the list of files of a hard disk filesystem directory
#
proc localfs_ls {dir} {
  global localfs_ls_result

  set localfs_ls_result ""
  if {[localfs_filetype $dir] != "d"} {return ""}

  set disp_en_mem [set_display_msg 0]
  send_marked_cmd "-lslx [make_text_shellsafe $dir]"
  handle_result_list localfs_ls_handler "''" "''" 0 0
  set_display_msg $disp_en_mem
  return $localfs_ls_result
}


# Tells the file type of an absolute path in the ISO model.
# Indicator characters like with ls -l. Empty text means non existing file.
#
proc localfs_filetype {path} {
  global result_list result_count scan_event_threshold

  set scan_event_mem $scan_event_threshold
  set scan_event_threshold "SORRY"
  send_silent_cmd "-lsdlx [make_text_shellsafe $path]"
  set scan_event_threshold $scan_event_mem
  if {[llength $result_list] < 1} {return ""} 
  return [string range [lindex $result_list 0] 0 0]
}


# Verify that the connected process runs a xorriso program that is modern
# enough. This is done before sending xorriso the setup commands.
#
proc check_xorriso_version {} {
  global sieve_ret result_list pre_msglist xorriso_version min_xorriso_version
  global use_command_move use_command_hardlinks_on
  global reply_conn

  set version "0.0.0 (= unknown)"

  set disp_en_mem [set_display_msg 0]

  # In order to see the pre-frontend messages of xorriso
  # set an individual -mark and use send_async_cmd

  set mark_text "xorriso-tcltk-version-check-[clock seconds]"
  send_async_cmd "-mark [make_text_shellsafe $mark_text]"
  set_display_msg $disp_en_mem
  wait_for_msg $mark_text "M"

  set_display_msg 0
  send_async_cmd "-version"

  set xorriso_version [wait_for_msg "xorriso version   :  " "R"]

  if {$xorriso_version < $min_xorriso_version} {
    puts stderr "xorriso-tcltk: xorriso-$xorriso_version is too old."
    puts stderr "xorriso-tcltk: Need at least version $min_xorriso_version"
    window_ack \
    "xorriso-$xorriso_version is too old. Need at least version $min_xorriso_version" \
              "red" "embedded"
    central_exit 2
  }
  if {$xorriso_version < "1.2.8"} {
    set use_command_move 0
  }
  if {$xorriso_version < "1.3.0"} {
    set use_command_hardlinks_on 0
  }
  set_display_msg $disp_en_mem
}


# Commands which bring the connected xorriso process into the state that
# is expected by this frontend.
#
proc setup_xorriso {} {
  global osirrox_allowed

  set cmd ""

  # Invalidate possible -mark 1
  set cmd "$cmd -mark 0" 

  # Make replies digestible for await_all_replies
  set cmd "$cmd -pkt_output on"

  # Report version early
  set cmd "$cmd -version"

  # This frontend relies heavily on the message sieve
  set cmd "$cmd -msg_op start_sieve -"

  # -reassure questions from xorriso would not be properly handled by
  # this frontend
  set cmd "$cmd -reassure off"

  set cmd "$cmd -abort_on NEVER"
  set cmd "$cmd -return_with ABORT 32"
  set cmd "$cmd -report_about UPDATE"
  set cmd "$cmd -iso_rr_pattern off"
  set cmd "$cmd -disk_pattern off"

  if {$osirrox_allowed == 0} {
    set cmd "$cmd -osirrox banned"
  }

  set cmd "$cmd [xorriso_loggable_init_cmds]"

  send_marked_cmd $cmd
  inquire_severity_list
}


# Commands which should also be at the start of a log script
#
proc xorriso_loggable_init_cmds {} {
  global use_command_hardlinks_on

  set cmd "-for_backup"

  # Before xorriso-1.3.0 there is a performance problem with -hardlinks "on"
  # and image manipulations before xorriso-1.3.0.
  if {$use_command_hardlinks_on == 0} {
    set cmd "$cmd -hardlinks off"
  } else {
    set cmd "$cmd -hardlinks on"
  }

  set cmd "$cmd -backslash_codes on"
  set cmd "$cmd -follow mount:limit=100"
  return $cmd
}


proc effectuate_permission_policy {} {
  global permission_policy

  if {$permission_policy == "readable"} {
    send_loggable_cmd \
             "-find / -exec chmod a+r -- -find / -type d -exec chmod a+x --"
  }
  if {$permission_policy == "readonly"} {
    send_loggable_cmd \
             "-find / -exec chmod a=r -- -find / -type d -exec chmod a+x --"
  }
  if {$permission_policy == "mkisofs_r"} {
    send_loggable_cmd \
             "-find / -exec mkisofs_r"
  }
}


# ------ Parsing by help of xorriso ------

# Parsing by xorriso takes from the frontend the burden to understand
# and implement the quoting rules of xorriso input and output.
# Lines which are supposed to consist of several words get sent to
# xorriso command -msg_op. The result lines of this command encode
# the words unambiguously in one or more text lines.
# This is supposed to be safe for even the weirdest file names.
# Only NUL characters cannot be part of names.


# Start a bulk parser job by which xorriso shall split the output of e.g. -lsl
# into single words from which this frontend can pick information.
#
proc start_bulkparse {prefix separators max_words flag num_lines} {
  global bulk_parse_prefix bulk_parse_separators
  global bulk_parse_max_words bulk_parse_flag bulk_parse_num_texts

  if {$num_lines <= 0} {return ""}

  set bulk_parse_prefix $prefix
  set bulk_parse_separators $separators
  set bulk_parse_max_words $max_words
  set bulk_parse_flag $flag
  set bulk_parse_num_texts $num_lines
  set cmd "-msg_op parse_bulk \"$prefix $separators $max_words $flag $num_lines\""
  send_async_cmd $cmd
  # Do not wait for mark
}


# Submit a new input line to the xorriso bulk parser job.
#
proc submit_bulkparse {text} {
  global cmd_conn reply_conn
  global result_list result_count
  global bulk_parse_prefix bulk_parse_separators
  global bulk_parse_max_words bulk_parse_flag

  set disp_en_mem [set_display_msg 0]

  set num_lines [expr [count_newlines $text] + 1]

  debug_log_puts ">>>>>  $num_lines"
  puts $cmd_conn $num_lines
  debug_log_puts ">>>>>  $text"
  puts $cmd_conn $text
  flush $cmd_conn
  set_display_msg $disp_en_mem
}


# Read the expected number of bulk parsing replies into the result buffer
# and call handler_proc to inspect them.
# Each input line of the parser yields one reply buffer full of parsed words.
#
proc read_bulkparse {handler_proc num_texts} {
  set disp_en_mem [set_display_msg 0]
  for {set i 0} {$i < $num_texts} {incr i} {
    clear_reply_lists
    read_parse_reply
    $handler_proc
  }
  set_display_msg $disp_en_mem
}


# Read and decode the xorriso parser reply for one input line.
#
proc read_parse_reply {} {
  global reply_conn
  global result_list result_count

  set sieve_result_count 0
  set payload ""
  set num_lines 0
  set acc ""
  set loop_limit 2
  while {$result_count < $loop_limit} {
    set ret [gets $reply_conn line]
    if {$ret < 0} { return ""}
    debug_log_puts $line
    de_pkt_line $line
    set line [lindex $result_list [expr $result_count-1]]
    if {$result_count == 1} {
      set parse_ret $line
    } else { if {$result_count == 2} {
      set num_replies $line
      # The minimum number of lines
      set loop_limit [expr "$num_replies * 2 + 2"]
    } else {
      if {$num_lines <= 0} {
        set num_lines $line
        if {$num_lines > 1} {
          # Need to read extra lines
          incr loop_limit [expr $num_lines-1]
        }
        set acc ""
      } else {
        incr num_lines -1
        if {$acc != ""} {
          set acc "$acc\n$line"
        } else {
          set acc $line
        }
        if {$num_lines <= 0} {
          lappend payload $acc
          incr sieve_result_count
        }
      }
    }}
  }
  set result_list $payload
  set result_count $sieve_result_count
}


# Let xorriso parse the lines in the result buffer and call handler_proc
# with the parse result of each line.
# This is used to split the result lines of -lsl into words from which
# handler proc isolist_parse_handler picks the info which it displays
# in .stbox isolist .
# Note that all parameters must be xorriso words. E.g. empty prefix or
# separator have to be submitted as tcl string "''" rather than "".
#
proc handle_result_list {handler_proc \
                         prefix separators max_words flag } {
  global result_list
  global bulk_parse_max_chunk

  set raw_list $result_list
  set raw_line_count [expr [llength $raw_list]]
  if {$raw_line_count > $bulk_parse_max_chunk} {
    set chunk_size $bulk_parse_max_chunk
  } else {
    set chunk_size $raw_line_count
  }
  start_bulkparse $prefix $separators $max_words $flag $chunk_size
  set submit_count 0
  set submit_in_chunk_count 0
  foreach i $raw_list {
    submit_bulkparse $i
    incr submit_count
    incr submit_in_chunk_count
    if {$submit_in_chunk_count == $chunk_size} {
      read_bulkparse $handler_proc $chunk_size
      set todo [expr "$raw_line_count - $submit_count"]
      if {$todo <= 0} {
  break
      }
      if {$todo > $bulk_parse_max_chunk} {
        set chunk_size $bulk_parse_max_chunk
      } else {
        set chunk_size $todo
      }
      start_bulkparse $prefix $separators $max_words $flag \
                      $chunk_size
      set submit_in_chunk_count 0
    }
  }
  display_busy 0
}


# ------------------------------- the GUI ----------------------------

# ------ State variables ------

# Whether to display messages in .msglist 
set display_msg_enabled 1

# Whether a device list is already displayed
set devices_scanned 0

# Currently displayed ISO directory
set isodir_adr ""
set isodir_is_pwd 0

# The plain names and types matching listbox .isolist
set isolist_names ""
set isolist_types ""

# The name which to select after isodir_return
set isodir_return_name ""

# The address where to move selected ISO files
set isomanip_move_target ""

# Memorized isolist selection
set memorized_isolist_selection ""

# Image file address for .burn_write_image
set burn_write_image_adr ""

# Whether to close medium after writing
set burn_write_close 0 

# Whether to force CD TAO, DVD-R Inremental, DVD+R/BD-R open ended track
set burn_write_tao 0

# Whether to engage Defect Management on formatted BD media
set burn_write_defect_mgt 0

# Answer of yes/no window
set answer_of_yesno ""

# Semi-persistent answers of yes/no window
set yesno_to_all 0

# The hard disk filesystem address to be mapped into isodir_adr
set insert_from_adr ""

# Whether to insert with leafname of insert_from_adr underneath isodir_adr
# (else: -map $insert_from_adr $isodir_adr) 
set insert_underneath 1

# Whether to insert at or under the selected .isolist item
# rather than isodir_adr
set insert_at_selected 0

# The hard disk filesystem address to which to extract from isodir_adr
set extract_to_adr ""

# Whether to insert with leafname of insert_from_adr underneath isodir_adr
# (else: -map $insert_from_adr $isodir_adr) 
set extract_underneath 1

# Whether to insert at or under the selected .isolist item
set extract_from_selected 1

# Whether to temporarily enforce rwx permissions for target directories on disk
set extract_auto_chmod 0

# Whether the display label .busy_text is already usable
set busy_text_exists 0

# Whether to demand a click before focus goes to entry or listbox
set click_to_focus 1

# Whether .ack_window , .yesno_window , .help_window, .main_help_window
# are already displayed.
set ack_window_is_active 0
set yesno_window_is_active 0
set help_window_is_active 0
set main_help_window_is_active 0

# Positions of above windows when they were last closed
set yesno_window_geometry ""
set ack_window_geometry ""
set help_window_geometry ""
set main_help_window_geometry ""

# Whether the help window already has a scroll bar
set help_window_has_scroll 0

# Whether there is the BWidget package available: 0=unknown, 1=yes, -1=banned
#
set have_bwidget 0
set bwidget_version ""

# Whether the .browse_disk_window is already displayed
set browse_disk_window_is_active 0
set browse_disk_window_var ""
# Position of window when it was last closed
set browse_disk_window_geometry ""
# Whether the window is grabbed
set browse_disk_window_is_grabbed 0

# Whether the .browse_iso_window is already displayed
set browse_iso_window_is_active 0
set browse_iso_window_var ""
# Position of window when it was last closed
set browse_iso_window_geometry ""
# Whether the window is grabbed
set browse_iso_window_is_grabbed 0

# Whether to modify the ISO file permissions before writing the ISO session
# Policies: as_is , readable , readonly , mkisofs_r
set permission_policy "as_is"


# ------ GUI callback procedures ----


# Called when the Return key is hit in commandline.
#
proc cmdline_return {} {
  global cmdline cmdline_clear
  global .cmdline .cmdline_text .cmdline_entry
  global highest_cmd_sev
  global highest_cmd_sev_msg

  reset_highest_cmd_sev
  set_display_msg 1
  send_loggable_cmd $cmdline
  set cmdline ""

  # To force display of GUI changes now and not some time later
  update idletasks
}


# Called when the input drive address shall be brought into effect with
# xorriso.
#
proc indev_return {} {
  global indev_adr
  global .indev_entry
  global .outdev_entry

  if {[assert_no_changes] == 0} {
    update_dev_var
    return "0"
  }
  reset_highest_cmd_sev
  send_loggable_cmd "-indev [make_text_shellsafe $indev_adr]"
  set indev_mem_adr $indev_adr
  .indev_entry icursor 0
  refresh_indev
  return "1"
}


# Called when the "Eject" button for the input drive is hit.
#
proc eject_indev {} {
  if {[assert_no_changes] == 0} {return ""}
  reset_highest_cmd_sev
  send_loggable_cmd "-eject indev"
  refresh_outdev
  refresh_indev
}


# Called when the output drive address shall be brought into effect with
# xorriso.
#
proc outdev_return {} {
  global outdev_adr indev_adr
  global .outdev_entry

  reset_highest_cmd_sev
  send_loggable_cmd "-outdev [make_text_shellsafe $outdev_adr]"
  set outdev_mem_adr $outdev_adr
  .outdev_entry icursor 0
  refresh_outdev
  return "1"
}


# Called when the "Eject" button for the output drive is hit.
#
proc eject_outdev {} {
  global outdev_adr indev_adr

  if {$outdev_adr == $indev_adr} {
    if {[assert_no_changes] == 0} {return ""}
  }
  reset_highest_cmd_sev
  send_loggable_cmd "-eject outdev"
  refresh_outdev
  refresh_indev
}


# Called when both drive addresses shall be brought into effect with xorriso.
#
proc dev_return {} {
  global outdev_adr indev_adr
  global .outdev_entry .indev_entry

  if {$outdev_adr != $indev_adr} {
    if {[indev_return] == 0} {return "0"}
    outdev_return
  } else {
    if {[assert_no_changes] == 0} {
      update_dev_var
      return "0"
    }
    reset_highest_cmd_sev
    send_loggable_cmd "-dev [make_text_shellsafe $outdev_adr]"
    .outdev_entry icursor 0
    refresh_outdev
    .indev_entry icursor 0
    refresh_indev
  }
}


# Obtain and display the input drive status.
# Called after the input drive address may have changed.
#
proc refresh_indev {} {
  global result_list
  global indev_adr
  global sieve_ret
  global .indev_summary

  .indev_summary configure -text ""
  set indev_adr ""
  update idletasks

  set disp_en_mem [set_display_msg 0]
  clear_sieve
  send_marked_cmd "-toc_of in:short"
  read_sieve "Drive current:"
  set_display_msg $disp_en_mem
  if {$sieve_ret > 0} {
    set cmd [lindex $result_list 0]
    if {$cmd == "-indev" || $cmd == "-dev"} {
      set indev_adr [lindex $result_list 1]
    }
    set line [obtain_drive_info in]
    .indev_summary configure -text $line
  }
  .avail_label configure -text ""
  update idletasks
  isodir_return "refresh_indev"
}


# Obtain and display the output drive status.
# Called after the output drive address may have changed.
#
proc refresh_outdev {} {
  global result_list
  global outdev_adr
  global sieve_ret

  .outdev_summary configure -text ""
  set outdev_adr ""
  update idletasks

  set disp_en_mem [set_display_msg 0]
  clear_sieve
  send_marked_cmd "-toc_of out:short"
  read_sieve "Drive current:"
  set_display_msg $disp_en_mem
  if {$sieve_ret > 0} {
    set cmd [lindex $result_list 0]
    if {$cmd == "-outdev" || $cmd == "-dev"} {
      set outdev_adr [lindex $result_list 1]
    }
    set line [obtain_drive_info out]
    .outdev_summary configure -text $line
  }
  .avail_label configure -text ""
  update idletasks
}


# Scan the system for optical drives with rw permission
# Called when the "Scan for drives button" is hit.
#
proc scan_for_drives {} {
  global .drivelist .drive_drop_both .drive_scan
  global sieve_ret result_list devlist devices_scanned indev_adr outdev_adr

  if {[assert_no_changes] == 0} {return ""}
  if {$indev_adr != "" || $outdev_adr != ""} {
    if {[window_yesno \
            "Really give up acquired drives for scanning a new drive list ?"] \
      != 1} { return "" }
  }

  set max_idx [.drivelist index end]
  .drivelist delete 0 [expr $max_idx-1]
  set devlist ""

  reset_highest_cmd_sev
  clear_sieve
  send_loggable_cmd "-devices"
  set max_idx 0
  while {1} {
    read_sieve "?  -dev"
    if {$sieve_ret > 0} {
      .drivelist insert end "[lindex $result_list 0] : [lindex $result_list 2] [lindex $result_list 3]"
      lappend devlist [lindex $result_list 0]
    } else {
  break
    }
  }
  while {1} {
    read_sieve "??  -dev"
    if {$sieve_ret > 0} {
      .drivelist insert end "[lindex $result_list 0] : [lindex $result_list 2] [lindex $result_list 3]"
      lappend devlist [lindex $result_list 0]
    } else {
  break
    }
  }
  set devices_scanned 1
  reset_to_normal_background .drive_scan

  # Command -devices drops all acquired drives
  refresh_outdev
  refresh_indev
}


# Refresh the display after some xorriso may have changed the status
# Called by the "Refresh disp" button and others.
#
proc refresh_state {} {
  refresh_indev
  refresh_outdev
}


# Reset the recorded Worst problem message.
# Called when the "Clear" button is hit.
#
proc clear_total_errmsg {} {
  global highest_total_sev
  global highest_total_sev_msg

  set highest_total_sev ALL
  set highest_total_sev_msg ""
  update idletasks
}


# Called when the "Pick input drive button" is hit.
#
proc pick_indev {} {
  pick_drive indev
}


# Called when the "Pick output drive button" is hit.
#
proc pick_outdev {} {
  pick_drive outdev
}


# Called when the "Pick drive for both roles" button is hit.
# or when an item in the scanned drive list is double-clicked.
#
proc pick_dev {} {
  pick_drive dev
}


# Perform the actual work of pick_dev, pick_indev, and pick_outdev
#
proc pick_drive {role} {
  global .drivelist
  global devlist
  global highest_cmd_sev_msg outdev_adr indev_adr devices_scanned

  set selected [.drivelist curselection]
  if {[llength $selected] != 1} {
    set must_scan ""
    if {$devices_scanned == 0} { set must_scan " scan and"}
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : First you must$must_scan select a single drive"
    return ""
  }
  set drive_idx [lindex $selected 0]

  if {$role == "dev"} {
    set outdev_adr [lindex $devlist $drive_idx]
    set indev_adr [lindex $devlist $drive_idx]
    dev_return
  }
  if {$role == "outdev"} {
    set outdev_adr [lindex $devlist $drive_idx]
    outdev_return
  }
  if {$role == "indev"} {
    set indev_adr [lindex $devlist $drive_idx]
    indev_return
  }
  .drivelist selection clear 0 end  
}


# Called when the "Give up drives" button is hit.
#
proc give_up_dev {} {
  global outdev_adr indev_adr

  if {[assert_no_changes] == 0} {return ""}
  set outdev_adr ""
  outdev_return
  set indev_adr ""
  indev_return
}


# Obtain and display the content of the current ISO directory.
# Called when the Return key is hit in the .isodir_entry text field
# and by many others which change variable isodir_adr or the
# content of the directory in xorriso's tree model.
#
proc isodir_return {caller} {
  global isodir_adr result_list isolist_names isolist_types isodir_return_name
  global isodir_is_pwd highest_cmd_sev highest_cmd_sev_msg
  global indev_adr outdev_adr eff_indev_adr
  global .isolist
  global bulk_parse_max_chunk

  set chunk_size 0

  set max_idx [.isolist index end]
  .isolist delete 0 [expr "$max_idx-1"]
  update idletasks
  set isolist_names ""
  set isolist_types ""

  inquire_dev
  if {$eff_indev_adr == "" && [changes_are_pending] == "0"} {return ""}

  normalize_isodir_adr
  set file_type [isofs_filetype $isodir_adr]
  if {$file_type != "d" && $file_type != ""} {
    .isolist insert end "@@@ exists but is not a directory @@@"
    set isodir_is_pwd 0
    return ""
  }
  set disp_en_mem [set_display_msg 0]
  set highest_cmd_sev_mem $highest_cmd_sev
  set highest_cmd_sev_msg_mem $highest_cmd_sev_msg
  reset_highest_cmd_sev
  send_loggable_cmd "-cd [make_text_shellsafe $isodir_adr]"
  if {[compare_sev $highest_cmd_sev "WARNING"] < 0} {
    send_marked_cmd "-lsl --"
    set isodir_is_pwd 1
  } else {
    send_marked_cmd "-lsl [make_text_shellsafe $isodir_adr] --"
    set isodir_is_pwd 0
  }
  handle_result_list isolist_parse_handler "''" "''" 0 0
  set_display_msg $disp_en_mem
  set highest_cmd_sev $highest_cmd_sev_mem
  set highest_cmd_sev_msg $highest_cmd_sev_msg_mem

  if {$isodir_return_name != ""} {
    set idx [lsearch -exact $isolist_names $isodir_return_name]
    if {$idx != -1} {
      .isolist see $idx
      .isolist selection set $idx
    }
    set isodir_return_name ""
  }
  update idletasks
}


# The handler procedure that is submitted to proc handle_result_list
# and will be called for every parsed line.
# It records file names and types in separate lists and displays them
# in the .isolist box.
#
proc isolist_parse_handler {} {
  global result_list isolist_names isolist_types
  global .isolist

  if {[lindex $result_list 0] == "total"} {return ""}
  set name [lindex $result_list 8]
  set ftype [string range [lindex $result_list 0] 0 0]
  lappend isolist_names $name
  lappend isolist_types $ftype
  .isolist insert end "$ftype   $name"
}


# Make current the ISO directory that was selected from the .isolist box.
# Called when an item in the .isolist box is double-clicked.
#
proc pick_isodir {} {
  global isolist_names isolist_types isodir_adr isodir_return_name
  global .isolist

  set selected [.isolist curselection]
  if {[llength $selected] != 1} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : First you must select a single directory"
    return ""
  }
  set idx [lindex $selected 0]
  if {[lindex $isolist_types $idx] != "d"} { return "" }
  if {$isodir_adr == "/"} {
    set isodir_adr ""
  }
  set isodir_adr "$isodir_adr/[lindex $isolist_names $idx]"
  set isodir_return_name ""
  isodir_return "pick_isodir"
}


# Make current the parent directory of the current ISO directory.
# Called when the "Up" button is hit.
#
proc isodir_up {} {
  global isodir_adr isodir_return_name

  set isodir_return_name ""
  set idx [string last "/" $isodir_adr]
  set l  [string length $isodir_adr]
  if {$idx == -1} {
    set isodir_return_name $isodir_adr
    set isodir_adr "/"
  } else {
    if {$idx > 0} {
      if {$idx < [expr $l - 1]} {
        set isodir_return_name \
            [string range $isodir_adr [expr $idx+1] end]
      }
      set isodir_adr [string range $isodir_adr 0 [expr $idx - 1]]
    } else {
      if {$l > 1} {
        set isodir_return_name [string range $isodir_adr 1 end]
      }
      set isodir_adr "/"
    }
  }
  isodir_return "isodir_up"
}


# Rename or move the files which are selected in the .isolist box.
# The target is defined by the .isomanip_move_target text field.
# Called when the "Move to:" button is hit.
#
proc isomanip_mv {} {
  global .isolist
  global isomanip_move_target isolist_names isodir_is_pwd isodir_adr
  global isodir_return_name use_command_move

  if {$isomanip_move_target == ""} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : First you must enter a target address"
    return ""
  }
  set selected [.isolist curselection]
  set num_selected [llength $selected]
  if {$num_selected < 1} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : First you must select one or more ISO files"
    return ""
  }

  set target $isomanip_move_target
  if {$isodir_is_pwd == 0 && [string range $target 0 0] != "/"} {
    set target [combine_dir_and_name $isodir_adr $target]
  }
  set target_ftype [isofs_filetype $target]

  # If more than one selected : target must be directory
  if {$num_selected > 1} {
    if {$target_ftype != "d" && $target_ftype != ""} {
      xorriso_tcltk_errmsg \
         "xorriso-tcltk : SORRY : If multiple files are selected then the target must be a directory"
      return ""
    }
    if {$target_ftype == ""} {
      set isomanip_move_target_mem $isomanip_move_target
      set isomanip_move_target $target
      isomanip_mkdir
      set isomanip_move_target $isomanip_move_target_mem
    } 
  }

  enforce_overwrite_settings "isofs"
  reset_highest_cmd_sev
  reset_yesno_to_all
  set multi_source 0
  if {[llength $selected] != 1} {set multi_source 1}
  foreach i $selected {
    set name [lindex $isolist_names $i]
    if {$isodir_is_pwd == 0} {
      set name [combine_dir_and_name $isodir_adr $name]
    }
    set name_ftype [isofs_filetype $name]

    # Ask for confirmation if overwriting is about to happen
    if {$target_ftype == "d" && $use_command_move == 0} {
      set eff_target [combine_dir_and_name $target $name]
      set eff_target_ftype [isofs_filetype $eff_target]
    } else {
      set eff_target $target
      set eff_target_ftype $target_ftype
    }
    if {[handle_overwriting "isofs" $eff_target $eff_target_ftype \
                            "isofs" $name $name_ftype $multi_source \
                            "" "" "replace"] == "0"} {
      if {$multi_source == 0} { return "" }
  continue
    }
    if {$use_command_move == 0} {
      send_loggable_cmd \
             "-mv [make_text_shellsafe $name] [make_text_shellsafe $target] --"
    } else {
      send_loggable_cmd \
           "-move [make_text_shellsafe $name] [make_text_shellsafe $target] --"
    }
  }

  if {[llength $selected] == 1} {
    set isodir_return_name [path_touches_isodir $target]
  }
  browse_iso_refresh
  isodir_return "isomanip_mv"
}


# Create an empty ISO directory with address given by variable
# isomanip_move_target.
# Called when the "Make dir" button is hit or by other functions.
#
proc isomanip_mkdir {} {
  global isomanip_move_target isodir_adr isodir_return_name

  if {$isomanip_move_target == ""} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : First you must enter a target address"
    return ""
  }

  if {[string range $isomanip_move_target 0 0] == "/"} {
    set abs_adr $isomanip_move_target
  } else {
    set abs_adr [combine_dir_and_name $isodir_adr $isomanip_move_target]
  }
  reset_highest_cmd_sev
  send_loggable_cmd "-mkdir [make_text_shellsafe $abs_adr] --"

  # Refresh only if new dir in isodir_adr
  # or if a parent directory of new dir is created in isodir_adr
  set touch_name [path_touches_isodir $abs_adr]
  if {$touch_name != ""} {
    if {[llength [.isolist curselection]] != 0} {
      memorize_isolist_selection 
      set selection_memorized 1
    } else {
      set isodir_return_name $touch_name
      set selection_memorized 0
    }
    isodir_return "isomanip_mkdir"
    if {$selection_memorized != 0} {
      restore_isolist_selection 
    }
  }
  browse_iso_refresh
}


# Remove a file or directory tree from the ISO image.
# Called when the "Delete" button is hit.
#
proc isomanip_rm_r {} {
  global .isolist
  global isomanip_move_target isolist_names isodir_is_pwd isodir_adr

  set selected [.isolist curselection]
  if {[llength $selected] < 1} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : First you must select one or more ISO files"
    return ""
  }
  if {[window_yesno "Really delete the selected files from ISO image ?"] \
      != 1} { return "" }
  reset_highest_cmd_sev
  foreach i $selected {
    set name [lindex $isolist_names $i]
    if {$isodir_is_pwd == 0} {
      set name [combine_dir_and_name $isodir_adr $name]
    }
    send_loggable_cmd "-rm_r [make_text_shellsafe $name] --"
  }
  browse_iso_refresh
  isodir_return "isomanip_rm_r"
}


# Perform a blanking run on the output drive.
# Called when the "Blank" button is hit.
#
proc burn_blank {} {
  global outdev_profile eff_outdev_adr eff_indev_adr

  refresh_outdev
  if {[assert_outdev blanking] <= 0} {return ""}

  if {$eff_outdev_adr == $eff_indev_adr} {
    if {[assert_no_changes] <= 0} {return ""}
  }
  set victim "medium in"
  if {[string first "stdio" $outdev_profile] == 0} {
    set victim "image file"
  } 
  if {[window_yesno \
          "Really blank the $victim [make_text_shellsafe $eff_outdev_adr] ?"] \
      != 1} { return "" }
  reset_highest_cmd_sev
  send_loggable_cmd "-blank as_needed"
  refresh_indev
  refresh_outdev
}


# Perform a formatting run on the output drive.
# Called when the "Format" button is hit.
#
proc burn_format {} {
  global outdev_profile eff_outdev_adr eff_indev_adr

  refresh_outdev
  if {[assert_outdev formatting] <= 0} {return ""}
  if {$eff_outdev_adr == $eff_indev_adr} {
    if {[assert_no_changes] <= 0} {return ""}
  }

  if {[string first "stdio" $outdev_profile] == 0} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : Image files cannot be formatted"
    return ""
  } 
  if {[window_yesno "Really format the medium in $eff_outdev_adr ?"] \
      != 1} { return "" }

  reset_highest_cmd_sev
  send_loggable_cmd "-format as_needed"
  refresh_indev
  refresh_outdev
}


# Write pending changes in the xorriso ISO model as session to the output
# drive. This will be an add-on session if the drive is output and input drive
# and if its medium is not blank.
# Else it will be a new independent ISO image.
#
proc burn_commit {} {
  global outdev_adr result_list result_count outdev_medium_status
  global burn_write_close burn_write_tao burn_write_defect_mgt
  global indev_adr outdev_adr permission_policy

  if {[assert_outdev "writing an ISO session"] <= 0} {return ""}
  if {$outdev_adr == $indev_adr} {
    if {$outdev_medium_status != "blank" && \
        $outdev_medium_status != "appendable"} {
      xorriso_tcltk_errmsg \
        "xorriso-tcltk : SORRY : Medium in output drive is neither blank nor appendable"
      return ""
    }
  } else {
    if {$outdev_medium_status != "blank"} {
      xorriso_tcltk_errmsg \
        "xorriso-tcltk : SORRY : Medium in output drive is not blank"
      return ""
    }
  }
  if {[changes_are_pending] == "0"} {
    window_ack "No changes are pending. Will not write ISO session." \
               "grey" "toplevel"
    return ""
  }
  if {$outdev_adr == $indev_adr} {
    if {[window_yesno "Really write ISO changes as session to $outdev_adr ?"] \
        != 1} { return "" }
  } else {
    if {[window_yesno "Really write a new ISO filesystem to $outdev_adr ?"] \
        != 1} { return "" }
  }

  reset_highest_cmd_sev
  effectuate_permission_policy
  set cmd "-close"
  if {$burn_write_close == 1} {
    set cmd "$cmd on"
  } else {
    set cmd "$cmd off"
  }
  set cmd "$cmd -write_type"
  if {$burn_write_tao == 1} {
    set cmd "$cmd tao"
  } else {
    set cmd "$cmd auto"
  }
  set cmd "$cmd -stream_recording"
  if {$burn_write_defect_mgt == 1} {
    set cmd "$cmd off"
  } else {
    set cmd "$cmd data"
  }
  set cmd "$cmd -commit"
  send_loggable_cmd $cmd
  refresh_indev
  refresh_outdev
}


# Verify the MD5 checksums of the data files in the tree underneath the
# current ISO directory.
# Called when the "Verify" in the "ISO directory:" line is hit.
#
proc isodir_verify {} {
  global isodir_adr

  reset_highest_cmd_sev
  send_loggable_cmd "-check_md5_r sorry [make_text_shellsafe $isodir_adr] --"

  # >>> select mismatching files or directories with mismatching files

}


# Verify the MD5 checksums of the data files orch are selected or which
# sit in the trees underneath the selected items in the isolist box.
# Called when the "Verify" in the "Selection:" line is hit.
#
proc isomanip_verify {} {
  global .isolist
  global isomanip_move_target isolist_names isodir_is_pwd isodir_adr

  set selected [.isolist curselection]
  if {[llength $selected] < 1} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : First you must select one or more ISO files"
    return ""
  }
  reset_highest_cmd_sev
  foreach i $selected {
    set name [combine_dir_and_name $isodir_adr \
                                    [lindex $isolist_names $i]]
    send_loggable_cmd "-check_md5_r sorry [make_text_shellsafe $name] --"
  }

  # >>> select mismatching files or directories with mismatching files

}


# Slow down the spinning of the acquired optical drives.
# Called when button "Calm drives" is hit.
#
proc calm_drives {} {
  reset_highest_cmd_sev
  send_loggable_cmd "-calm_drive all"
}


# Burn a data file from disk as session to the output drive.
# Called when the "Burn image file:" button is hit.
#
proc burn_write_image {} {
  global burn_write_image_adr burn_write_close outdev_adr outdev_medium_status
  global outdev_profile burn_write_tao burn_write_defect_mgt indev_adr

  update_dev_var
  if {$indev_adr != ""} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : You may not have an input drive open when writing an image file"
    return ""
  }

  if {[assert_outdev "writing an image file"] <= 0} {return ""}
  if {$burn_write_image_adr == ""} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : You have to set an image file address in the hard disk filesystem first"
    return ""
  }

  if {$outdev_medium_status != "blank"} {
    xorriso_tcltk_errmsg \
      "xorriso-tcltk : SORRY : You must have a blank medium in the output drive for burning an image data file"
    return ""
  }
  if {[file readable $burn_write_image_adr] == 0 || \
      [file isfile $burn_write_image_adr] == 0 ||
      [file exists $burn_write_image_adr] == 0} {
    xorriso_tcltk_errmsg \
               "xorriso-tcltk : SORRY : Image file '$burn_write_image_adr' is not a readable data file"
    return ""
  }
  if {[window_yesno "Really write '$burn_write_image_adr' as image to $outdev_adr ?"] \
      != 1} { return "" }

  set cmd "-as cdrecord -v"
  if {[regexp "^CD" $outdev_profile] == 1 && \
       ( $outdev_medium_status == "appendable" || $burn_write_tao == 1 )} {
    set cmd "$cmd padsize=150s"
  }
  set cmd "$cmd dev=[make_text_shellsafe $outdev_adr]"
  set cmd "$cmd [make_text_shellsafe $burn_write_image_adr]"
  if {$burn_write_tao == 1} {
    set cmd "$cmd -tao"
  }
  if {$burn_write_close != 1} {
    set cmd "$cmd -multi"
  } 
  if {$burn_write_defect_mgt == 1} {
    set cmd "$cmd stream_recording=off"
  } else {
    set cmd "$cmd stream_recording=32s"
  }
  reset_highest_cmd_sev
  send_loggable_cmd $cmd
  refresh_state
}


# Discard all image modifications and reload ISO image model from input drive.
# Called when the "Rollback" button is hit.
#
proc iso_rollback {} {
  if {[window_yesno \
         "Really discard all pending changes and reload from input drive  ?"] \
      != 1} { return "" }
  reset_highest_cmd_sev
  send_loggable_cmd "-rollback"
  .avail_label configure -text ""
  isodir_return "iso_rollback"
} 


# Inquire an accurate prediction of free space after writing a session with
# the pending changes of the ISO image.
# Called when button "Refresh avail:" is hit.
#
proc refresh_avail {} {
  global result_list highest_cmd_sev
  global sieve_ret

  if {[assert_outdev "refreshing available space prediction"] <= 0} {return ""}

  set line "n.a."
  reset_highest_cmd_sev
  clear_sieve
  send_loggable_cmd "-tell_media_space"
  if {[compare_sev $highest_cmd_sev "FAILURE"] < 0} {
    set ac ""
    read_sieve "After commit :"
    if {$sieve_ret > 0} {
      set ac [lindex $result_list 0]
      set ac [string range $ac 0 [expr [string length $ac] - 2]]
      set line "[format "%7dm" [expr "$ac / 512"]]"
    }
  }
  .avail_label configure -text $line
}


# Warn and prompt the user for confirmation if there is the risk to overwrite
# existing files on hard disk or in the ISO image model.
# Called from several procedures which cause side effects on directory trees.
#
proc handle_overwriting {target_fs target target_ftype
                         source_fs source source_ftype multi_source
                         selected_adr selected_ftype dir_action} {
  global overwrite_iso_files overwrite_iso_dirs overwrite_disk_files 

  if {$target_fs == "localfs"} {
    set to_fs "hard disk"
    set overwrite_fs "disk"
    set overwrite_dirs 0
    set overwrite_files $overwrite_disk_files
  } else {
    set to_fs "ISO"
    set overwrite_fs "ISO"
    set overwrite_dirs $overwrite_iso_dirs
    set overwrite_files $overwrite_iso_files
  }
  if {$source_fs == "localfs"} {
    set from_fs "hard disk"
  } else {
    set from_fs "ISO"
  }
  if {$multi_source == 1} {
    set what_window window_yesno_ever
  } else {
    set what_window window_yesno
  }

  # >>> Nicer would be:
    # >>> Check if any file will get overwritten. Not only the direct target.
    # >>> Then silently allow directories to be merged

  if {$target_ftype != ""} {
    if {$target_ftype == "d"} {
      if {$source_ftype == "d"} {
        if {$dir_action == "replace"} {
          if {$overwrite_iso_dirs != 1} {
            xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You would have to enable \"Overwrite $overwrite_fs dirs\" for\n[make_text_shellsafe $target]"
            return "0"
          }
          if {[$what_window \
              "Really replace existing $to_fs directory\n\n[make_text_shellsafe $target]\n\nby $from_fs directory\n[make_text_shellsafe $source]\n?"] \
            != 1} { return "0" }
          return "1"
        }
        if {[$what_window \
              "Really merge existing $to_fs directory\n\n[make_text_shellsafe $target]\n\nwith $from_fs directory\n[make_text_shellsafe $source]\n?"] \
            != 1} { return "0" }
      } else {
        if {$target_fs != "isofs"} {
          xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : Will not replace directory on hard disk by file of other type\n[make_text_shellsafe $target]"
          return "0"
        }
        if {$overwrite_dirs == 1} {
          if {[$what_window \
               "Really overwrite $to_fs directory\n\n[make_text_shellsafe $target]\n\nby $from_fs file\n[make_text_shellsafe $source]\n?"] \
              != 1} { return "0" }
        } else {
          xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You would have to enable \"Overwrite $overwrite_fs dirs\" for\n[make_text_shellsafe $target]"
          return "0"
        }
      }
    } else {
      if {$overwrite_files == 1} {
        if {[$what_window \
              "Really overwrite $to_fs file\n\n[make_text_shellsafe $target]\n\nby $from_fs file\n[make_text_shellsafe $source]\n?"] != 1} {
          return "0"
        }
      } else {
        xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You would have to enable \"Overwrite $overwrite_fs files\" for\n[make_text_shellsafe $target]"
        return "0"
      }
    }
  }
  if {$selected_adr != $target && $selected_adr != "" && \
      $selected_ftype != "d" && $selected_ftype != ""} {
    if {[$what_window \
          "Really replace existing $to_fs file\n\n[make_text_shellsafe $target]\n\nby $from_fs directory\n[make_text_shellsafe $source]\n?"] != 1} {
      return "0"
    }
  }
  return "1"
}


# Insert a file or directory tree into the ISO model tree and schedule it
# for being copied when "Write ISO session" is hit.
# Called when button "Insert from disk:" is hit.
#
proc insert_from {} {
  global insert_from_adr isodir_adr isolist_names isodir_return_name
  global insert_at_selected insert_underneath

  if {[assert_iso_image 1] == 0} {return ""}
  if {$insert_from_adr == ""} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : You have to set a source address in the hard disk filesystem first"
    return ""
  }
  set selected_ftype ""
  set selected_adr ""
  if {$insert_at_selected == 1} {
    set selected [.isolist curselection]
    if {[llength $selected] != 1} {
      xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You must select exactly one ISO file as insertion target"
      return ""
    }
    set target "[lindex $isolist_names [lindex $selected 0]]"
    set selected_ftype [isofs_filetype $target]
    set selected_adr $target
  } else {
    set target $isodir_adr
  }
  set source_ftype [localfs_filetype $insert_from_adr]
  set name [file tail $insert_from_adr]
  if {$insert_underneath == 1 || $source_ftype == "d"} {
    set target [combine_dir_and_name $target $name]
  }
  set target_ftype [isofs_filetype $target]

  reset_yesno_to_all
  if {[handle_overwriting "isofs" $target $target_ftype \
                          "localfs" $insert_from_adr $source_ftype 0 \
                          $selected_adr $selected_ftype "merge"] == "0"} {
    return ""
  }

  set preserve_selection 0
  if {$insert_underneath + $insert_at_selected == 1} {
    set isodir_return_name $name
  } else {
    set preserve_selection 1
  }
  reset_highest_cmd_sev
  enforce_overwrite_settings "isofs"
  send_loggable_cmd "-map [make_text_shellsafe $insert_from_adr] [make_text_shellsafe $target]"

  if {$preserve_selection == 1} {
    memorize_isolist_selection
  }
  isodir_return "insert_from"
  if {$preserve_selection == 1} {
    restore_isolist_selection
  }
  browse_iso_refresh
}


# Copy a file out of the ISO image model to the hard disk filesystem.
# The meta data stem from the ISO model tree. The content data are usually
# read from the input drive.
# Called when button "Extract to disk:" is hit.
#
proc extract_to {} {
  global extract_to_adr extract_from_selected extract_underneath
  global extract_auto_chmod osirrox_allowed
  global isodir_adr isolist_names

  if {$osirrox_allowed != 1} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : Extraction from ISO to hard disk is already irrevocably banned."
    return ""
  }

  if {[assert_iso_image 1] == 0} {return ""}
  if {$extract_to_adr == ""} {
    xorriso_tcltk_errmsg \
       "xorriso-tcltk : SORRY : You have to set a target address in the hard disk filesystem first"
    return ""
  }
  set sources ""
  set selected_ftype ""
  set selected_adr ""
  if {$extract_from_selected == 1} {
    set selected [.isolist curselection]
    if {[llength $selected] < 1} {
      xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You must select at least one ISO file as extraction source"
      return ""
    }
    foreach i $selected {
      set path [combine_dir_and_name $isodir_adr \
                                     [lindex $isolist_names $i]]
      lappend sources $path
    }
  } else {
    set sources [list $isodir_adr]
  }

  reset_highest_cmd_sev
  reset_yesno_to_all
  enforce_overwrite_settings "localfs"
  set disp_en_mem [set_display_msg 0]
  if {$extract_auto_chmod == 1} {
    send_loggable_cmd "-osirrox on:sort_lba_on:auto_chmod_on"
  } else {
    send_loggable_cmd "-osirrox on:sort_lba_off:auto_chmod_off"
  }
  set_display_msg $disp_en_mem
  set multi_source 0
  if {[llength $sources] != 1} {set multi_source 1}
  foreach i $sources {
    if {$extract_underneath == 1} {
      set name [file tail $i]
      set target [combine_dir_and_name $extract_to_adr $name]
    } else {
      if {[llength $sources] != 1} {
        xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You must select exactly one ISO file as extraction source"
        return ""
      }
      set target $extract_to_adr
    }
    if {$i == ""} {
      set i "/"
    }
    set target_ftype [localfs_filetype $target]
    set from_is_dir 0
    set source_ftype [isofs_filetype $i]
    if {[handle_overwriting "localfs" $target $target_ftype \
                            "isofs" $i $source_ftype $multi_source \
                            "" "" "merge"] == 0} {
  continue
    }
    send_loggable_cmd \
              "-extract [make_text_shellsafe $i] [make_text_shellsafe $target]"
  }
  browse_tree_populate "localfs"
}


# Send the currently chosen -overwrite settings of the checkbuttons
# "Overwrite ISO files", "Overwrite ISO dirs", "Overwrite hard disk files".
# Called before operations which could overwrite files in ISO model
# or in the hard disk filesystem.
# I.e. any -overwrite command sent via the "Command:" text field will not
# be able to override the checkbuttons.
#
proc enforce_overwrite_settings {which_fs} {
  global overwrite_iso_files overwrite_iso_dirs overwrite_disk_files

  if {$which_fs == "isofs"} {
    if {$overwrite_iso_files == 0} {
      set mode "off"
    } else {
      if {$overwrite_iso_dirs == 0} {
        set mode "nondir"
      } else {
        set mode "on"
      }
    }
  } else {
    if {$overwrite_disk_files == 1} {
      set mode "on"
    } else {
      set mode "off"
    }
  }
  set disp_en_mem [set_display_msg 0]
  send_loggable_cmd "-overwrite $mode"
  set_display_msg $disp_en_mem
}


# Send xorriso an appropriate end command and wait for the pipes to break.
# Called when button "End" is hit.
#
proc end_xorriso {} {
  global expect_broken_pipes

  if {[window_yesno "Really end this program and its xorriso backend ?"] \
      != 1} { return "" }
  if {[changes_are_pending] == 1} {
    if {[window_yesno \
              "Changes of the ISO image are pending.\nReally discard them ?"] \
        != 1} { return "" }
    set expect_broken_pipes "1"
    send_loggable_cmd "-rollback_end"
  } else {
    set expect_broken_pipes "1"
    send_loggable_cmd "-end"
  }
  central_exit 0
}


# Check whether an output drive is acquired. Propose refusal if not.
# Called by procedures which are about to use the output drive.
#
proc assert_outdev {purpose} {
  global eff_outdev_adr 

  inquire_dev
  if {$eff_outdev_adr == ""} {
    xorriso_tcltk_errmsg \
      "xorriso-tcltk : SORRY : You must choose an output drive before $purpose"
    return "0"
  }
  return "1"
}


# Check whether changes to the ISO model are pending. If so, propose refusal.
# Called by procedures which are about to discard the ISO model.
#
proc assert_no_changes {} {
  if {[changes_are_pending] == 1} {
     window_ack "ISO image changes are pending. You have to do \"Write ISO session\" or \"Rollback\"." "grey" "toplevel"
    return "0"
  }
  return "1"
}


# Set the text of the "Permissions:" menubutton
# Called by the radiobuttons in the menu.
#
proc show_permission_policy {} {
  global permission_policy

  set text $permission_policy
  if {$permission_policy == "as_is"} {
    set text "as is"
  }
  if {$permission_policy == "mkisofs_r"} {
    set text "mkisofs -r"
  }
  .perm_policy configure -text "Permissions: $text"
}


# Set the target address of command logging.
# Called by the "Script/Log" menu.
#
proc set_log_script_address {} {
  browse_tree cmd_log_target "localfs"
  set w .browse_disk_window
  tkwait window $w
  effectuate_command_logging 0
}


# Bring into effect the settings for command script logging.
# Called by the "Accept" button or the Return key of the
# "Set log script address" file browser.
#
proc effectuate_command_logging {close_window} {
  global cmd_log_target cmd_logging_mode browse_disk_window_is_active

  if {$close_window == 1 && $browse_disk_window_is_active == 1} {
    destroy_browse_disk .browse_disk_window
  }
  if {$close_window == 1 || $cmd_logging_mode > 0} {
    start_command_logging $cmd_log_target $cmd_logging_mode
  }
}


# Set the target address of communication pipe logging.
# Called by the "Script/Log" menu.
#
proc set_debug_log_address {} {
  browse_tree debug_log_file "localfs"
  set w .browse_disk_window
  tkwait window $w
  effectuate_debug_logging 0
}


# Bring into effect the settings for communication pipe logging.
# Called by the "Accept" button or the Return key of the
# "Set pipe log address" file browser.
#
proc effectuate_debug_logging {close_window} {
  global debug_log_file debug_logging browse_disk_window_is_active

  if {$close_window == 1 && $browse_disk_window_is_active == 1} {
    destroy_browse_disk .browse_disk_window
  }
  if {$close_window == 1 || $debug_logging > 0} {
    start_debug_logging $debug_log_file $debug_logging
  }
}


# Trigger execution of a script of xorriso commands.
# Called by the "Script/Log" menu.
#
proc start_script_execution {} {
  browse_tree execute_script_adr "localfs"
  # actual script start is done by browse_tree_accept -> execute_script
}


# Permanently ban any extraction from ISO to hard disk
#
proc osirrox_banned {} {
  global osirrox_allowed

  reset_yesno_to_all
  if {[window_yesno \
             "Really irrevocably ban any extraction from ISO to hard disk ?"] \
      != 1} { return "" }

  send_loggable_cmd "-osirrox banned"
  set osirrox_allowed 0

  set m ".script_log.menu"
  $m entryconfigure "Allow extract to disk" -state "disabled"
  $m entryconfigure "Permanently ban extraction" -state "disabled"

  .extract_button configure -state "disabled"
  
}


# ------ A primitive file tree browser for hard disk filesystem and ISO model 

# Write a directory content list into a Tree widget
#
proc browse_tree_fill_dir {tr parent children} {

  if {$parent == "/"} {
    set parent_name root
    set parent_dir /
  } else {
    set parent_name [escape_to_tree $parent]
    set parent_dir $parent_name
  }
  if {[$tr exists $parent_name] == 0} {return ""}

  $tr delete [$tr nodes $parent_name]

  foreach i $children {
    set name [string range $i 2 end]
    set escpd [escape_to_tree $name]
    set adr [combine_dir_and_name $parent_dir $escpd]
    $tr insert end $parent_name $adr -text $name
    if {[string range $i 0 0] == "d"} {
      set dir_dummy [combine_dir_and_name $adr "_"]
      $tr insert end $adr $dir_dummy -text " "
    }
  }
}


# The command to be executed when the user double-clicks a node.
#
proc browse_tree_accept {adr_var_name do_return tr value} {
  global have_bwidget
  global extract_to_adr insert_from_adr burn_write_image_adr isodir_adr
  global isomanip_move_target indev_adr outdev_adr cmd_log_target
  global debug_log_file execute_script_adr

  # Caution: Before using $tr, check for $have_bwidget

  if {$adr_var_name == "burn_write_image_adr"} {
    set burn_write_image_adr $value
    if {$do_return == 1} {burn_write_image}
  }
  if {$adr_var_name == "extract_to_adr"} {
    set extract_to_adr $value
    if {$do_return == 1} {extract_to}
  }
  if {$adr_var_name == "insert_from_adr"} {
    set insert_from_adr $value
    if {$do_return == 1} {insert_from}
  }
  if {$adr_var_name == "isodir_adr"} {
    set isodir_adr $value
    if {$do_return == 1} {isodir_return "browse_tree_accept"}
  }
  if {$adr_var_name == "isomanip_move_target"} {
    set isomanip_move_target $value
    if {$do_return == 1} {isomanip_mv}
  }
  if {$adr_var_name == "indev_adr"} {
    set indev_adr $value
    if {$do_return == 1} {indev_return}
  }
  if {$adr_var_name == "outdev_adr"} {
    set outdev_adr $value
    if {$do_return == 1} {outdev_return}
  }
  if {$adr_var_name == "cmd_log_target"} {
    set cmd_log_target $value
    if {$do_return == 1} {effectuate_command_logging 1}
  }
  if {$adr_var_name == "debug_log_file"} {
    set debug_log_file $value
    if {$do_return == 1} {effectuate_debug_logging 1}
  }
  if {$adr_var_name == "execute_script_adr"} {
    set execute_script_adr $value
    if {$do_return == 1} {execute_script 1}
  }
}


# Translate a browser tree variable in a human readable topic text
#
proc browse_tree_topic {adr_var_name} {
  if {$adr_var_name == "burn_write_image_adr"} {
    return "Burn image file:"
  }
  if {$adr_var_name == "extract_to_adr"} {
    return "Extract to disk:"
  }
  if {$adr_var_name == "insert_from_adr"} {
    return "Insert from disk:"
  }
  if {$adr_var_name == "isodir_adr"} {
    return "ISO directory:"
  }
  if {$adr_var_name == "isomanip_move_target"} {
    return "Move to:"
  }
  if {$adr_var_name == "indev_adr"} {
    return "Input drive/image"
  }
  if {$adr_var_name == "outdev_adr"} {
    return "Output drive/image"
  }
  if {$adr_var_name == "cmd_log_target"} {
    return "Set log script address"
  }
  if {$adr_var_name == "debug_log_file"} {
    return "Set pipe log address"
  }
  if {$adr_var_name == "execute_script_adr"} {
    return "Execute command script"
  }
  return $adr_var_name
}


# Unescape &|^! from Bwidget tree browser
#
proc unescape_from_tree {text} {
  return [string map [list "\{\{\}" "\{" "\{+\}" "&" "\{I\}" "|" \
                           "\{A\}" "^"   "\{.\}"  "!"] \
                     $text]

  # <<< alternative encoding
  # set escpd [string map [list "\\\\" "\\" "\\+" "&" "\\I" "|" \ 
  #                             "\\A" "^"   "\\."  "!"] \ 
}


# Escape &|^! which are special to BWidget Tree
#
proc escape_to_tree {text} {
  return [string map [list "\{" "\{\{\}"  "&" "\{+\}"  "|" "\{I\}" \
                           "^"  "\{A\}"   "!" "\{.\}"] \
                     $text]
}



# Accept the single selected item of the tree browser
# Called by the \"Accept\" button in the browser window.
#
proc browse_tree_accept_sel {adr_var_name do_return tr} {
  set selected [$tr selection get]

  if {[llength $selected] != 1} {
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You must select a single tree item before clicking the \"Accept\" button."
    return ""
  }
  browse_tree_accept $adr_var_name $do_return $tr \
                     [unescape_from_tree [lindex $selected 0]]
}


# Hit the Return key on the text entry of the browser
#
proc browse_tree_accept_entry {adr_var_name do_return tr} {
  global extract_to_adr insert_from_adr burn_write_image_adr isodir_adr
  global isomanip_move_target indev_adr outdev_adr cmd_log_target
  global debug_log_file execute_script_adr

  eval set text $$adr_var_name
  browse_tree_accept $adr_var_name $do_return $tr $text
}


# Submit a Tree-escaped path to browse_tree_accept.
# Called by Double-click in browser.
#
proc browse_tree_accept_escd {adr_var_name do_return tr escd_path} {
  browse_tree_accept $adr_var_name $do_return $tr \
                     [unescape_from_tree $escd_path]
}


# Move up one directory level of the file browser selection
#
proc browse_tree_up {adr_var_name tr which_fs} {
  global extract_to_adr insert_from_adr burn_write_image_adr isodir_adr
  global isomanip_move_target indev_adr outdev_adr

  set selected [$tr selection get]
  if {[llength $selected] != 1} {
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You must select a single tree item before clicking the \"Up\" button."
    return ""
  }
  set old_adr [lindex $selected 0]
  set adr [file dirname $old_adr]
  catch {
    $tr see $adr
    if {[$tr nodes $old_adr 0] != ""} {
      $tr closetree $old_adr
    }
  }
  if {$adr != "/" && $adr != ""} {
    $tr selection clear
    $tr selection set $adr
  }
}


# Move down one directory level of the file browser selection
#
proc browse_tree_down {adr_var_name tr which_fs} {
  global extract_to_adr insert_from_adr burn_write_image_adr isodir_adr
  global isomanip_move_target indev_adr outdev_adr

  set selected [$tr selection get]
  if {[llength $selected] != 1} {
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You must select a single tree item before clicking the \"Down\" button."
    return ""
  }
  set adr [lindex $selected 0]
  if {$which_fs == "isofs"} {
    browse_iso_open_dir $tr $adr
  } else {
    browse_disk_open_dir $tr $adr
  }
  catch {
    $tr opentree $adr 0
    $tr see $adr
  }
}


# The command to be executed when the user closes a directory node.
# It replaces the directory content list by a single dummy item.
#
proc browse_tree_close_dir {tr name} {
  browse_tree_fill_dir $tr $name [list "?  "]
}


# Delete the old content of the browse window and display the freshly
# obtained current state down to the current address in the field variable.
#
proc browse_tree_populate {which_fs} {
  global have_bwidget
  global browse_disk_window_var browse_iso_window_var
  global browse_iso_window_is_active browse_disk_window_is_active
  global extract_to_adr insert_from_adr burn_write_image_adr isodir_adr
  global isomanip_move_target indev_adr outdev_adr cmd_log_target
  global debug_log_file execute_script_adr

  if {$have_bwidget != 1} {return ""}

  if {$which_fs == "isofs"} {
    if {$browse_iso_window_is_active == 0} {return ""}
    set w {.browse_iso_window}
    set open_dir_cmd "browse_iso_open_dir"
    set adr_var $browse_iso_window_var
  } else {
    if {$browse_disk_window_is_active == 0} {return ""}
    set w {.browse_disk_window}
    set open_dir_cmd "browse_disk_open_dir"
    set adr_var $browse_disk_window_var
  }

  # Variable indirection
  eval set adr $$adr_var

  # Install root level
  $open_dir_cmd $w.tree "/" 

  # Set $adr as current address
  set comps [split $adr "/"]
  # Install the stack of directories above current address
  set path "/"
  foreach i $comps {
    if {$i == ""} {
  continue
    }
    set path [combine_dir_and_name $path [escape_to_tree $i]]
    $open_dir_cmd $w.tree $path 
    catch {
      $w.tree opentree $path 0
      $w.tree see $path
    }
  }
}


# The procedure to be run by mouse button 3 in the file browser.
# It has to strip off the surplus parameter added by the Tree widget.
#
proc browse_tree_help {about_what button_color from_item} {
  window_help $about_what $button_color
}


# Destroy the hard disk browser pop-up window.
# 
proc destroy_browse_disk {w} {
  global browse_disk_window_is_active browse_disk_window_geometry
  global browse_disk_window_is_grabbed

  if {$w != "" && $browse_disk_window_is_active == 1} {
    if {$browse_disk_window_is_grabbed == 1} {
      grab release $w
    }
    set browse_disk_window_is_grabbed 0
    set browse_disk_window_geometry [wm geometry $w]
    destroy $w
  }
  set browse_disk_window_is_active 0
}


# The command to be executed when the user opens a directory node in
# the hard disk filesystem.
#
proc browse_disk_open_dir {tr name} {
  set escpd [unescape_from_tree $name]
  if {[localfs_filetype $escpd] != "d"} {return ""}
  set lslist [localfs_ls $escpd]
  browse_tree_fill_dir $tr $escpd $lslist
}


# Refresh the content of a possibly displayed tree browser for hard disk
#
proc browse_disk_refresh {} {
  browse_tree_populate "localfs"
}


# The command to be executed when the user opens a directory node in
# the ISO model.
#
proc browse_iso_open_dir {tr name} {
  set escpd [unescape_from_tree $name]
  if {[isofs_filetype $escpd] != "d"} {return ""}
  set lslist [isofs_ls $escpd]
  browse_tree_fill_dir $tr $escpd $lslist
}


# Destroy the ISO browser pop-up window.
# 
proc destroy_browse_iso {w} {
  global browse_iso_window_is_active browse_iso_window_geometry
  global browse_iso_window_is_grabbed

  if {$w != "" && $browse_iso_window_is_active == 1} {
    set browse_iso_window_geometry [wm geometry $w]
    if {$browse_iso_window_is_grabbed == 1} {
      grab release $w
    }
    set browse_iso_window_is_grabbed 0
    destroy $w
  }
  set browse_iso_window_is_active 0
}


# Refresh the content of a possibly displayed tree browser for ISO model
#
proc browse_iso_refresh {} {
  browse_tree_populate "isofs"
}


# Multiplexer for updating both vertical scrollbars
#
proc browse_tree_yscrollcommand {w arg1 arg2} {
  $w.treescroll_y_l set $arg1 $arg2
  $w.treescroll_y_r set $arg1 $arg2
}


# Open a file browser window for hard disk filesystem or ISO model
#
proc browse_tree {adr_var which_fs} {
  upvar $adr_var adr
  global have_bwidget browse_disk_window_is_active browse_iso_window_is_active
  global browse_disk_window_var browse_iso_window_var
  global tree_window_lines tree_window_width tree_window_button_width
  global browse_disk_window_geometry browse_iso_window_geometry

  set button_color "grey"

  if {$which_fs == "isofs"} {
    set w {.browse_iso_window}
    set window_is_active $browse_iso_window_is_active
    set title_name "xorriso-tcltk ISO model browser"
    set open_dir_cmd "browse_iso_open_dir"
    set destroy_cmd "destroy_browse_iso"
    if {$browse_iso_window_var != $adr_var && $window_is_active == 1} {
      destroy_browse_iso $w
      set window_is_active 0
    }
    set browse_iso_window_var $adr_var
    set old_geometry $browse_iso_window_geometry
    set browse_iso_window_is_active 1
  } else {
    set w {.browse_disk_window}
    set window_is_active $browse_disk_window_is_active
    set title_name "xorriso-tcltk hard disk filesystem browser"
    set open_dir_cmd "browse_disk_open_dir"
    set destroy_cmd "destroy_browse_disk"
    if {$browse_disk_window_var != $adr_var && $window_is_active == 1} {
      destroy_browse_disk $w
      set window_is_active 0
    }
    set browse_disk_window_var $adr_var
    set old_geometry $browse_disk_window_geometry
    set browse_disk_window_is_active 1
  }
  set re_use_widgets 0
  if {$window_is_active == 0} {
    toplevel $w -borderwidth 10 -class Browser
    wm title $w $title_name
    set_window_position $w $old_geometry
  } else { 
    set re_use_widgets 1
  }
  if {$re_use_widgets == 0} {

    if {$have_bwidget == 1} {
      # BWidget Tree
      frame $w.tree_frame
      frame $w.tree_frame_x
      Tree $w.tree -width $tree_window_width -height $tree_window_lines \
                   -opencmd "$open_dir_cmd $w.tree" \
                   -closecmd "browse_tree_close_dir $w.tree" \
                   -selectfill 1 \
                   -yscrollcommand "browse_tree_yscrollcommand $w" \
                   -xscrollcommand "$w.treescroll_x set"

      # ??? why doesn't <Return> work ?
      # $w.tree bindText <Return> \
      #                  "browse_tree_accept_bindtext $adr_var 1 $w.tree"

      # At least double-click does work
      $w.tree bindText <Double-Button-1> \
                       "browse_tree_accept_escd $adr_var 1 $w.tree"

      $w.tree bindText <Button-3> {browse_tree_help "Browse tree" grey}

      scrollbar $w.treescroll_y_l -command "$w.tree yview"
      scrollbar $w.treescroll_y_r -command "$w.tree yview"
      scrollbar $w.treescroll_x -orient horizontal -command "$w.tree xview "
      pack $w.tree -in $w.tree_frame_x -side top  -expand 1 -fill both
      pack $w.treescroll_x -in $w.tree_frame_x -side top  -expand 1 -fill x
      pack $w.treescroll_y_l -in $w.tree_frame -side left -expand 1 -fill y
      pack $w.tree_frame_x   -in $w.tree_frame -side left -expand 1 -fill both
      pack $w.treescroll_y_r -in $w.tree_frame -side left -expand 1 -fill y

      frame $w.button_line
      button $w.accept -text "Accept" -width $tree_window_button_width \
                       -command "browse_tree_accept_sel $adr_var 1 $w.tree"
      bind_help $w.accept "Accept (browse tree)"
      button $w.to_field -text "Edit" -width $tree_window_button_width \
                         -command "browse_tree_accept_sel $adr_var 0 $w.tree"
      bind_help $w.to_field "Edit (browse tree)"
      button $w.up -text "Up" -width $tree_window_button_width \
                   -command "browse_tree_up $adr_var $w.tree $which_fs"
      bind_help $w.up "Up (browse tree)"
      button $w.down -text "Down" -width $tree_window_button_width \
                     -command "browse_tree_down $adr_var $w.tree $which_fs"
      bind_help $w.down "Down (browse tree)"
      pack $w.up $w.down $w.accept $w.to_field \
           -in $w.button_line -side left -expand 0

      pack $w.tree_frame -side top -anchor w -expand 1 -fill both
    } else {
      frame $w.button_line
      button $w.accept -text "Accept" -width $tree_window_button_width \
                       -command "browse_tree_accept_entry $adr_var 1 $w.tree"
      bind_help $w.accept "Accept (browse tree)"
      pack $w.accept -in $w.button_line -side left -expand 0
    }
    button $w.help -text "Help" -width $tree_window_button_width \
                   -command {window_help "Browse tree" grey}
    bind_help $w.help "Browse tree"
    button $w.close -text "Close" -width $tree_window_button_width \
                    -command "$destroy_cmd $w" \
                    -background $button_color
    bind_help $w.close "Close (browse tree)"
    pack $w.help $w.close \
         -in $w.button_line -side left -expand 0

    pack $w.button_line -side top -anchor center

    frame $w.text_frame
    label $w.topic -text "[browse_tree_topic $adr_var]"
    bind_help $w.topic "Browse tree"
    entry $w.text_entry -relief sunken -bd 1 -width 40 \
                        -textvariable $adr_var
    bind_entry_keys $w.text_entry \
                    "browse_tree_accept_entry $adr_var 1 $w.tree"
    bind_help $w.text_entry "Browse tree"
    pack $w.topic -in $w.text_frame -side left
    pack $w.text_entry -in $w.text_frame -side left -expand 1 -fill both
    pack $w.text_frame -side top -expand 1 -fill both
  }
  raise $w
  if {$have_bwidget == 1} {
    browse_tree_populate $which_fs
    focus $w.tree
  }
  update idletasks
}

 
# ------ GUI display procedures ----


# Display a message of xorriso or of this frontend in the .msglist box
#
proc display_msg {msg} {
  global .msglist
  global msglist_max_fill msglist_running pre_msglist display_msg_enabled

  if {$display_msg_enabled == 0} {return ""}
  if {$msg == "============================" || \
  $msg == "==============================================================" || \
  $msg == "enter option and arguments :"} {return ""}
    
  if {$msglist_running == 0} {
    lappend pre_msglist $msg
  } else {
    if {[.msglist index end] > $msglist_max_fill} {
      .msglist delete 0 0
    }
    .msglist insert end [escape_newline $msg 0]
    .msglist see [expr "[.msglist index end]-1"]
    update idletasks
  }
}


# Set whether messages submitted to proc display_message shall really show up
# This is used by callback procedures to hide auxiliary commands and lengthy
# reply messages from the user display.
#
proc set_display_msg {mode} {
  global display_msg_enabled

  set old $display_msg_enabled
  if {$mode == "0"} {
    set display_msg_enabled 0
  } else {
    set display_msg_enabled "1"
  }
  return $old
}


# Display a frontend error message in the .msglist box and by a pop-up window.
#
proc xorriso_tcltk_errmsg {msg} {
  global highest_cmd_sev_msg

  set highest_cmd_sev_msg [escape_newline $msg 0]
  display_msg $msg
  window_ack $msg "grey" "toplevel"
  update idletasks
}


# Memorize the current selection in the .isolist box.
#
proc memorize_isolist_selection {} {
  global memorized_isolist_selection isolist_names
  global .isolist

  set memorized_isolist_selection ""
  set selected [.isolist curselection]
  foreach i $selected {
    lappend memorized_isolist_selection [lindex $isolist_names $i]
  }
}


# Restore the memorized selection in the .isolist box as far as the
# names have survived in the meantime.
#
proc restore_isolist_selection {} {
  global memorized_isolist_selection isolist_names
  global .isolist

  .isolist selection clear 0 end
  foreach i $memorized_isolist_selection {
    set idx [lsearch -exact $isolist_names $i]
    if {$idx > -1} {
      .isolist selection set $idx $idx
    }
  }
  set memorized_isolist_selection ""
}


# Receive the answer of the yes/no window and destroy it.
#
proc destroy_yesno {w answer} {
  global yesno_window_is_active answer_of_yesno yesno_window_geometry
  global yesno_to_all

  if {$w != ""} {
    set yesno_window_geometry [wm geometry $w]
    grab release $w
    destroy $w
    update idletasks
  }
  set yesno_window_is_active 0
  set answer_of_yesno $answer
  if {$answer == 2} {
    set yesno_to_all 1
    set answer_of_yesno 1
  }
  if {$answer == -1} {
    set yesno_to_all -1
    set answer_of_yesno 0
  }
}
  

# Pop-up a window which asks for yes or no. Return 1 if answer is yes.
#
proc window_yesno {question} {
  global answer_of_yesno yesno_window_is_active yesno_window_geometry

  set w {.yesno_window}
  if {$yesno_window_is_active == 1} {
    set yesno_window_is_active [window_exists $w]
  }
  if {$yesno_window_is_active == 1} {
    raise $w
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You still need to answer an older yes/no question"
    return "0"
  }
  set yesno_window_is_active 1
  set answer_of_yesno ""
  toplevel $w -borderwidth 20 -class Dialog
  wm title $w "xorriso-tcltk yes/no"
  set_window_position $w $yesno_window_geometry
  label $w.question -text $question
  button $w.yes -text "yes" -command "destroy_yesno $w 1" \
                -borderwidth 4 -padx 20 -pady 20
  bind_help $w.yes "yes/no"
  button $w.no -text "no" -command "destroy_yesno $w 0" \
                -borderwidth 4 -padx 20 -pady 20
  bind_help $w.no "yes/no"
  pack $w.yes $w.question $w.no -side left
  update idletasks

  grab set $w
  tkwait variable answer_of_yesno
  return $answer_of_yesno
}


# Pop-up a window which asks for yes, yes-to-all, no, or no-to-all.
# Return 1 if answer is yes.
#
proc window_yesno_ever {question} {
  global answer_of_yesno yesno_window_is_active yesno_window_geometry
  global yesno_to_all

  set w {.yesno_window}
  if {$yesno_window_is_active == 1} {
    set yesno_window_is_active [window_exists $w]
  }
  if {$yesno_window_is_active == 1} {
    raise $w
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You still need to answer an older yes/no question"
    return "0"
  }
  if {$yesno_to_all == 1} {
    return "1"
  }
  if {$yesno_to_all == -1} {
    return "0"
  }
  set yesno_window_is_active 1
  set answer_of_yesno ""
  toplevel $w -borderwidth 20 -class Dialog
  wm title $w "xorriso-tcltk yes/no"
  set_window_position $w $yesno_window_geometry

  frame $w.yes_frame
  frame $w.no_frame
  label $w.question -text $question
  button $w.yes -text "yes" -command "destroy_yesno $w 1" \
                -borderwidth 4 -padx 20 -pady 20 -relief raised
  button $w.no -text "no" -command "destroy_yesno $w 0" \
                -borderwidth 4 -padx 20 -pady 20 -relief raised 
  button $w.yes_to_all -text "yes to all" -command "destroy_yesno $w 2"
  bind_help $w.yes_to_all "yes to all"
  button $w.no_to_all -text "no to all" -command "destroy_yesno $w -1"
  bind_help $w.no_to_all "no to all"
  pack $w.yes $w.yes_to_all -in $w.yes_frame -side top  -expand 1 -fill both
  pack $w.no $w.no_to_all -in $w.no_frame -side top  -expand 1 -fill both
  pack $w.yes_frame $w.question $w.no_frame \
                -in $w -side left -expand 1 -fill both

  raise $w
  update idletasks

  grab set $w
  tkwait variable answer_of_yesno
  return $answer_of_yesno
}


proc reset_yesno_to_all {} {
  global yesno_to_all

  set yesno_to_all 0
}


# Destroy the notification pop-up window.
#
proc destroy_ack {w had_focus} {
  global ack_window_is_active ack_window_geometry

  if {$w != ""} {
    set ack_window_geometry [wm geometry $w]
    grab release $w
    if {$had_focus != "-"} {
      focus $had_focus
    }
    destroy $w
    update idletasks
  }
  set ack_window_is_active 0
}


# Pop-up a window which notifies of a problem and asks for a button click.
#
proc window_ack {question button_color where} {
  global answer_of_yesno ack_window_is_active ack_window_geometry
  global continue_from_ack

  set had_focus [focus]
  if {$had_focus == ""} {set had_focus "-"}
  set re_use_widgets 0
  if {$where == "embedded"} {
    set w ""
    set destroy_cmd ""
  } else {
    set w {.ack_window}
    if {$ack_window_is_active == 1} {
      set ack_window_is_active [window_exists $w]
    }
    if {$ack_window_is_active == 0} {
      toplevel $w -borderwidth 20 -class Dialog
      wm title $w "xorriso-tcltk acknowledge"
      set ack_window_is_active 1
    } else {
      set re_use_widgets 1
    }
    set_window_position $w $ack_window_geometry
    set destroy_cmd "destroy_ack $w $had_focus"
  }
  if {$re_use_widgets == 1} {
    $w.question configure -text $question
  } else {
    label $w.question -text $question
    button $w.ok -text "Continue" -command $destroy_cmd \
                 -background $button_color
    bind $w.ok <Return> $destroy_cmd
    bind_help $w.ok "Continue"
    pack $w.question -side top -expand 1 -fill both
    pack $w.ok -side top 
  }

  raise $w
  update idletasks
  focus $w.ok
  grab set $w
  tkwait variable ack_window_is_active
}


# Destroy the help pop-up window.
#
proc destroy_help {w help_main} {
  global help_window_is_active help_window_has_scroll help_window_geometry
  global main_help_window_is_active main_help_window_geometry

  if {$w != ""} {
    if {$help_main == 1} {
      set main_help_window_geometry [wm geometry $w]
    } else {
      set help_window_geometry [wm geometry $w]
    }
    destroy $w
  }
  if {$help_main == 1} {
    set main_help_window_is_active 0
  } else {
    set help_window_is_active 0
    set help_window_has_scroll 0
  }
}


proc surround_text {text} {
  return "\n\n  [string map {\n "\n  "} $text]\n"
}


# Pop-up a window which shows a help text and a Close button.
#
proc window_help {about_what button_color} {
  global help_window_is_active help_window_lines help_window_has_scroll
  global help_window_border_width help_window_geometry
  global main_help_window_is_active
  global main_help_window_lines main_help_window_geometry
  global .help_window .main_help_window

  # The main help window is independent of the GUI element help window
  if {$about_what == "Help"} {
    set help_main 1
    set w {.main_help_window}
    set window_is_active $main_help_window_is_active
    set window_has_scroll 1
    set old_geometry $main_help_window_geometry
    set window_lines $main_help_window_lines
  } else {
    set help_main 0
    set w {.help_window}
    set window_is_active $help_window_is_active
    set window_has_scroll $help_window_has_scroll
    set old_geometry $help_window_geometry
    set window_lines $help_window_lines
  }
  if {$window_is_active == 1} {
    set window_is_active [window_exists $w]
  }

  # Giving the help text some distance from the border decorations
  set line_width 82
  set helptext "\n\n  [string map {\n "\n  "} [tell_helptext $about_what]]\n"

  if {[count_newlines $helptext] >= $window_lines} {
    if {$window_is_active == 1 && $window_has_scroll == 0} {
       destroy_help $w $help_main
       set window_is_active 0
    }
    if {$help_main == 1} {
      set old_geometry $main_help_window_geometry
    } else {
      set help_window_has_scroll 1
      set window_has_scroll 1
      set old_geometry $help_window_geometry
    }
  }
  # Dealing with initiating windows that are grabbed
  set grabbed [grab current]
  if {$grabbed == ""} {set grabbed "-"}
  if {$grabbed != "-" && $window_is_active == 1} {
     destroy_help $w $help_main
     set window_is_active 0
  }
  if {$grabbed != "-"} {
    # Set old_geometry to position underneath grabbed window
    set value [wm geometry $grabbed]
    set idx [string first "+" $value]
    set height_idx [string first "x" $value]
    if {$idx != -1 && $height_idx != -1 && $idx > $height_idx} {
      set width [string range $value 0 [expr $height_idx-1]]
      set height [string range $value [expr $height_idx+1] [expr $idx-1]]
      set x [string range $value [expr $idx+1] end]
      set idx [string first "+" $x]
      if {$idx != -1} {
        set y [string range $x [expr $idx+1] end]
        set x [string range $x 0 [expr $idx-1]]
        set y [expr $y+$height]
        set old_geometry "${width}x${height}+${x}+${y}"
      }
    }
  }

  set re_use_widgets 0
  if {$window_is_active == 0} {
    toplevel $w -borderwidth $help_window_border_width -class Help
    set_window_position $w $old_geometry
    if {$help_main == 1} {
      wm title $w "xorriso-tcltk main help text"
      set main_help_window_is_active 1
      reset_to_normal_background .help
      update idletasks
    } else {
      wm title $w "xorriso-tcltk GUI element help text"
      set help_window_is_active 1
    }
  } else {
    set re_use_widgets 1
  }

  if {$re_use_widgets == 1} {
    $w.text configure -state normal
    $w.text delete 1.0 end
    $w.text insert end $helptext
    raise $w
  } else {
    set destroy_cmd "destroy_help $w $help_main"

    frame $w.text_frame
    text $w.text -width $line_width -height $window_lines \
                 -relief flat -borderwidth 0
    $w.text insert end $helptext
    pack $w.text -in $w.text_frame -side left  -expand 1 -fill both
    if {$window_has_scroll == 1} {
      scrollbar $w.scroll_y -command "$w.text yview"
      $w.text configure -yscrollcommand "$w.scroll_y set"
      bind_listbox_keys $w.text $window_lines "text"
      pack $w.scroll_y -in $w.text_frame -side left -fill y
    } 

    button $w.close -text "Close" -command $destroy_cmd \
                    -background $button_color
    pack $w.text_frame -side top -expand 1 -fill both
    frame $w.middle_spacer -height 6
    frame $w.bottom_spacer -height 6
    pack $w.middle_spacer $w.close $w.bottom_spacer -side top 
  }
  $w.text configure -state disabled
}


# Display the busy/ready state of xorriso.
# Called with 1 by sender of commands and with 0 by receivers of replies .
#  
proc display_busy {state} {
  global busy_text_exists
  global .busy_text 

  if {$busy_text_exists == 0} {return ""}

  if {$state == 0} {
    .busy_text configure -text "ready"
    .busy_text configure -background "#D0D0D0"
  } else {
    .busy_text configure -text "busy"
    .busy_text configure -background "#808080"
  }
  update idletasks
}


# Tries to make use of the BWidget package for getting its Tree widget
#
proc check_for_bwidget {} {
  global have_bwidget bwidget_version

  if {$have_bwidget == 0} {
    catch {
      set bwidget_version [package require BWidget]
      set have_bwidget 1
    }
  }
}


# A window to display if no file browser is available
#
proc browser_dummy {} {
  window_ack \
    "The file browser cannot be used because Tcl/Tk package \"BWidget\" is not loaded" "grey" "toplevel"
}


# Obtain the geometry string of a window
#
proc get_window_geometry {w} {
  wm geometry $w
}


# Set the position of a window from a geometry string
#
proc set_window_position {w geometry} {
  set value $geometry
  set idx [string first "+" $value]
  if {$idx == -1} {
    set value [wm geometry .]
    set idx [string first "+" $value]
  }
  if {$idx == -1} { return "" }
  set pos [string range $value $idx end]
  wm geometry $w $pos
}


# Reset button appearance from startup color to normal color
#
proc reset_to_normal_background {w} {
  set normal_color [.drive_drop_both cget -background]
  $w configure -background $normal_color
}


# Checks whether a window is really there
#
proc window_exists {w} {
  set window_exists 0
  catch {
    $w cget -background
    set window_exists 1
  }
  return $window_exists
}


# ------ Building GUI components ------

# ------ GUI layout parameters ------

# The default position of the main window
set main_window_geometry ""

# How to mark the borders of the main grouping frames
set main_framerelief ridge
set main_borderwidth 4

# How to mark the borders of the second level grouping frames
set borderwidth 1

# Number of lines in msglist display
set msglist_lines 8 
set msglist_max_fill 1000
set msglist_running 0

# Number of lines in drivelist display
set drivelist_lines 2

# Number of lines in ISO directory content display
set isolist_lines 8

# Whether the message box shall export its selection to the whole X display
set export_msg_selection true

# Whether the item lists shall export their selection
set export_selection false

# The number of lines in the display of the help texts
set main_help_window_lines 24
set help_window_lines 16

# The distance of the help text from the help window border
set help_window_border_width 0

# The number of items to display in a tree browser window
set tree_window_lines 12
# The number of visible characters in a tree browser line
set tree_window_width 50
# The width in characters of the six buttons under the tree browser
set tree_window_button_width 6


# -------- GUI definition procedures


# Overall definition of the GUI
#
proc init_gui {} {
  global .input .cmdline_entry .msgbox  .errmsg .dev .drivebox
  global .isobox .localfs
  global main_framerelief main_borderwidth click_to_focus

  check_for_bwidget

  # Main grouping frames
  frame .connection_block \
        -relief $main_framerelief -borderwidth $main_borderwidth
  frame .drive_block \
        -relief $main_framerelief -borderwidth $main_borderwidth
  frame .iso_block \
        -relief $main_framerelief -borderwidth $main_borderwidth

  init_input
  init_msgbox
  init_errmsg
  init_dev
  init_drivebox
  init_isobox
  init_isomanip
  init_burn
  init_localfs

  pack .input .msgbox .errmsg -in .connection_block \
       -side top -expand 1 -fill both
  pack .drivebox .dev .burn -in .drive_block \
       -side top -expand 1 -fill both
  pack .localfs .isobox .isomanip -in .iso_block \
       -side top -expand 1 -fill both

  pack .connection_block .drive_block .iso_block \
       -side top -expand 1 -fill both

  if {$click_to_focus == 1} {
    focus .msglist
  }
}


# The xorriso headline with End button, xorriso version, busy/ready indicator,
# command line, and "Refresh disp" button.
#
proc init_input {} {
  global borderwidth busy_text_exists xorriso_version debug_logging
  global cmd_logging_mode cmd_log_target osirrox_allowed cmd_logging_all
  global .input .input_line1 .xorriso_version .busy .busy_text
  global .refresh_state .end_button .cmdline .log_pipes_switch

  set extract_state "normal"
  if {$osirrox_allowed == 0} {set extract_state "disabled"}

  frame .input -borderwidth $borderwidth
  frame .input_line1 -borderwidth 0
  pack .input_line1 -in .input \
                    -side top -anchor w -expand 1 -fill both

  button .end_button -text "End" -command "end_xorriso"
  bind_help .end_button "End"

  if {[string length $xorriso_version] > 10} {
    set xorriso_version [string range $xorriso_version 0 9]
  }
  label .xorriso_version -text "xorriso-$xorriso_version"
  bind_help .xorriso_version "version"
  frame .busy -relief ridge -borderwidth 2
  label .busy_text -width 5 -text "busy"
  bind_help .busy_text "ready/busy"
  set busy_text_exists 1
  pack .busy_text -in .busy

  button .refresh_state -text "Refresh disp" \
                        -command "refresh_state"
  bind_help .refresh_state "Refresh disp"

  menubutton .script_log -text "Script/Log" -anchor w \
                         -direction below -relief ridge -indicatoron 1 \
                          -menu .script_log.menu
  bind_help .script_log "Script/Log"
  set m ".script_log.menu"
  menu $m
  $m add checkbutton -label "Log command script" \
                     -indicatoron 1 -selectcolor "" \
                     -command "effectuate_command_logging 0" \
                     -variable cmd_logging_mode \
                     -onvalue 1 -offvalue 0
  $m add checkbutton -label "Log non-essential commands" \
                     -indicatoron 1 -selectcolor "" \
                     -variable cmd_logging_all \
                     -onvalue 1 -offvalue 0
  $m add command     -label "Set log script address" \
                     -command "set_log_script_address"
  $m add separator
  $m add checkbutton -label "Log pipes" \
                     -indicatoron 1 -selectcolor "" \
                     -variable debug_logging \
                     -onvalue 1 -offvalue 0
  $m add command     -label "Set pipe log address" \
                     -command "set_debug_log_address"
  $m add separator
  $m add separator
  $m add command     -label "Execute command script" \
                     -command "start_script_execution"
  $m add checkbutton -label "Allow extract to disk" \
                     -state $extract_state \
                     -indicatoron 1 -selectcolor "" \
                     -variable script_with_osirrox \
                     -onvalue 1 -offvalue 0
  $m add separator
  $m add command     -label "Permanently ban extraction" \
                     -state $extract_state \
                     -command "osirrox_banned"

  button .help -text "Help" -command {window_help "Help" "grey"} \
               -background "grey"
  bind_help .help "Help"

  init_cmdline

  pack .end_button .xorriso_version .busy -in .input_line1 -side left
  pack .cmdline \
       -in .input_line1 -side left -expand 1 -fill both
  pack .refresh_state .script_log .help -in .input_line1 -side left
}


# The combination of "Command:" label and command line
#
proc init_cmdline {} {
  global cmdline borderwidth
  global .cmdline .cmdline_text .cmdline_entry

  frame .cmdline -borderwidth 0

  label .cmdline_text -width 10 -text "Command:"
  bind_help .cmdline_text "Command:"
  entry .cmdline_entry -width 56 -relief sunken -bd 1 \
                              -textvariable cmdline
  bind_entry_keys .cmdline_entry {cmdline_return}
  bind_help .cmdline_entry "Command:"

  # >>> is there a chance to get a history on an entry ?

  pack .cmdline_text -in .cmdline -side left 
  pack .cmdline_entry -in .cmdline -side left -expand 1 -fill both
}


# The listbox where to display commands and reply messages unless this is
# disabled for auxiliary commands which shall not clutter the display.
#
proc init_msgbox {} {
  global borderwidth
  global msglist_lines export_msg_selection msglist_running pre_msglist
  global .msgbox .msglist .msgscroll

  frame .msgbox -borderwidth $borderwidth

  listbox .msglist -height $msglist_lines -selectmode extended \
                   -yscrollcommand ".msgscroll set" \
                   -exportselection $export_msg_selection
  bind_listbox_keys ".msglist" $msglist_lines "listbox"
  bind_help .msglist "message box"
  set msglist_running 1
  foreach i $pre_msglist {
    display_msg [escape_newline $i 0]
  }
  scrollbar .msgscroll -command ".msglist yview"
  pack .msglist   -in .msgbox -side left  -expand 1 -fill both
  pack .msgscroll -in .msgbox -side right -fill y
  set pre_msglist ""
}


# Two display lines for most severe event messages. One gets reset with
# each important command. The other one stays until the user clears it.
#
proc init_errmsg {} {
  global borderwidth
  global .errmsg .total_errmsg .cmd_errmsg

  frame .errmsg -borderwidth $borderwidth

  init_total_errmsg
  init_cmd_errmsg

  pack .cmd_errmsg .total_errmsg -in .errmsg \
       -side top -anchor w -expand 1 -fill both
 
}


# The most severe message display which gets reset automatically.
#
proc init_cmd_errmsg {} {
  global borderwidth
  global .cmd_errmsg .cmd_errmsg_label .cmd_errmsg_msg

  frame .cmd_errmsg -borderwidth $borderwidth

  label .cmd_errmsg_label -width 14 -text "Recent problem:" -anchor w
  bind_help .cmd_errmsg_label "Recent problem:"
  label .cmd_errmsg_msg -width 80 -relief ridge -bd 2 \
                        -anchor w \
                        -textvariable highest_cmd_sev_msg
  # (no keys, no focus)
  bind_help .cmd_errmsg_msg "Recent problem:"
  pack .cmd_errmsg_label -in .cmd_errmsg -side left
  pack .cmd_errmsg_msg -in .cmd_errmsg -side left -expand 1 -fill both
}


# The persistent most severe message display that is to be reset by the user.
#
proc init_total_errmsg {} {
  global borderwidth
  global .total_errmsg .total_errmsg_label .total_errmsg_msg
  global .total_errmsg_clear

  frame .total_errmsg -borderwidth $borderwidth

  label .total_errmsg_label -text "Worst problem:" -width 14 -anchor w
  bind_help .total_errmsg_label "Worst problem:"
  button .total_errmsg_clear -text "Clear" \
                             -width 5 \
                             -command "clear_total_errmsg"
  bind_help .total_errmsg_clear "Clear"
  label .total_errmsg_msg -width 80 -relief ridge -bd 2 \
                          -anchor w \
                          -textvariable highest_total_sev_msg
  # (no keys, no focus)
  bind_help .total_errmsg_msg "Worst problem:"
  pack .total_errmsg_label -in .total_errmsg -side left 
  pack .total_errmsg_msg -in .total_errmsg -side left -expand 1 -fill both
  pack .total_errmsg_clear -in .total_errmsg -side left
}


# The list of drives which were found by scanning, the Scan button, and
# buttons for picking a drive from the list, for giving them up, for
# calming them down, and for reloading the ISO image from the input drive.
#
proc init_drivebox {} {
  global borderwidth drivelist_lines export_selection
  global .drivebox .drivelistbox .drivelist .drivescroll .drive_scan
  global .drive_picker .drive_scan .drive_pick_in .drive_pick_out
  global .drive_pick_both .drive_drop_both .drive_calm .iso_rollback_button


  frame .drivebox -borderwidth $borderwidth
  frame .drivelistbox -borderwidth $borderwidth
  listbox .drivelist -height $drivelist_lines -selectmode extended \
                     -yscrollcommand ".drivescroll set" \
                     -exportselection $export_selection
  bind_listbox_keys ".drivelist" $drivelist_lines "listbox"
  bind_help .drivelist "drivelist"
  scrollbar .drivescroll -command ".drivelist yview"

  pack .drivelist   -in .drivelistbox -side left  -expand 1 -fill both
  pack .drivescroll -in .drivelistbox -side right -fill y 

  frame .drive_picker -borderwidth $borderwidth
  frame .drive_picker_line_1 -borderwidth 0
  frame .drive_picker_line_2 -borderwidth 0
  frame .drive_aux_buttons_line_1 -borderwidth 0
  frame .drive_aux_buttons_line_2 -borderwidth 0
  frame .drive_aux_buttons -borderwidth 0

  button .drive_scan -text "Scan for drives" \
                     -background "grey" \
                     -command "scan_for_drives"
  bind_help .drive_scan "Scan for drives"
  button .drive_pick_in -text "Pick input drive" \
                        -command "pick_indev"
  bind_help .drive_pick_in "Pick input drive"
  button .drive_pick_out -text "Pick output drive" \
                         -command "pick_outdev"
  bind_help .drive_pick_out "Pick output drive"
  button .drive_pick_both -text "Pick drive for both roles" \
                         -command "pick_dev"
  bind_help .drive_pick_both "Pick drive for both roles"
  button .drive_drop_both -text "Give up drives" \
                         -command "give_up_dev"
  bind_help .drive_drop_both "Give up drives"
  button .drive_calm -text "Calm drives" \
                         -command "calm_drives"
  bind_help .drive_calm "Calm drives"
  button .iso_rollback_button -text "Rollback" -width 9 \
                              -command {iso_rollback}
  bind_help .iso_rollback_button "Rollback"

  # One button block left, one right
  pack .drive_pick_in .drive_pick_out \
       -in .drive_picker_line_1 -side left -expand 1 -fill none
  pack .drive_pick_both \
       -in .drive_picker_line_2 -side left -expand 1 -fill x
  pack .drive_picker_line_1 .drive_picker_line_2 \
       -in .drive_picker -side top -expand 1 -fill x -anchor w
  pack .drive_scan .drive_calm  \
       -in .drive_aux_buttons_line_1 -side left -expand 1 -fill none
  pack .drive_drop_both .iso_rollback_button \
       -in .drive_aux_buttons_line_2 -side left -expand 1 -fill x
  pack .drive_aux_buttons_line_1 .drive_aux_buttons_line_2 \
       -in .drive_aux_buttons -side top -expand 1 -fill x -anchor w
  pack .drive_picker -in .drivebox -side left -expand 0 -fill none
  pack .drivelistbox -in .drivebox  -side left -expand 1 -fill both
  pack .drive_aux_buttons -in .drivebox -side left -expand 0 -fill none

  bind .drivelist <Double-Button-1> {
    pick_dev
  }
}


# The text fields for setting and display of the current input and output
# drives. With Eject button and a short text description of the medium status.
#
proc init_dev {} {
  global borderwidth
  global .dev .indev .outdev

  frame .dev -borderwidth $borderwidth

  init_indev
  init_outdev 

  pack .indev .outdev -in .dev \
                    -side top -anchor w -expand 1 -fill both
}


# Set and display the current input drive.
#
proc init_indev {} {
  global borderwidth indev_adr
  global .indev .indev_eject .indev_label .indev_entry .indev_summary

  frame .indev -borderwidth $borderwidth

  button .indev_eject -text "Eject" -command {eject_indev}
  bind_help .indev_eject "Eject (indev)"

  button .indev_label -width 16 -text "Input drive/image  " \
                      -command {indev_return}   

  bind_help .indev_label "Input drive/image"
  entry .indev_entry -width 34 -relief sunken -bd 1 \
                     -textvariable indev_adr

  bind_entry_keys .indev_entry {indev_return}
  bind_help .indev_entry  "Input drive/image"
  label .indev_summary -width 60 -text "" -relief ridge -borderwidth 2
  bind_help .indev_summary  "input drive info"
  create_browser_button .indev_browse_button \
                        "indev_adr" "localfs" "Browse disk (indev)"

  pack .indev_eject .indev_label .indev_entry \
       -in .indev -side left -expand 1 -fill both
  pack .indev_browse_button -in .indev -side left 
  pack .indev_summary \
       -in .indev -side left -expand 1 -fill both
}


# Set and display the current output drive.
#
proc init_outdev {} {
  global .outdev .outdev_eject .outdev_label .outdev_entry .outdev_summary
  global borderwidth outdev_adr

  frame .outdev -borderwidth $borderwidth

  button .outdev_eject -text "Eject" -command {eject_outdev}
  bind_help .outdev_eject "Eject (outdev)"

  button .outdev_label -width 16 -text "Output drive/image" \
                       -command {outdev_return}   

  bind_help .outdev_label "Output drive/image"
  entry .outdev_entry -width 34 -relief sunken -bd 1 \
                      -textvariable outdev_adr
  bind_entry_keys .outdev_entry {outdev_return}
  bind_help .outdev_entry "Output drive/image"
  create_browser_button .outdev_browse_button \
                        "outdev_adr" "localfs" "Browse disk (outdev)"
  label .outdev_summary -width 60 -text "" -relief ridge -borderwidth 2
  bind_help .outdev_summary "output drive info"
  pack .outdev_eject .outdev_label .outdev_entry \
       -in .outdev -side left -expand 1 -fill both
  pack .outdev_browse_button -in .outdev -side left 
  pack .outdev_summary \
       -in .outdev -side left -expand 1 -fill both
}


# The button panel for blanking, formatting, and writing to the output drive.
#
proc init_burn {} {
  global borderwidth burn_write_image_adr burn_write_close burn_write_tao
  global burn_write_defect_mgt
  global .burn .burn_blank_button .burn_format_button .burn_commit_button
  global .burn_write_image .burn_write_image_entry .burn_write_close
  global .burn_write_tao .burn_write_defect_mgt

  frame .burn -borderwidth $borderwidth
 
  button .burn_blank_button -text "Blank" \
                            -command {burn_blank}
  bind_help .burn_blank_button "Blank"
  button .burn_format_button -text "Format" \
                             -command {burn_format}
  bind_help .burn_format_button "Format"
  button .burn_commit_button -text "Write ISO session" \
                             -command {burn_commit}
  bind_help .burn_commit_button "Write ISO session"
  button .burn_write_image -text "Burn image file:" \
                           -command {burn_write_image}
  bind_help .burn_write_image "Burn image file:"
  entry .burn_write_image_entry -width 40 -relief sunken -bd 1 \
                                -textvariable burn_write_image_adr
  bind_entry_keys .burn_write_image_entry {burn_write_image}
  bind_help .burn_write_image_entry "Burn image file:"
  create_browser_button .burn_image_browse_button \
                    "burn_write_image_adr" "localfs" "Browse disk (burn image)"
  checkbutton .burn_write_close -text "Close" \
                                -indicatoron 1 -selectcolor "" \
                                -relief ridge -borderwidth 2 \
                                -variable burn_write_close \
                                -onvalue 1 -offvalue 0
  bind_help .burn_write_close "Close"
  checkbutton .burn_write_tao -text "TAO" \
                                -indicatoron 1 -selectcolor "" \
                                -relief ridge -borderwidth 2 \
                                -variable burn_write_tao \
                                -onvalue 1 -offvalue 0
  bind_help .burn_write_tao "TAO"
  checkbutton .burn_write_defect_mgt -text "Defect Mgt" \
                                -indicatoron 1 -selectcolor "" \
                                -relief ridge -borderwidth 2 \
                                -variable burn_write_defect_mgt \
                                -onvalue 1 -offvalue 0
  bind_help .burn_write_defect_mgt "Defect Mgt"
  pack .burn_blank_button .burn_format_button \
       .burn_commit_button .burn_write_close .burn_write_tao \
       .burn_write_defect_mgt \
       .burn_write_image .burn_write_image_entry \
       -in .burn -side left -expand 1 -fill both
  pack .burn_image_browse_button -in .burn -side left
}


# Set and display the current ISO directory and its content.
#
proc init_isobox {} {
  global borderwidth isolist_lines export_selection
  global .isobox .isodir .isolist .isodir_entry .isodir_up .isodir_up2
  global .isodir_label .isodir_verify .isolistbox .isoscroll_y .isoscroll_x
  
  frame .isobox -borderwidth $borderwidth
  frame .isodir -borderwidth 0

  label .isodir_label -text "ISO directory:" \
                      -width 14
  bind_help .isodir_label "ISO directory:"
  entry .isodir_entry -width 60 -relief sunken -bd 1 \
                      -textvariable isodir_adr
  bind_entry_keys .isodir_entry {isodir_return "isodir_entry"}
  bind_help .isodir_entry "ISO directory:"
  create_browser_button .isodir_browse_button \
                        "isodir_adr" "isofs" "Browse ISO (isodir)"
  button .isodir_verify -text "Verify" -command {isodir_verify}
  bind_help .isodir_verify "Verify"
  button .isodir_up -text "Up" -command {isodir_up}
  bind_help .isodir_up "Up"
  button .isodir_up2 -text "Up" -command {isodir_up}
  bind_help .isodir_up2 "Up"
  pack .isodir_label .isodir_up \
       -in .isodir -side left
  pack .isodir_entry \
       -in .isodir -side left -expand 1 -fill both
  pack .isodir_browse_button .isodir_up2 .isodir_verify \
       -in .isodir -side left

  frame .isolistbox -borderwidth 0
  listbox .isolist -height $isolist_lines -selectmode extended \
                   -yscrollcommand ".isoscroll_y set" \
                   -xscrollcommand ".isoscroll_x set" \
                   -exportselection $export_selection
  bind_listbox_keys ".isolist" $isolist_lines "listbox"
  bind_help .isolist "isolist"
  scrollbar .isoscroll_y -command ".isolist yview"
  scrollbar .isoscroll_x -orient horizontal -command ".isolist xview"
  pack .isolist     -in .isolistbox -side left  -expand 1 -fill both
  bind .isolist <Double-Button-1> { pick_isodir }
  pack .isoscroll_y -in .isolistbox -side right -fill y 

  pack .isodir .isolistbox .isoscroll_x \
       -in .isobox -side top -expand 1 -fill both
}


# The ISO-internal manipulation buttons for the ISO directory or its content.
# Plus a text field where to set an ISO path as target for renaming or
# directory making.
#
proc init_isomanip {} {
  global borderwidth isomanip_move_target
  global .isomanip .isomanip_move .isomanip_prefix .isomanip_verify_button
  global .isomanip_move_target .isomanip_rm_r_button .isomanip_move_button
  global .isomanip_mkdir_button .isomanip_move_target
  global .avail_label .avail_label_frame .avail_button

  frame .isomanip -borderwidth $borderwidth
  frame .isomanip_move -borderwidth 0
 
  label .isomanip_prefix -text "Selection:"
  bind_help .isomanip_prefix "Selection:"

  button .isomanip_verify_button -text "Verify" \
                                -command {isomanip_verify}
  bind_help .isomanip_verify_button "Verify (selection)"
  button .isomanip_rm_r_button  -text "Delete" \
                                -command {isomanip_rm_r}
  bind_help .isomanip_rm_r_button "Delete"
  button .isomanip_move_button -text "Move to:" \
                               -command {isomanip_mv}
  bind_help .isomanip_move_button "Move to:"
  button .isomanip_mkdir_button  -text "Make dir" \
                                -command {isomanip_mkdir}
  bind_help .isomanip_mkdir_button "Make dir"
  entry .isomanip_move_target -width 60 -relief sunken -bd 1 \
                             -textvariable isomanip_move_target
  bind_entry_keys .isomanip_move_target {isomanip_mv}
  bind_help .isomanip_move_target "rename and mkdir target"
  create_browser_button .isomanip_move_target_button \
                      "isomanip_move_target" "isofs" "Browse ISO (move target)"

  pack .isomanip_prefix .isomanip_verify_button .isomanip_rm_r_button \
       .isomanip_move_button \
       -in .isomanip_move -side left
  pack .isomanip_move_target \
       -in .isomanip_move -side left -expand 1 -fill both
  pack .isomanip_move_target_button -in .isomanip_move -side left
  pack .isomanip_mkdir_button \
       -in .isomanip_move -side left -expand 1 -fill both
  pack .isomanip_move \
       -in .isomanip -side top -expand 1 -fill both
}


# The means for interaction of ISO image and hard disk filesystem.
#
proc init_localfs {} {
  global borderwidth
  global .localfs .extract_frame .aux_control_frame .insert_frame

  frame .localfs -borderwidth $borderwidth

  init_extract
  init_aux_control
  init_insert

  pack .extract_frame .aux_control_frame .insert_frame \
       -in .localfs -side top -expand 1 -fill both
}


# The means for extracting files from ISO image to disk
# 
proc init_extract {} {
  global borderwidth extract_to_adr extract_from_selected extract_underneath
  global osirrox_allowed
  global .extract_button .extract_frame .extract_entry .extract_from_selected
  global .extract_underneath 

  set extract_state "normal"
  if {$osirrox_allowed == 0} {set extract_state "disabled"}

  frame .extract_frame -borderwidth 0
  button .extract_button -text "Extract to disk:" \
                         -state $extract_state \
                         -width 17 \
                         -command {extract_to}
  bind_help .extract_button "Extract to disk:"
  entry .extract_entry -width 40 -relief sunken -bd 1 \
                       -textvariable "extract_to_adr"
  bind_entry_keys .extract_entry {extract_to}
  bind_help .extract_entry "Extract to disk:"
  create_browser_button .extract_browse_button \
                        "extract_to_adr" "localfs" "Browse disk (extract)"
  checkbutton .extract_underneath -text "Underneath" \
                                  -indicatoron 1 -selectcolor "" \
                                  -relief ridge -borderwidth 2 \
                                  -variable extract_underneath \
                                  -onvalue 1 -offvalue 0
  bind_help .extract_underneath "Underneath (extract)"
  checkbutton .extract_from_selected -text "Selected" \
                                     -indicatoron 1 -selectcolor "" \
                                     -relief ridge -borderwidth 2 \
                                     -variable extract_from_selected \
                                     -onvalue 1 -offvalue 0
  bind_help .extract_from_selected "Selected (extract)"
  pack .extract_button -in .extract_frame -side left
  pack .extract_entry \
       -in .extract_frame -side left -expand 1 -fill both
  pack .extract_from_selected .extract_underneath \
       -in .extract_frame -side right
  pack .extract_browse_button -in .extract_frame -side right
}


# Some controls which apply to insertion, extraction, or both.
#
proc init_aux_control {} {
  global borderwidth have_bwidget permission_policy
  global .aux_control_frame
  global .overwrite_iso_files_button .overwrite_dir_button .extract_auto_chmod

  frame .aux_control_frame -borderwidth 0

  menubutton .overwriting -text "Overwriting:" -width 16 -anchor w \
                          -direction above -relief ridge -indicatoron 1 \
                          -menu .overwriting.menu
  bind_help .overwriting "Overwriting:"
  set_overwriting_label
  set m ".overwriting.menu"
  menu $m 
  $m add checkbutton -label "Overwrite ISO files" \
                     -indicatoron 1 -selectcolor "" \
                     -command set_overwriting_label \
                     -variable overwrite_iso_files \
                     -onvalue 1 -offvalue 0
  $m add checkbutton -label "Overwrite ISO dirs" \
                     -indicatoron 1 -selectcolor "" \
                     -command set_overwriting_label \
                     -variable overwrite_iso_dirs \
                     -onvalue 1 -offvalue 0
  $m add checkbutton -label "Overwrite hard disk files" \
                     -indicatoron 1 -selectcolor "" \
                     -command set_overwriting_label \
                     -variable overwrite_disk_files \
                     -onvalue 1 -offvalue 0
  $m add checkbutton -label "Enforce disk dir write access" \
                     -indicatoron 1 -selectcolor "" \
                     -command set_overwriting_label \
                     -variable extract_auto_chmod \
                     -onvalue 1 -offvalue 0

  pack .overwriting -in .aux_control_frame -side left

  menubutton .perm_policy -text "Permissions: as is" -width 22 -anchor w \
                          -direction above -relief ridge -indicatoron 1 \
                          -menu .perm_policy.menu
  set m ".perm_policy.menu"
  menu $m -tearoff 0
  $m add radiobutton -label "as is" -value "as_is" \
                    -variable permission_policy -command show_permission_policy
  $m add radiobutton -label "readable" -value "readable" \
                    -variable permission_policy -command show_permission_policy
  $m add radiobutton -label "readonly" -value "readonly" \
                    -variable permission_policy -command show_permission_policy
  $m add radiobutton -label "mkisofs -r" -value "mkisofs_r" \
                    -variable permission_policy -command show_permission_policy
  show_permission_policy
  bind_help .perm_policy "Permissions:"

  button .avail_button -text "Refresh avail:" \
                             -command {refresh_avail}
  bind_help .avail_button "Refresh avail:"
  frame .avail_label_frame -relief ridge -borderwidth 2
  label .avail_label -width 12 -text ""
  bind_help .avail_label "Refresh avail:"
  pack .avail_label -in .avail_label_frame

  pack .avail_label_frame .avail_button .perm_policy \
       -in .aux_control_frame -side right
}


# The means for inserting files from disk into the ISO image
# 
proc init_insert {} {
  global borderwidth insert_from_adr insert_at_selected insert_underneath
  global .insert_button .insert_from_frame .insert_entry .insert_at_selected
  global .insert_underneath .insert_frame

  frame .insert_frame -borderwidth 0
  frame .insert_from_frame -borderwidth 0
  button .insert_button -text "Insert from disk:" \
                        -width 17 \
                        -command {insert_from}
  bind_help .insert_button "Insert from disk:"
  entry .insert_entry -width 40 -relief sunken -bd 1 \
                      -textvariable "insert_from_adr"
  bind_entry_keys .insert_entry {insert_from}
  bind_help .insert_entry "Insert from disk:"
  create_browser_button .insert_browse_button \
                        "insert_from_adr" "localfs" "Browse disk (insert)"
  checkbutton .insert_underneath -text "Underneath" \
                                 -indicatoron 1 -selectcolor "" \
                                 -relief ridge -borderwidth 2 \
                                 -variable insert_underneath \
                                 -onvalue 1 -offvalue 0
  bind_help .insert_underneath "Underneath (insert)"
  checkbutton .insert_at_selected -text "Selected" \
                                  -indicatoron 1 -selectcolor "" \
                                  -relief ridge -borderwidth 2 \
                                  -variable insert_at_selected \
                                  -onvalue 1 -offvalue 0
  bind_help .insert_at_selected "Selected (insert)"
  pack .insert_button -in .insert_from_frame -side left
  pack .insert_entry \
       -in .insert_from_frame -side left -expand 1 -fill both
  pack .insert_browse_button -in .insert_from_frame -side left
  pack .insert_at_selected .insert_underneath \
       -in .insert_from_frame -side right
  pack .insert_from_frame -in .insert_frame -side left -expand 1 -fill both
}


# Set common behavior of listboxes in respect to focus and navigation keys.
#
proc bind_listbox_keys {box height what_widget} {
  global click_to_focus

  if {$click_to_focus == 1} {
    bind $box <1> "focus \"$box\""
    bind $box <2> "focus \"$box\""
    bind $box <3> "focus \"$box\""
  } else {
    bind $box <Enter> "focus \"$box\""
  }

  # No underlining
  if {$what_widget == "listbox"} {
    $box configure -activestyle "none"
  }

  # Need to evaluate all $box and $height at bind-time. Thus "-quotes.
  bind $box <Any-KeyPress> "
    if {\"%K\" == \"Up\"} {
      $box yview scroll \"-1\" units
    }
    if {\"%K\" == \"Down\"} {
      $box yview scroll 1 units
    }
    if {\"%K\" == \"Prior\"} {
      $box yview scroll -[expr \"$height\" - 1] units
#      $box yview scroll -1 pages
    }
    if {\"%K\" == \"Next\"} {
      $box yview scroll [expr \"$height\" - 1] units
#      $box yview scroll 1 pages
    }
    if {\"%K\" == \"Home\"} {
      $box yview 0
    }
    if {\"%K\" == \"End\"} {
      $box yview end
    }

    # >>> Do i need this ?
      # >>> For now: yes. It prevents double scrolling by PgUp PgDown
    # Prevent other bindings from being performed
    break
  "
}


# Set common behavior of entries in respect to focus and Return key.
#
proc bind_entry_keys {entry return_cmd} {
  global click_to_focus

  if {$click_to_focus != 1} {
    bind $entry <Enter> "focus \"$entry\""
  }
  if {$return_cmd != ""} {
    bind $entry <Return> $return_cmd
  }
}


# Bind a help text to a widget.
#
proc bind_help {to_what help_name} {
  bind $to_what <Button-3> "window_help \"$help_name\" grey"
}


# Create a "/" button and wire it with variable, filesystem type,and help text.
#
proc create_browser_button {button_name var_name which_fs help_name} {
  global have_bwidget

  button $button_name -text "/" -command "browse_tree $var_name $which_fs"
  bind_help $button_name $help_name
}


proc set_overwriting_label {} {
  global overwrite_iso_files overwrite_iso_dirs overwrite_disk_files
  global extract_auto_chmod
  global .overwriting

  # Determine text suffix for menubutton from overwrite variables
  set oif "-"
  if {$overwrite_iso_files == 1} {set oif "f"}
  set oid "-"
  if {$overwrite_iso_dirs == 1} {set oid "d"}
  set ohf "-"
  if {$overwrite_disk_files == 1} {set ohf "h"}
  set fdw "-"
  if {$extract_auto_chmod == 1} {set fdw "w"}
  set otext "Overwriting:  ${oif}${oid}${ohf}${fdw}"
  .overwriting configure -text $otext
}


# The central storage for help texts.
#
proc tell_helptext {what} {
  global own_version argv0 bwidget_version use_command_move

  if {$what == "Help"} {
    return \
"For getting particular help texts:

  Click the rightmost mouse button on any button, list box, or text field. 

For a help text about startup options of this frontend, execute in a shell:

  $argv0 --help

For background info about xorriso and its commands, execute in a shell:

  man xorriso

-----------------------------------------------------------------------------

The GUI window is separated into three main areas:

- The area for connection to xorriso
  - shows xorriso messages,
  - offers some general activities,
  - displays the \"ready/busy\" state of the connection,
  - and allows to toggle xorriso commands into the \"Command:\" field.

- The area for management of drives and ISO image data files
  - allows to scan for optical drives,
  - to acquire them and load their ISO directory tree,
  - to acquire ISO image files from hard disk as pseudo drives like DVD+RW,
  - to blank CD-RW, DVD-RW DVD+RW, BD-RE and format DVD-RW, BD-R,
  - to trigger writing of ISO sessions (which get defined in the third area),
  - and to burn image data files from hard disk to optical media.

- The area for inspection, manipulation, and exploitation of the ISO model
  - allows to insert directories and files from hard disk into the ISO model,
  - to delete and rename file objects in the ISO model,
  - to verify data files of loaded ISO directory trees by MD5,
  - to extract directories and files from ISO filesystem to hard disk.
  
-----------------------------------------------------------------------------
                              Some Use Cases
-----------------------------------------------------------------------------

- Burn a directory as only content onto a CD, DVD or BD
- Write a directory as only content to an ISO image data file on hard disk
- Burn an image data file from hard disk onto CD, DVD or BD
- Add more data to an appendable medium or to an ISO image data file
- Extract a directory tree from an ISO filesystem to hard disk

-----------------------------------------------------------------------------

            Burn a directory as only content onto a CD, DVD or BD

- Click the \"Scan for drives\" button in the middle area.
- Select a drive and click the \"Pick output drive\" button.
- If the information field in the \"Output drive/image\" line begins by
  \"appendable\" or \"closed\" and if the medium is CD-RW, DVD-RW, DVD+RW, or
  BD-RE then click the \"Blank\" button to erase the old data.
  (Blanking of \"DVD-RW sequential recording\" will last very long.)
- Go to the \"Insert from disk:\" line in the lower area.
  Either toggle in the address of the hard disk directory,
  or click on the \"/\" button to the right of the text field to get
  a file browser.
- Hit the Return key in the text field or double click on a name in the
  browser to schedule the disk directory for writing to the medium.
  You may of course insert several directories or files that way.
- Close the browser and click the \"Write ISO session\" button in the
  middle area. Confirm in the \"yes/no\" window that pops up.
  Burning will begin (or refuse on unsuitable medium status).
- When the \"busy\" field displays \"ready\" again, you may click \"Eject\".
  Desktop drives should then put out the tray with the medium.

-----------------------------------------------------------------------------

   Write a directory as only content to an ISO image data file on hard disk

- Go to the text field beside the \"Output drive/image\" button and toggle
  the address of the image file. Click the button or hit the Return key
  when the address is complete.
  Or click on the \"/\" button to the right of the field to get a file browser.
- You may click on a name in the browser and bring it into the text field
  by button \"Edit\".
- When the intended file address is composed, hit the Return key in the
  text field or click the \"Output drive/image\" button.
- If the information field in the \"Output drive/image\" line begins by
  \"appendable\" or \"closed\" then you addressed an existing data file.
  Warning: Applying the \"Blank\" button to it would damage its content !
  You probably do not want this in this special use case.
- Go to the \"Insert from disk:\" line in the lower area.
  Continue like in the above description for CD, DVD, and BD media.

-----------------------------------------------------------------------------

         Burn an image data file from hard disk onto CD, DVD or BD

- Click the \"Scan for drives\" button in the middle area.
- Select a drive and click the \"Pick output drive\" button.
- If the information field in the \"Output drive/image\" line begins by
  \"appendable\" or \"closed\" and if the medium is CD-RW, DVD-RW, DVD+RW, or
  BD-RE then click the \"Blank\" button to erase the old data.
  (Blanking of \"DVD-RW sequential recording\" will last very long.)
- Go to the text field beside the \"Burn image file:\" button and toggle
  the address of the image file. Or click on the \"/\" button to the right
  of the field to get a file browser.
- Hit the Return key in the text field or double click on a name in the
  browser to initiate the burn run.
  Confirm in the \"yes/no\" window that pops up.
- When the \"busy\" field displays \"ready\" again, you may click \"Eject\".
  Desktop drives should then put out the tray with the medium.

-----------------------------------------------------------------------------

      Add more data to an appendable medium or to an ISO image data file

- Like above, \"Scan for drives\" but click button \"Pick drive for both roles\"
  in order to load the directory tree of the existing ISO filesystem.
  For an ISO image data file, bring its name into the input fields of both
  lines \"Input drive/image\" and \"Output drive/image\" and activate it
  by clicking both buttons or hitting the Return key in both fields.
  You should now see in both info fields texts which begin by \"appendable\".
- Go to the \"Insert from disk:\" line in the lower area.
  Use the means described in the first use case to add more directories or
  data files.
- If you are interested in \"Delete\" or \"Move to:\" buttons in the
  bottom line of the GUI: Click them by the rightmost mouse button to see
  their help texts.
- When all intended changes are done: Click \"Write ISO session\" and
  confirm in the \"yes/no\" window.

-----------------------------------------------------------------------------

         Extract a directory tree from an ISO filesystem to hard disk

- Like above, \"Scan for drives\" but click button \"Pick input drive\"
  in order to load the directory tree of the existing ISO filesystem.
  For an ISO image data file, bring its name into the input field of the
  line \"Input drive/image\". You should now see in its info field a text
  which begins by \"appendable\" or \"closed\".
- Go to the \"ISO directory:\" line and list box in the lower area and
  select the directory or file you want to copy to hard disk.
- To get to see the desired file items, either toggle the address of their
  parent directory into the text field and hit Return, or double click items
  to open them as directories, or click the \"/\" button to get a file browser.
  Select the item in the list box of the main window by a single click.
- Go to the \"Extract to disk:\" line and choose the target address on disk.
  Either toggle in the address of the hard disk directory, or click on the
  \"/\" button to the right of the text field to get a file browser.
- Hit the Return key in the text field or double click on a name in the
  browser to initiate the extraction run.
  If a \"yes/no\" window pops up, consider well whether you are up to
  shooting your own foot right now.
  Enable the \"Overwrite hard disk files\" switch only if you are really
  sure that the files from ISO are better than the ones on hard disk.

-----------------------------------------------------------------------------

xorriso-tcltk is mainly a proof of concept for a frontend that operates
xorriso in dialog mode.

It demonstrates some of xorriso's multi-session features with ISO 9660
filesystems on optical media (CD, DVD, BD) or in disk files.

Dependencies:
  xorriso, Tcl language, Tk GUI toolkit, optionally Tcl/Tk package BWidget

Copyright (C) 2012 - 2026
Thomas Schmitt <scdbackup@gmx.net>, libburnia-project.org
Provided under BSD license: Use, modify, and distribute as you like."
  }
  if {$what == "End"} {
    return \
"The \"End\" button leads to the end of frontend and xorriso process."
  }
  if {$what == "version"} {
    return \
"The field between \"End\" button and ready/busy field displays the
version of the serving xorriso program.

xorriso is a program which copies file objects from POSIX compliant
filesystems into Rock Ridge enhanced ISO 9660 filesystems and allows
session-wise manipulation of such filesystems. It can load the management
information of existing ISO images and it writes the session results to
optical media or to filesystem objects.
Vice versa xorriso is able to restore file objects from ISO 9660 filesystems.

xorriso-tcltk-$own_version is mainly a proof of concept for a frontend
that operates xorriso in dialog mode.

It exercises several fundamental gestures of communication:
- connecting via two pipes
- sending commands
- receiving replies
- inquiring the xorriso message sieve
- using the xorriso parsing service

Note that any other language than Tcl/Tk could be used, if it only can
do i/o via standard input and standard output or via named pipes.
Further it has to perform integer arithmetics and string manipulations.
And, well, a graphical widget set would be nice."
  }
  if {$what == "Refresh disp"} {
    return \
"The \"Refresh disp\" button causes several text fields and list
boxes to update their display after manually transmitted commands may
have changed the state of drives or ISO model."
  }
  if {$what == "ready/busy"} {
    return \
"The ready/busy field indicates whether a xorriso command is being executed
and the frontend is still waiting for its reply messages."
  }
  if {$what == "Command:"} {
    return \
"The \"Command:\" field can be used to send commands to xorriso.
See the manual page of xorriso for its concepts and commands.

Normally the other GUI elements will emit xorriso commands for you.
This input field is presented only to make accessible those features
of xorriso which are not covered by the GUI. Use the \"Refresh disp\"
button to update the display after you have manually transmitted commands."
  }
  if {$what == "Script/Log"} {
    return \
"The \"Script/Log\" menu controls logging of xorriso commands and replies.

The \"Log command script\" switch controls whether the essential xorriso
commands of the GUI session shall be written to the end of a script file
on hard disk. Not written will be the commands by which the GUI inspects
the xorriso state, but only those which set up that state and those which
get sent via the \"Command:\" field.
Commands -osirrox and -extract will be logged only as comments.

The \"Log non-essential commands\" switch controls whether all commands
shall be logged if \"Log command script\" is enabled. Commands
-msg_op \"parse\" and -msg_op \"parse_bulk\" will be logged only as comments.

The item \"Set log script address\" pops up a file tree browser window
which asks for the target of appending to script. Address \"-\" means
standard error. Else it must not yet exist or be a writable data file.

The \"Log pipes\" switch controls whether all xorriso commands and replies
shall be logged to standard error or to the file that has been given
with program argument --pipe_log_file.
Caution: This log is verbose.

The item \"Set pipe log address\" pops up a file tree browser window
which asks for the target of pipe logging . Address \"-\" means
standard error. Else it must not yet exist or be a writable data file.

The item \"Execute command script\" executes the commands in a script
file that should stem from \"Log command script\".
At least it must begin by this line:
# xorriso-tcltk command log script
Be aware that xorriso will slavishly execute those commands. Better check
in advance whether the content of the script file is what you expect.
See man xorriso for the meaning of the commands.

The \"Allow extract to disk\" switch controls whether commands like -extract
are allowed in command scripts. If disabled, then command -osirrox is used
to temporarily block those commands (unless the script unblocks itself, which
would be nasty behavior).

The item \"Permanently ban extraction\" disables -extract irrevocably for
scripts and GUI alike."
  }
  if {$what == "message box"} {
    return \
"The message box displays commands sent to xorriso and messages received
from xorriso.

Many commands which are emitted by the GUI will hide themselves and their
replies from this display. All event messages with severity WARNING or
higher will show up, nevertheless."
  }
  if {$what == "Recent problem:"} {
    return \
"The \"Recent problem:\" field shows the most severe event message that occurred
during the execution of the most recent command. It also displays the most
recent problem message from the frontend program itself.

Several commands emitted by the GUI will not clear this display. But any
manually transmitted command and the major GUI gestures will do.
"
  }
  if {$what == "Worst problem:"} {
    return \
"The \"Worst problem:\" field shows the most severe event message that occurred
since last time the \"Clear\" button was hit. It will not clear automatically."
  }
  if {$what == "Clear"} {
    return \
"The \"Clear\" button removes the message from the \"Worst problem:\" field."
  }
  if {$what == "Scan for drives"} {
    return \
"The \"Scan for drives\" button executes command -devices and puts the list
of found optical drives into the box beside the button.

Scanning should be done before any ISO image manipulations because xorriso
has to give up its acquired drives in order to perform the scan run.

To become visible and to be usable, the drives have to offer rw-permission
to the user of this program. If drives do not show up, then consider to
become superuser and to execute
  xorriso -devices
Then apply
  chmod a+rw 
to the listed device files. (Consider to use finer means of permission
granting for a permanent solution.)"
  }
  if {$what == "Pick input drive"} {
    return \
"The \"Pick input drive\" button executes command -indev and obtains some
information about the medium status. This info is displayed in the 
\"Input drive/image\" line. 
Further it causes the display of the ISO image model to be updated.

The medium in the input drive must be blank or contain a valid ISO 9660
filesystem.
Choosing an input drive causes a root directory to be created in the ISO
model of xorriso. If there is a valid ISO filesystem in the input drive
then its directory tree gets loaded underneath that model root directory.

The input drive may also be a data file on hard disk if that file contains
an ISO 9660 filesystem image. See the \"Input drive/image\" button."
  }
  if {$what == "Pick output drive"} {
    return \
"The \"Pick output drive\" button executes command -outdev and obtains some
information about the medium status. This info is displayed in the 
\"Output drive/image\" line.

The output drive may be empty or loaded with a medium, that may be blank,
appendable or closed.
It is usable for writing only if there is a medium inserted which is either
blank or appendable. Button \"Blank\" can bring appendable or closed media
into blank state.

The output drive may also be a data file on hard disk. See field
\"Output drive/image\"."
It is considered appendable if it contains an ISO 9660 filesystem image.
It is considered blank if it is empty or marked as blank by button \"Blank\".
It is considered closed if it contains other data."
  }
  if {$what == "Pick drive for both roles"} {
    return \
"The \"Pick drive for both roles\" button executes command -dev and obtains some
information about the medium status. This info is displayed in the
\"Input drive/image\" line and in the \"Output drive/image\" line.
Further it causes the display of the ISO image model to be updated.

The medium in the drive must be blank or contain a valid ISO 9660 filesystem.
Else the drive will only be acquired as output drive.

This drive configuration is the most usual one with xorriso. It loads
an eventual ISO image, allows to manipulate it by insertion, deletion,
and renaming. When this is done, the changes get written to the drive
via button \"Write ISO session\".

The drive may also be a data file on hard disk. See the fields beside
the \"Input drive/image\" and \"Output drive/image\" buttons.
A file is considered appendable if it contains an ISO 9660 filesystem image.
It is considered blank if it is empty or marked as blank by button \"Blank\".
It is considered closed if it contains other data."
  }
  if {$what == "Give up drives"} {
    return \
"The \"Give up drives\" button executes commands -indev \"\" -outdev \"\"
and clears both \"... drive/image\" lines, as well as the ISO model."
  }
  if {$what == "Calm drives"} {
    return \
"The \"Calm drives\" button executes command -calm_drives which tells the
acquired optical drives to stop spinning until the next drive activity
gets triggered."
  }
  if {$what == "Rollback"} {
    return \
"The \"Rollback\" button executes command -rollback which drops all pending
changes of the ISO model and reloads it from the input drive, if one is
acquired."
  }
  if {$what == "drivelist"} {
    return \
"The box beside the \"Scan for drives\" button shows the optical drives
which were found by the most recent scan run.

A double-click on a drive item has the same effect as button
\"Pick drive for both roles\".
"
  }
  if {$what == "Input drive/image"} {
    return \
"The field beside the \"Input drive/image\" button displays the address of
the input drive. You may edit this field.
Clicking the button or pressing the Return key causes the execution of
command -indev with the field content as drive address.

Use this to load the model from an ISO image data file on hard disk.
It is of course permissible that input image and output image are the
same file.
"
  }
  if {$what == "input drive info"} {
    return \
"The text beside the \"Input drive/image\" field displays the medium
status of the input drive. It tells about the writability, the medium type,
the number of ISO sessions, and the amount of readable data."
  }
  if {$what == "Eject (indev)"} {
    return \
"The \"Eject\" button beside the \"Input drive/image\" button executes
command -eject \"in\"."
  }
  if {$what == "Output drive/image"} {
    return \
"The field beside the \"Output drive/image\" button displays the address
of the output drive. You may edit this field.
Clicking the button or pressing the Return key causes the execution
of command -outdev with the field content as drive address.

Use this to direct writing to an ISO image data file on hard disk.
It is of course permissible that input image and output image are the
same file.
"
  }
  if {$what == "output drive info"} {
    return \
"The text beside the \"Output drive/image\" field displays the medium
status of the output drive. It tells about the writability, the medium type,
the number of ISO sessions, and the amount of free space."
  }
  if {$what == "Eject (outdev)"} {
    return \
"The \"Eject\" button beside the \"Output drive/image\" button executes
command -eject \"out\"."
  }
  if {$what == "Blank"} {
    return \
"The \"Blank\" button executes command -blank \"as_needed\" on the output drive
in order to make a re-usable medium or an ISO image data file writable from
scratch.

Genuine blanking applies only to CD-RW and DVD-RW.
But xorriso emulates ISO 9660 multi-session on DVD+RW, DVD-RAM,
formatted DVD-RW, BD-RE, as well as in ISO image data files on hard disk.
On those media and pseudo media, blanking will be performed by a small
write operation which invalidates their existing ISO filesystem.

One-time writable media CD-R, DVD-R, DVD+R, and BD-R cannot be blanked."
  }
  if {$what == "Format"} {
    return \
"The \"Format\" button executes -format \"as_needed\".

This only applies to real optical drives and is of interest only with DVD-RW
or BD-R media, which both can be used formatted and unformatted. Other media
types which mandatorily need formatting will be formatted by the write
commands.

Formatted DVD-RW media have the advantage of being overwritable and thus
being quickly blankable while maintaining the capability for multi-session.

Formatted BD-R can perform Defect Management, which is of questionable value."
  }
  if {$what == "Write ISO session"} {
    return \
"The \"Write ISO session\" executes command -commit, which writes a session
with all pending changes to the output drive.

The output drive must be either blank or it must be the same as the input
drive.

Writing the session is the last step in the course of creating a new ISO
filesystem or an add-on session that expands or changes the ISO filesystem
on the medium of the output drive.
So first choose a drive, then insert files from hard disk or do other
manipulations, and then click \"Write ISO session\" to let xorriso
write the data to medium or ISO image file.
"
  }
  if {$what == "Close"} {
    return \
"The \"Close\" switch controls whether command -close \"on\" is emitted with
\"Write ISO session\" or whether -as cdrecord option -multi is omitted with
\"Burn image file:\".

Closed optical media cannot be written any more unless they get blanked,
which is not possible with CD-R, DVD-R, DVD+R, and BD-R.
"
  }
  if {$what == "TAO"} {
    return \
"The \"TAO\" switch controls whether an incremental MMC write type shall be
enforced with write commands. See xorriso command -write_type.

Normally xorriso will decide by medium status and job parameters which
MMC write type to choose. Some drives at the edge of failure might work
with the one write type while already failing with the other."
  }
  if {$what == "Defect Mgt"} {
    return \
"The \"Defect Mgt\" switch controls whether slow and error-prone drive internal
check-reading shall be enabled when writing to formatted BD-R or BD-RE.
See xorriso command -stream_recording."
  }
  if {$what == "Burn image file:"} {
    return \
"The \"Burn image file:\" button executes command -as \"cdrecord\" to
burn a data file from hard disk onto the output drive.
The address of the disk file is taken from the neighboring text field.

If you do not plan to append further data to the medium, then consider
to enable the \"Close\" switch.

No input drive may be acquired. (Delete all characters from the field
\"Input drive/image\" and hit Return to give up the input drive.)

The medium in the drive must be blank.
(It is well possible to burn image files to appendable media. But the
image needs to be prepared for the address offset. Who can do that can
as well use one of the command line tools for burning the result. E.g.
  xorriso -as cdrecord -v dev=/dev/sr0 -multi stream_recording=32s image.iso
)"
  }
  if {$what == "Extract to disk:"} {
    return \
"The \"Extract to disk:\" button executes command -extract with the whole
tree of the current ISO directory or with the selected items of the box
underneath \"ISO directory:\".

This copies the selected files or directory trees from the input drive
to the address on hard disk which is given by the text field right of
the button."
  }
  if {$what == "Browse tree"} {
    return "[tell_file_browser_help 0]"
  }
  if {$what == "Close (browse tree)"} {
    return \
"The \"Close\" button in the file browser closes the browser window without
performing other actions."
  }
  if {$what == "Up (browse tree)"} {
    return \
"The \"Up\" button in the file browser brings you to the parent directory
of the currently selected file tree item.

The parent directory will be opened and become the selected item.
All opened directory trees underneath the parent will be closed."
  }
  if {$what == "Down (browse tree)"} {
    return \
"The \"Down\" button in the file browser opens the directory underneath 
the currently selected file tree item.

It has the same effect as clicking the \"+\" node of the selected item."
  }
  if {$what == "Accept (browse tree)"} {
    return \
"The \"Accept\" button in the file browser brings the single selected item
from the file browser tree into effect with the associated text field.
I.e. it hits the Return key of the field.

It works as if the item had been double clicked."
  }
  if {$what == "Edit (browse tree)"} {
    return \
"The \"Edit\" button in the file browser brings the single selected item
from the file browser tree into the associated text field.

It does not hit the Return key of the field, but gives you the opportunity
to edit the file address."
  }
  if {$what == "Browse disk (extract)"} {
    return \
"The \"/\" button in the \"Extract to disk:\" line pops up a file tree
browser to select a target address in the hard disk filesystem.

[tell_file_browser_help 1]"
  }
  if {$what == "Browse disk (burn image)"} {
    return \
"The \"/\" button beside the \"Burn image file\" field pops up a file
tree browser to select a source address in the hard disk filesystem.

[tell_file_browser_help 1]"
  }
  if {$what == "Browse disk (insert)"} {
    return \
"The \"/\" button beside the \"Insert from disk\" field pops up a file
tree browser to select a source address in the hard disk filesystem.

[tell_file_browser_help 1]"
  }
  if {$what == "Browse disk (indev)"} {
    return \
"The \"/\" button in the \"Input drive/image\" line pops up a file tree
browser to select a source address in the hard disk filesystem.

[tell_file_browser_help 1]"
  }
  if {$what == "Browse disk (outdev)"} {
    return \
"The \"/\" button in the \"Output drive/image\" line pops up a file tree
browser to select a source address in the hard disk filesystem.

[tell_file_browser_help 1]"
  }
  if {$what == "Browse ISO (isodir)"} {
    return \
"The \"/\" button in the \"ISO directory\" line pops up a file tree
browser to select the current directory in the ISO filesystem model.

[tell_file_browser_help 1]"
  }
  if {$what == "Browse ISO (move target)"} {
    return \
"The \"/\" button in the \"Selection:\" line pops up a file tree
browser to select the current directory in the ISO filesystem model.

[tell_file_browser_help 1]"
  }
  if {$what == "Browse disk (dummy)"} {
    return \
"Normally this button would start a file browser to select a file or
directory on hard disk.

But the browser cannot be displayed because Tcl/Tk package \"BWidget\"
is not loaded."
  }
  if {$what == "Browse ISO (dummy)"} {
    return \
"Normally this button would start a file browser to select a file or
directory in the ISO model.

But the browser cannot be displayed because Tcl/Tk package \"BWidget\"
is not loaded."
  }
  if {$what == "Underneath (extract)"} {
    return \
"The \"Underneath\" switch controls the effective hard disk target address
of an item if the address in the \"Extract to disk:\" field points to a
directory.

If \"Underneath\" is enabled, then the file object from the ISO filesystem
will be copied to its name underneath the hard disk directory.
If \"Underneath\" is disabled then an ISO directory tree item will be merged
with the disk directory tree at the given address.

Example:
Selected are \"/iso_dir\" and \"/iso_file\".
Address for hard disk is \"/tmp/from_iso\". Switch \"Selected\" is enabled.
\"Underneath\" enabled causes commands:
  -extract /iso_dir /tmp/from_iso/iso_dir
  -extract /iso_file /tmp/from_iso/iso_file
\"Underneath\" disabled:
  -extract /iso_dir /tmp/from_iso
  -extract /iso_file /tmp/from_iso
The last command will fail because /tmp/from_iso already exists as directory."
  }
  if {$what == "Selected (extract)"} {
    return \
"The \"Selected\" switch controls whether the whole current ISO directory,
or only the selected items shall be copied to hard disk.
"
  }
  if {$what == "Overwriting:"} {
    return \
"The \"Overwriting\" menu bundles several switches which control whether
existing files or directories may be overwritten.

The frontend program will only detect the most obvious name collisions,
but xorriso will reliably refuse to overwrite files if this is banned.

----------------------------------------------------------------------------

The \"Overwrite ISO files\" switch controls whether existing files may be
overwritten in the ISO image. See xorriso command -overwrite \"nondir\".

----------------------------------------------------------------------------

The \"Overwrite ISO dirs\" switch controls whether it is allowed to replace
an ISO directory by a another file. See xorriso command -overwrite \"on\".

----------------------------------------------------------------------------

The \"Overwrite hard disk files\" switch controls whether existing files may be
overwritten by extraction to hard disk. See xorriso command -overwrite \"on\".

This is DANGEROUS, of course, but comes in handy with restoring of backups.

----------------------------------------------------------------------------

The \"Enforce disk dir write access\" switch enables the -osirrox options
\"auto_chmod_on\" and \"sort_lba_on\" which influence file extraction.

\"auto_chmod_on\" allows xorriso to give itself temporariy w-permission to
all disk directories which are owned by the xorriso user.

This is DANGEROUS, of course, but comes in handy with restoring of backups.

Option \"sort_lba_on\" reduces head-moves of optical drives and thus can
speed up extraction substantially. It is bound to \"auto_chmod_on\" because
else it might get into trouble when restoring ISO directories which offer
no w-permission."
  }
  if {$what == "Permissions:"} {
    return \
"The \"Permissions\" menu allows to choose a global policy to adjust
the access permissions of the files when an ISO session gets written.

The default policy \"as is\" leaves the permissions as they are.
Usually they have been imported from hard disk or from a loaded ISO image.
xorriso commands -chmod , -chmod_r, and -find ... -exec chmod --
may be used to perform permission manipulations.

Policy \"readable\" adds read permission to all kinds of files and
search permission to all directories.

Policy \"readonly\" sets the permissions of all kinds of files to read-only.
Directories get added search permission.

Policy \"mkisofs -r\" does what option -r of program mkisofs does:
User id and group id become 0, all r-permissions get granted, all w denied.
If there is any x-permission, then all three x get granted. s- and t-bits
get removed.
"
  }
  if {$what == "Refresh avail:"} {
    return \
"The \"Refresh avail:\" button triggers command -tell_media_space. It makes
a time consuming exact prediction of the free space on the medium in the
output drive. For this purpose, the size of an ISO session with the pending
changes is computed.

With image files rather than real optical drives, the free space of
the hosting filesystem is displayed."
  }
  if {$what == "Insert from disk:"} {
    return \
"The \"Insert from disk:\" button executes command -map with the disk file
address that is given by the text field right to the button.

This inserts files or directory trees into the ISO image model and
schedules them for being copied with the next \"Write ISO session\" run.

The switches \"Underneath\" and \"Selected\" control what ISO address
the inserted files shall have. You may use buttons \"Delete\" and
\"Move to:\" for further adjustments.
"
  }
  if {$what == "Underneath (insert)"} {
    return \
"The \"Underneath\" switch controls the effective ISO target address
if the address in the \"Insert from disk:\" field points to a hard disk
directory.

If \"Underneath\" is enabled, a directory from disk will not be unpacked
to its single files but be put underneath the target address by its own
leaf name."

If \"Underneath\" is disabled then the directory itself will not show up in
the ISO image but only its files and sub directories will do."
  }
  if {$what == "Selected (insert)"} {
    return \
"If the switch \"Selected\" is enabled, then the given disk file or tree will
be inserted at or underneath the only selected item in the box underneath
\"ISO directory:\"."
  }
  if {$what == "ISO directory:"} {
    return \
"The current ISO directory shall be used to navigate in the ISO image model
of xorriso. By default it is the target of file insertions and the source
of file extractions.

The text field in the \"ISO directory:\" line displays the current ISO
directory and can be used to toggle its path directly.
Hitting the Return key causes the current directory to change and the
display in the box underneath to be refreshed.

It is possible to choose the ISO directory by double-clicking an item
in the box underneath the \"ISO directory:\" line.
"
  }
  if {$what == "Up"} {
    return \
"The \"Up\" buttons move the current ISO directory one directory level up."
  }
  if {$what == "Verify"} {
    return \
"The \"Verify\" button executes -md5_check_r \"SORRY\" with the current ISO
directory.

This reads the content of all data files which are underneath the current ISO
directory and which have MD5 checksums in the ISO image. 
ISO images bear MD5 checksums for each data file if they were produced
by xorriso with -md5 \"on\" or -for_backup. This frontend enables
this feature on startup."
  }
  if {$what == "isolist"} {
    return \
"The list box underneath the \"ISO directory:\" line displays the files in
the current ISO directory. One or more item can be selected and play a
role with extraction or insertion of files.

Most of the buttons underneath the box operate on the selected items
unconditionally."
  }
  if {$what == "Selection:"} {
    return \
"The ISO selection consists of the items which are selected in the list box
above the \"Selection:\" line.

If the respective \"Selected\" switches are enabled, then the ISO selection
is source of file extraction and target of file insertion.

In any case it is the old name of the \"Move to:\" button, the victim
of the \"Delete\" button, and the subject of the \"Verify\" button."
  }
  if {$what == "Verify (selection)"} {
    return \
"The \"Verify\" button in the \"Selection:\" line executes command
-md5_check_r \"SORRY\" with each of the selected items.

This reads the content of all data files which are selected or underneath
selected directories and which have MD5 checksums in the ISO image. 
ISO images bear MD5 checksums for each data file if they were produced
by xorriso with -md5 \"on\" or -for_backup. This frontend enables
this feature on startup."
  }
  if {$what == "Delete"} {
    return \
"The \"Delete\" button executes command -rm_r with each of the selected items.

This removes the affected files and directory trees from the ISO model.
They will not show up in the directory tree of the next session that
is written via \"Write ISO session\". Nevertheless they will stay present
in earlier sessions beginning from the session where they were inserted."
  }
  if {$what == "Move to:"} {
    if {$use_command_move == 0} {
      return \
"The \"Move to:\" button uses command -mv to move each of the selected
items to the address that is given by the text field right to the button.

If this address points to an existing ISO directory, then the items will
be moved underneath that directory and keep their leaf names.
Else there may be only one selected item which will be renamed to the
given address."
    } else {
      return \
"The \"Move to:\" button uses command -move to rename each of the selected
items to the address that is given by the text field right to the button."
    }
  }
  if {$what == "Make dir"} {
    return \
"The \"Make dir\" button executes command -mkdir with the address in the
text field to its left (the same as used by \"Move to:\").

Useful to create a target directory before moving the selection."
  }
  if {$what == "rename and mkdir target"} { 
    return \
"The text field between the \"Move to:\" button and the \"Make dir\" button
serves both buttons by providing the target address for renaming
or directory creation, respectively.

If you hit the Return key in this field, it will trigger \"Mode to:\"."
  }
  if {$what == "yes to all"} {
    return \
"The \"yes to all\" button appears in the yes/no window if a GUI action is
about to overwrite a file object and more such overwrite situations are
to be expected.

If the button is clicked, then all further yes/no questions of that GUI
action will be answered automatically with yes.

[about_help_for_yesno]"
  }
  if {$what == "no to all"} {
    return \
"The \"no to all\" button appears in the yes/no window if a GUI action is
about to overwrite a file object and more such overwrite situations are
to be expected.

If the button is clicked, then all further yes/no questions of that GUI
action will be answered automatically with no.

[about_help_for_yesno]"
  }
  if {$what == "Continue"} {
    return \
"The \"Continue\" button appears in the notification windows which tell
about a failed or rejected GUI action.

---------------------------------------------------------------------------

It is impossible to trigger any further GUI action while the notification
window is displayed. You either have to click the \"Continue\" button
or hit the Return key.

You cannot even close this help window before you did that."
  }
  if {$what == "yes/no"} {
    return \
"The \"yes\" and \"no\" buttons appear in the confirmation window which tells
about a potentially dangerous GUI action and demands a user decision whether
to really perform this action.

[about_help_for_yesno]"
  }

  return "--- No help text found for topic '$what'"
}


# Tell the general help text of the file browser.
#
proc tell_file_browser_help {with_separator} {
  global have_bwidget

  set text ""
  if {$with_separator == 1} {
    set text \
"-------------------------------------------------------------------------\n\n"
  }
  if {$have_bwidget == 1} { 

    set text \
"${text}The file tree browser presents to you a directory tree and
lets you bring into effect one of the file addresses in that tree.\n"

  } else {

    set text \
"${text}Normally the file tree browser would present to you a directory tree
and let you bring into effect one of the file addresses in that tree.

But the tree view cannot be displayed because Tcl/Tk package \"BWidget\"
is not loaded.

-------------------------------------------------------------------------\n"

  }

  set text "${text}
The bottom line of the browser window tells the associated text field
in the GUI. E.g. \"ISO directory:\".
Left of this label is a copy of that associated text field. You may edit
its content and bring it into effect by hitting the Return key.\n"

  if {$have_bwidget == 1} { 

    set text "${text}
In the tree display click on the \"+\" or \"-\" nodes to open or
close directories, respectively.

Double click on an item to bring it into effect with the associated
text field. I.e. double clicking also hits the Return key in that field.\n"

  }

  set text "${text}
The \"Accept\" button does the same with the selected item.\n"

  if {$have_bwidget == 1} { 

    set text "${text}
The \"Edit\" button brings the selected item into the text field
without hitting the Return key. So you may edit the name before hitting
Return yourself.

The \"Up\" button brings you to the parent directory of the selected item.

The \"Down\" button works like clicking the \"+\" node of the selected item.\n"

  }

  set text "${text}
The \"Help\" button displays this help text window.

The \"Close\" button closes the browser window.\n"
}


# Tell about pecliarity of help window triggered by yes/no window
proc about_help_for_yesno {} {
  return \
"---------------------------------------------------------------------------

It is impossible to trigger any further GUI action while the confirmation
window is displayed. You have to click one of the buttons in that window.

You cannot even close this help window before you clicked one of the buttons."
}


# ------- Misc helper procedures -------


# Equip a text with quotation marks so that xorriso will consider it as
# a single word.
#
proc make_text_shellsafe {text} {
  set result "'"
  set rest $text
  while {[string length $rest]} {
    set idx [string first "'" $rest]
    if {$idx == -1} {
      set result "$result$rest"
  break
    } else {
      if {$idx > 0} {
        set result "$result[string range $rest 0 [expr $idx - 1]]"
      }
      set result "$result'\"'\"'"
      if {$idx == [expr [string length $rest] - 1]} {
  break
      }
      set rest [string range $rest [expr $idx + 1] end]
    }
  }
  set result "$result'"
}


# Count the number of newline characters in text.
#
proc count_newlines {text} {
  set rest $text
  set count 0
  while {[string length $rest]} {
    set idx [string first "\n" $rest]
    if {$idx == -1} {
  break
    } else {
      set count [expr $count + 1]
      if {$idx == [expr [string length $rest] - 1]} {
  break
      }
      set rest [string range $rest [expr $idx + 1] end]
    }
  }
  return $count
}


# Append name to dir so that the result is a path to name under dir.
#
proc combine_dir_and_name {dir name} {
  set has_slash 0
  if {$name == ""} {
    return $dir
  }
  if {[string range $name 0 0] == "/"} {
    incr has_slash
  }
  if {[string last "/" $dir] == [expr [string length $dir] - 1] &&
      $dir != ""} {
    incr has_slash 1
  }
  if {$has_slash == 2} {
    return "$dir[string range $name" 1 end]"
  }
  if {$has_slash == 1} {
    return "$dir$name"
  }
  return "$dir/$name"
}


# Force the content of variable isodir_adr to be an absolute address
#
proc normalize_isodir_adr {} {
  global isodir_adr

  if {$isodir_adr == ""} {
    set isodir_adr "/"
  }
  if {[string range $isodir_adr 0 0] != "/"} {
    set isodir_adr "/$isodir_adr"
  }
}


# Inspect path whether one of its components is in isodir_adr
#
proc path_touches_isodir {path} {
  global isodir_adr

  normalize_isodir_adr
  set cmp_start 0
  if {$isodir_adr == "/"} {
    set cmp_start 1
  }
  if {[string range $path 0 0] != "/"} {
    if {[string first "/" $path] == -1} {
      return $path
    } else {
      return [file dirname $path]
    }
  }
  set l [expr {[string length $isodir_adr] - $cmp_start}]
  if {[string length $path] < [expr {$l + 2}]} {
    return ""
  }
  if {$l > 0} { 
    if {[string range $path $cmp_start [expr {$l - 1}]] != \
        [string range $isodir_adr $cmp_start end]} {
      return ""
    }
  }
  if {[string range $path $l $l] != "/"} {
    return ""
  }
  set subpath [string range $path [expr {$l + 1}] end]
  set slash [string first "/" $subpath]
  if {$slash == -1} {
    return $subpath
  }
  if {$slash == 0} {
    return ""
  }
  return [string range $subpath 0 [expr {$slash - 1}]]
}


# Compare two severity names by help of the severity list that was obtained
# from xorriso via proc inquire_severity_list.
#
proc compare_sev {sev1 sev2} {
  global xorriso_severity_list

  set idx1 [lsearch -exact $xorriso_severity_list $sev1]
  set idx2 [lsearch -exact $xorriso_severity_list $sev2]
  if {$idx1 < $idx2} {return -1}
  if {$idx1 > $idx2} {return 1}
  return 0
}


# Write a text to the pipe log
#
proc debug_log_puts {text} {
  global debug_logging debug_log_conn

  if {$debug_logging == 1} {
    puts $debug_log_conn $text
    flush $debug_log_conn
  }
}


# End program and return the given exit value.
#
proc central_exit {value} {
  exit $value
}


# Start a xorriso process which will in return launch another frontend
# process. This is necessary until i learned how to create a pair of pipes
# and to fork in Tcl.
#
proc start_xorriso {} {
  global argv0 argv

  set self ""
  if {[string first "/" $argv0] != -1} {
    set self $argv0
  }
  if {$self == ""} {
    set self "/usr/bin/xorriso-tcltk"
    if {[file executable $self] == 0} {set self ""}
  }
  if {$self == ""} {
    set self "/usr/local/bin/xorriso-tcltk"
    if {[file executable $self] == 0} {set self ""}
  }
  if {$self == ""} {
    catch {
      set conn [open "|which xorriso-tcltk" r]
      set self [gets $conn]
      close $conn
    }
  }
  if {$self == ""} {
    catch {
      set conn [open "|sh -c \"type -p xorriso-tcltk\"" r]
      set self [gets $conn]
      close $conn
    }
  }
  if {$self == ""} {
    puts stderr "$argv0 :\n  Cannot locate address of script xorriso-tcltk in filesystem.\n"
    puts stderr "You will have to use --stdio or --named_pipes."
    puts stderr "See $argv0 --help\n"
    central_exit 1
  }

  # eval is used to split $argv into single words
  eval exec xorriso -launch_frontend "\"$self\"" --silent_start --stdio $argv -- 2>@stderr

  central_exit 0
}


# Print a startup message to stderr if not the first argument is --silent_start
#
proc yell_xorriso_tcltk {} {
  global argv own_version

  if {[llength $argv] > 0} {
    if {[lindex $argv 0] == "--silent_start"} {return ""}
  }
  puts stderr "xorriso-tcltk $own_version : Proof of concept for GUI frontends of xorriso\n"
}


# Log a command (if enabled)
#
proc log_command {cmd essential} {
  global cmd_log_conn cmd_logging_mode cmd_logging_all recent_cd_path

  if {$cmd_logging_mode < 1} {return ""}
  if {$essential <= 0} {
    if {$cmd_logging_all <= 0} {return ""}
  } else {
    # Leave logging to non-essential call which will come soon after
    if {$cmd_logging_all > 0} {return ""}
  }

  if {[string first "-cd " $cmd] == 0} {
    set path [string range $cmd 4 end]
    if {$path == $recent_cd_path && $cmd_logging_all <= 0} {return ""}
    set recent_cd_path $path
  }

  if {$cmd_log_conn == ""} {
    effectuate_command_logging 0
    if {$cmd_logging_mode < 1} {return ""}
  }
  set prefix ""
  if {$cmd_logging_mode == 1} {
    if {[string first "-osirrox" $cmd] != -1 || \
        [string first "-extract" $cmd] != -1} {
      set prefix "# "
    }
  }
  if {[string first "-msg_op parse" $cmd] != -1} {
      set prefix "# "
  }
  puts $cmd_log_conn $prefix$cmd
  flush $cmd_log_conn
}


# Start command logging
# Called by setup_by_args and by the "Script/Log" menu.
# (target == "." and mode == -1 preserve the current state.)
#
proc start_command_logging {target mode} {
  global cmd_log_conn cmd_logging_mode msglist_running cmd_log_target

  set is_stderr 0
  if {$cmd_log_target == "" || $cmd_log_target == "-" || \
      $cmd_log_conn == "stderr"} {set is_stderr 1}
  set errmsg ""
  if {$target != "." && $cmd_log_conn != "" && $target != $cmd_log_target && \
      $is_stderr == 0} {
    catch "close $cmd_log_conn"
    set cmd_log_conn ""
  }
  set ret 0
  if {$cmd_log_conn == "" || $is_stderr == 1} {
    if {$target == "-" || $target == "" || $target == "."} {
      set cmd_log_conn stderr
    } else {
      set ret [catch {set cmd_log_conn [open $target a]} errmsg]
    }
    if {$target != "."} {
      set cmd_log_target $target
    }
  }
  if {$ret == 0 && $mode >= 0} {
    set cmd_logging_mode $mode
  }
  if {$ret == 1} {
    set msg "xorriso-tcltk : SORRY : Failed to open command log script [make_text_shellsafe $target] :\n$errmsg"
    if {$msglist_running == 1} {
      xorriso_tcltk_errmsg $msg
    } else {
      puts stderr $msg
    }
    set cmd_logging_mode 0
    return 0
  }
  if {$mode > 0} {
    puts $cmd_log_conn "# xorriso-tcltk command log script"
    puts $cmd_log_conn  [xorriso_loggable_init_cmds]
    flush $cmd_log_conn
  }
  return 1
}


# Start communications pipe logging
# Called by setup_by_args and by the "Script/Log" menu.
# (target == "." and mode == -1 preserve the current state.)
#
proc start_debug_logging {target mode} {
  global debug_log_conn debug_log_file debug_logging msglist_running 

  set is_stderr 0
  if {$debug_log_file == "" || $debug_log_file == "-" || \
      $debug_log_conn == "stderr"} {set is_stderr 1}
  set errmsg ""
  if {$target != "." && $debug_log_conn != "" && \
      $target != $debug_log_file && $is_stderr == 0} {
    catch "close $debug_log_conn"
    set debug_log_conn ""
  }
  set ret 0
  if {$debug_log_conn == "" || $is_stderr == 1} {
    if {$target == "-" || $target == "" || $target == "."} {
      set debug_log_conn stderr
    } else {
      set ret [catch {set debug_log_conn [open $target a]} errmsg]
    }
    if {$target != "."} {
      set debug_log_file $target
    }
  }
  if {$ret == 0 && $mode >= 0} {
    set debug_logging $mode
  }
  if {$ret == 1} {
    set msg "xorriso-tcltk : SORRY : Failed to open pipe log [make_text_shellsafe $target] :\n$errmsg"
    if {$msglist_running == 1} {
      xorriso_tcltk_errmsg $msg
    } else {
      puts stderr $msg
    }
    return 0
  }
  return 1
}


proc execute_script {close_window} {
  global execute_script_conn execute_script_adr browse_disk_window_is_active
  global osirrox_allowed script_with_osirrox cmd_logging_mode cmd_log_target
  global highest_cmd_sev

  if {$close_window == 1 && $browse_disk_window_is_active == 1} {
    destroy_browse_disk .browse_disk_window
  }

  set n1 [file normalize $execute_script_adr]
  set n2 [file normalize $cmd_log_target]
  if {$n1 == $n2 && $cmd_logging_mode > 0} {
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : You first have to disable command script logging before using the log script"
    return ""
  }

  set errmsg ""
  set ret [catch {set execute_script_conn [open $execute_script_adr r]} errmsg]
  if {$ret != 0} {
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : Failed to open command script [make_text_shellsafe $execute_script_adr] :\n$errmsg"
    return ""
  }
  set line ""
  set ret [gets $execute_script_conn line]
  if {$ret < 0 || $line != "# xorriso-tcltk command log script"} {
    xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : Given file does not look like a xorriso command log script"
    close $execute_script_conn
    return ""
  }

  # >>> ??? Show script

  if {[window_yesno "Really perform the xorriso commands in file\n\n[make_text_shellsafe $execute_script_adr]\n\n?"] != 1} {
    close $execute_script_conn
    return ""
  }
  if {$script_with_osirrox != 1} {
    send_silent_cmd "-osirrox blocked"
  }
  while {1} {
    set ret [gets $execute_script_conn line]
    if {$ret < 0} {
  break
    }
    if {$line == "" || [string first "#" $line] == 0} {
  continue
    }
    reset_highest_cmd_sev
    send_loggable_cmd $line
    if {[compare_sev $highest_cmd_sev "FAILURE"] >= 0} {
      xorriso_tcltk_errmsg "xorriso-tcltk : SORRY : Encountered problem event of severity '$highest_cmd_sev'.\nScript execution aborted."
  break
    }
  }
  close $execute_script_conn
  if {$script_with_osirrox != 1} {
    send_silent_cmd "-osirrox unblock"
  }
}


# Convert newline into \n
#
proc escape_newline {text backslash_too} {
  if {$backslash_too == 0} {
    return [string map [list "\n" "\\n"] $text]
  }
  return [string map [list "\n" "\\n" "\\" "\\\\"] $text]
}


# -------- start living


proc setup_by_args {argv0 argv} {
  global cmd_pipe_adr reply_pipe_adr main_window_geometry click_to_focus
  global have_bwidget cmd_conn reply_conn geometry stdout stdin
  global osirrox_allowed cmd_logging_all use_command_move

  # wish normally eats the -geometry option and puts the result into $geometry
  catch {set main_window_geometry $geometry}

  set connection_defined 0
  set pipe_logging 0
  set script_logging 0

  set loop_limit [llength $argv]
  for {set i 0} {$i < $loop_limit} {incr i} {
    set ok "0"
    set opt [lrange $argv $i $i]
    if {$opt == "--help"} {
      set ok "1"
      print_usage $argv0
      central_exit 0
    }
    if {$opt == "--silent_start"} {
      set ok "1"
    }
    if {$opt == "--stdio"} {
      set ok "1"
      set connection_defined 1
    }
    if {$opt == "--named_pipes"} {
      set ok "1"
      incr i
      set cmd_pipe_adr [lrange $argv $i $i]
      incr i
      set reply_pipe_adr [lrange $argv $i $i]
      if {$cmd_pipe_adr != "" && $reply_pipe_adr != "" &&
          $cmd_pipe_adr != "-" && $reply_pipe_adr != "-"} {
        init_frontend_named_pipes $cmd_pipe_adr $reply_pipe_adr
      }
      set connection_defined 1
    }
    if {$opt == "--geometry" || $opt == "-geometry"} {
      set ok "1"
      # Just in case -geometry does not get eaten by wish
      incr i
      set main_window_geometry [lrange $argv $i $i]
      set give_geometry [lrange $argv $i $i]
    }
    if {$opt == "--click_to_focus"} {
      set ok "1"
      set click_to_focus "1"
    }
    if {$opt == "--auto_focus"} {
      set ok "1"
      set click_to_focus "0"
    }
    if {$opt == "--pipe_log_file"} {
      set ok "1"
      incr i
      set pipe_log_name [lrange $argv $i $i]
      # postpone actual log start until start_xorriso has been passed
      set pipe_logging 1
    }
    if {$opt == "--script_log_file"} {
      set ok "1"
      incr i
      set script_log_name [lrange $argv $i $i]
      # postpone actual log start until start_xorriso has been passed
      set script_logging 1
    }
    if {$opt == "--script_log_all_commands"} {
      set ok "1"
      set cmd_logging_all 1
    }
    if {$opt == "--no_extract"} {
      set ok "1"
      set osirrox_allowed 0
    }
    if {$opt == "--no_bwidget"} {
      set ok "1"
      set have_bwidget "-1"
    }
    if {$opt == "--use_command_move"} {
      set ok "1"
      set use_command_move 1
    }
    if {$opt == "--use_command_mv"} {
      set ok "1"
      set use_command_move 0
    }
    if {$ok == 0} {
      puts stderr "$argv0 : Unknown option '$opt'"
      print_usage $argv0
      central_exit 1
    }
  }

  if {$connection_defined == 0} {
    start_xorriso
  }

  if {$cmd_pipe_adr == "" || $reply_pipe_adr == "" ||
      $cmd_pipe_adr == "-" || $reply_pipe_adr == "-"} {
    set cmd_conn stdout
    set reply_conn stdin
  }
  if {$pipe_logging == 1} {
    set ret [start_debug_logging $pipe_log_name 1]
    if {$ret <= 0} {
      puts stderr \
           "$argv0 : Cannot open --pipe_log_file '$pipe_log_name' for writing"
      central_exit 2
    }
  }
  if {$script_logging == 1} {
    set ret [start_command_logging $script_log_name 1]
    if {$ret <= 0} {
      puts stderr \
        "$argv0 : Cannot open --script_log_file '$script_log_name' for writing"
      central_exit 2
    }
  }

  if {$main_window_geometry != ""} {
    wm geometry . $main_window_geometry
  }
}

yell_xorriso_tcltk

setup_by_args $argv0 $argv
check_xorriso_version
setup_xorriso

init_gui

display_busy 0
refresh_state

