hfsutils/xhfs.tcl

1927 lines
43 KiB
Tcl

#
# hfsutils - tools for reading and writing Macintosh HFS volumes
# Copyright (C) 1996-1998 Robert Leslie
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# $Id: xhfs.tcl,v 1.8 1998/04/11 08:27:01 rob Exp $
#
set tk_strictMotif 1
set gNormalFont {-adobe-helvetica-bold-r-normal-*-*-120-*-*-p-*-iso8859-1}
set gThinFont {-adobe-helvetica-medium-r-normal-*-*-120-*-*-p-*-iso8859-1}
set gSmallFont {-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1}
set gTypeFont {-*-lucidatypewriter-medium-r-normal-*-*-120-*-*-m-*-iso8859-1}
set gTitleFont {-*-lucida-bold-i-normal-*-*-250-*-*-p-*-iso8859-1}
set gDefaultDevice /dev/fd0
set gLowFormatCmd [list fdformat $gDefaultDevice]
set gMountSide .l
set gFlushInterval 30000
set gDfCmd [list df]
option add *Font $gNormalFont
###############################################################################
# Set architecture defaults
if {[catch {exec uname} gUname] == 0} {
switch $gUname {
SunOS {
if {[catch {exec uname -r} gUrelease] == 0} {
switch -glob $gUrelease {
4.* {
set gLowFormatCmd [list fdformat -f /dev/rfd0]
}
5.* {
set gDefaultDevice /vol/dev/aliases/floppy0
set gLowFormatCmd [list fdformat -f $gDefaultDevice]
set gDfCmd [list df -k]
}
}
}
}
FreeBSD {
set gDefaultDevice /dev/rfd0
set gLowFormatCmd [list fdformat $gDefaultDevice]
}
}
}
###############################################################################
set gUnique 0
proc unique {} {
global gUnique
return [incr gUnique]
}
proc window {title iconname} {
set w ".w[unique]"
toplevel $w -cursor left_ptr -relief raised -bd 1
wm title $w $title
wm iconname $w $iconname
wm transient $w .
wm withdraw $w
return $w
}
proc showwindow {w} {
update idletasks
set x [expr [winfo x .] + [winfo width .]/2 - [winfo reqwidth $w]/2]
set y [expr [winfo y .] + [winfo height .]/3 - [winfo reqheight $w]/3]
wm geom $w +$x+$y
wm deiconify $w
}
proc dialog {title cancel {ok ""} {default 0}} {
set w ".d[unique]"
toplevel $w -class Dialog -cursor left_ptr
wm title $w $title
wm transient $w .
wm withdraw $w
frame $w.top -relief raised -bd 1
frame $w.bot -relief raised -bd 1
pack $w.top -side top -expand 1 -fill both -ipadx 4m -ipady 4m
pack $w.bot -side bottom -fill both -ipadx 2m -ipady 2m
frame $w.bot.default -relief sunken -bd 1
button $w.bot.cancel -width 6 -text $cancel \
-command "set gAnswer 0; [list destroy $w]"
if {[string length $ok] > 0} {
button $w.bot.ok -width 6 -text $ok \
-command "set gAnswer 1; [list destroy $w]"
}
if {$default} {
pack $w.bot.cancel -side left -expand 1;# -ipadx 2m -ipady 1m
pack $w.bot.default -side left -expand 1 -ipadx 1m -ipady 1m
pack $w.bot.ok -in $w.bot.default -expand 1;# -ipadx 2m -ipady 1m
bind $w <Key-Return> [list $w.bot.ok invoke]
} else {
pack $w.bot.default -side left -expand 1 -ipadx 1m -ipady 1m
pack $w.bot.cancel -in $w.bot.default -expand 1;# -ipadx 2m -ipady 1m
if {[string length $ok] > 0} {
pack $w.bot.ok -side left -expand 1;# -ipadx 2m -ipady 1m
}
bind $w <Key-Return> [list $w.bot.cancel invoke]
}
return $w
}
proc showdialog {w} {
showwindow $w
grab set $w
}
proc alert {kind title text {aspect 100}} {
global gThinFont
set w [dialog $title "OK"]
frame $w.top.x
label $w.top.x.icon -bitmap $kind -anchor n
frame $w.top.x.space -width 2m
message $w.top.x.msg -aspect $aspect -font $gThinFont -text $text
pack $w.top.x.icon $w.top.x.space $w.top.x.msg -side left -fill y
pack $w.top.x -expand 1
showdialog $w
tkwait window $w
}
proc bgerror {error} {
alert stop "Error" "Sorry, $error." 1000
return -code break
}
proc fixtext {text} {
regsub -all "(\[^\n])\n\[ \t]*(\[^\n])" $text {\1 \2} text
regsub -all "(^|\n)\[ \t]+" $text {\1} text
regsub "\n*\$" $text "" text
return $text
}
proc copy_ufs_to_hfs {srcvol name mode dstvol} {
$dstvol copyin $mode "[$srcvol cwd]/$name" :
}
proc copy_hfs_to_ufs {srcvol name mode dstvol} {
$srcvol copyout $mode $name "[$dstvol cwd]/."
}
proc copy_hfs_to_hfs {srcvol name mode dstvol} {
$srcvol copy $name $dstvol :
}
proc copy_ufs_to_ufs {srcvol name mode dstvol} {
$srcvol copy $name "[$dstvol cwd]/."
}
proc copy_directory {srcvol name dstvol copyproc modeproc} {
global gXMode
set srccwd [abspath [$srcvol sepchar] [$srcvol path]]
set dstcwd [abspath [$dstvol sepchar] [$dstvol path]]
$dstvol mkdir $name
$srcvol chdir $name
$dstvol chdir $name
foreach ind [$srcvol dir] {
array set item $ind
if {$item(kind) == "directory"} {
copy_directory $srcvol $item(name) $dstvol $copyproc $modeproc
} else {
if {$gXMode == "auto"} {
set mode [$modeproc $srcvol item]
} else {
set mode $gXMode
}
$copyproc $srcvol $item(name) $mode $dstvol
}
}
$srcvol chdir $srccwd
$dstvol chdir $dstcwd
}
proc delete_directory {vol name} {
set cwd [abspath [$vol sepchar] [$vol path]]
$vol chdir $name
foreach ind [$vol dir] {
array set item $ind
if {$item(kind) == "directory"} {
delete_directory $vol $item(name)
} else {
$vol delete $item(name)
}
}
$vol chdir $cwd
$vol rmdir $name
}
proc do_open {side} {
set box $side.mid.lb
set sel [$box curselection]
set ind [lindex $sel 0]
global vol
set name [$box get $ind]
if {[regexp "(.*)[$vol($side) sepchar]\$" $name ignore name]} {
mchdir $side $name
} else {
show_file $side $name
}
}
proc do_copy {side other} {
set box $side.mid.lb
set sel [$box curselection]
global gXMode gAskDelete gAnswer
global fstype dir vol
if {$side == ".l"} {
set other ".r"
} else {
set other ".l"
}
set needupdate 0
foreach ind $sel {
array set item [lindex $dir($side) $ind]
$box see $ind
# if {$gAskDelete && [catch {$vol($other) stat $item(name)}] == 0} {
# set w [dialog "Overwrite" "Cancel" "OK"]
# frame $w.top.x
# label $w.top.x.icon -bitmap caution -anchor n
# frame $w.top.x.space -width 2m
# message $w.top.x.msg -font $gThinFont -aspect 500 \
# -text "Overwrite \"$item(name)\"?"
# pack $w.top.x.icon $w.top.x.space $w.top.x.msg -side left -fill y
# pack $w.top.x -expand 1
# showdialog $w
# tkwait window $w
# if {$gAnswer == 0} {
# break
# }
# }
watch_cursor
if {$item(kind) == "directory"} {
set update [after idle \
"[list updatelist $side]; [list updatelist $other]"]
copy_directory $vol($side) $item(name) $vol($other) \
copy_$fstype($side)_to_$fstype($other) \
$fstype($side)_automode
} else {
set update [after idle [list updatelist $other]]
if {$gXMode == "auto"} {
set mode [$fstype($side)_automode $vol($side) item]
} else {
set mode $gXMode
}
copy_$fstype($side)_to_$fstype($other) \
$vol($side) $item(name) $mode $vol($other)
}
after cancel $update
set needupdate 1
$box selection clear $ind
}
if {$needupdate} {
updatelist $other; clearlists
}
}
proc do_info {side} {
set box $side.mid.lb
set sel [$box curselection]
global vol dir
foreach ind $sel {
show_info $vol($side) [lindex $dir($side) $ind]
}
}
proc do_rename {side} {
global dir
global gTypeFont
set box $side.mid.lb
set sel [$box curselection]
array set item [lindex $dir($side) [lindex $sel 0]]
set w [dialog "Rename" "Cancel" "Rename" 1]
$w.bot.ok config -command [list rename2 $w $side]
frame $w.top.x
label $w.top.x.lbl -text "Rename \"$item(name)\" to:"
label $w.top.x.srcname -text $item(name)
entry $w.top.x.dstname -width 25 -font $gTypeFont -exportsel 0
pack $w.top.x.lbl $w.top.x.dstname -side top -pady 0.5m
pack $w.top.x -expand 1
$w.top.x.dstname insert end $item(name)
$w.top.x.dstname selection range 0 end
focus $w.top.x.dstname
showdialog $w
}
proc rename2 {w side} {
global vol
set srcname [$w.top.x.srcname cget -text]
set dstname [$w.top.x.dstname get]
watch_cursor $w
destroy $w
if {[string length $dstname] == 0} {
error "cannot rename to zero-length name"
}
if {[string compare $srcname $dstname] != 0} {
$vol($side) rename $srcname $dstname
updatelist $side; clearlists
}
}
proc do_delete {side} {
set box $side.mid.lb
set sel [$box curselection]
global gThinFont gAskDelete gAnswer
global vol dir
set sepchar [$vol($side) sepchar]
set needupdate 0
foreach ind $sel {
array set item [lindex $dir($side) $ind]
$box see $ind
set recursive [expr {$item(kind) == "directory" && $item(size) > 0}]
if {$recursive || $gAskDelete} {
set w [dialog "Delete" "Cancel" "Delete"]
if {$recursive} {
set p1 "Recursively delete \"$item(name)\""
set p2 "containing $item(size) item[plural $item(size) "" "s"]"
set prompt "$p1 $p2?"
} else {
set prompt "Really delete \"$item(name)\"?"
}
frame $w.top.x
label $w.top.x.icon -bitmap caution -anchor n
frame $w.top.x.space -width 2m
message $w.top.x.msg -font $gThinFont -aspect 500 -text $prompt
pack $w.top.x.icon $w.top.x.space $w.top.x.msg -side left -fill y
pack $w.top.x -expand 1
showdialog $w
tkwait window $w
if {$gAnswer == 0} {
break
}
}
watch_cursor
set update [after idle "[list updatelist $side]; clearlists"]
if {$item(kind) == "directory"} {
delete_directory $vol($side) $item(name)
} else {
$vol($side) delete $item(name)
}
after cancel $update
set needupdate 1
$box selection clear $ind
}
if {$needupdate} {
updatelist $side; clearlists
}
}
proc format_disk {} {
global gTypeFont gThinFont gLowFormatCmd gDefaultDevice
set w [dialog "Format Disk" "Cancel" "Format"]
$w.bot.ok config -command [list format2 $w]
frame $w.top.x
set low [frame $w.top.x.low]
label $low.icon -bitmap floppy
frame $low.info
checkbutton $low.info.cb -var gLowFormat \
-text "Low-level Format" -anchor w
entry $low.info.cmd -width 26 -font $gTypeFont
pack $low.info.cb $low.info.cmd -side top -fill x -pady 0.5m
pack $low.icon $low.info -side left -padx 2m
frame $w.top.x.space1 -height 2m
label $w.top.x.hfs -text "Initialize as HFS Volume..." -anchor w
set dev [frame $w.top.x.dev]
label $dev.lbl -width 6 -text "Device:" -font $gThinFont -anchor w
entry $dev.name -width 15 -font $gTypeFont
label $dev.parlbl -text "Partition:" -font $gThinFont
entry $dev.par -width 2 -font $gTypeFont
pack $dev.lbl $dev.name $dev.parlbl $dev.par -side left -padx 0.5m
set vol [frame $w.top.x.vol]
label $vol.lbl -width 6 -text "Name:" -font $gThinFont -anchor w
entry $vol.name -width 27 -font $gTypeFont
pack $vol.lbl $vol.name -side left -padx 0.5m
frame $w.top.x.space2 -height 2m
set mount [frame $w.top.x.mount]
label $mount.lbl -text "Open:" -font $gThinFont
radiobutton $mount.l -var gMountSide -value ".l" \
-text "On Left" -font $gThinFont
radiobutton $mount.r -var gMountSide -value ".r" \
-text "On Right" -font $gThinFont
pack $mount.lbl $mount.l $mount.r -side left
pack $low $w.top.x.space1 $w.top.x.hfs $dev $vol \
-side top -fill x -pady 0.5m
pack $w.top.x.space2 $mount -side top -pady 0.5m
pack $w.top.x -expand 1
$w.top.x.low.info.cmd insert end $gLowFormatCmd
$w.top.x.dev.name insert end $gDefaultDevice
$w.top.x.vol.name insert end "Untitled"
# focus $w.top.x.dev.name
showdialog $w
}
proc format2 {w} {
global gAppCWD gLowFormat gLowFormatCmd gMountSide
set gLowFormatCmd [$w.top.x.low.info.cmd get]
set path [$w.top.x.dev.name get]
set partno [$w.top.x.dev.par get]
set vname [$w.top.x.vol.name get]
if {[string length $partno] == 0} {
set partno 0
}
watch_cursor $w
destroy $w
cd $gAppCWD
if {$gLowFormat && [string length $gLowFormatCmd] > 0} {
exec /bin/sh -c $gLowFormatCmd </dev/null >/dev/null
}
if {[string length $path] > 0} {
hfs format $path $partno $vname
mountdev $gMountSide $path
}
}
proc help_about {} {
global gThinFont gSmallFont gTitleFont
set w [dialog "About" "OK"]
set f [frame $w.top.t]
regsub {\(C\)} [hfs copyright] "\251" copyright
frame $f.banner
label $f.banner.icon -bitmap macdaemon
label $f.banner.title -text "xhfs" -font $gTitleFont
pack $f.banner.icon $f.banner.title -side left -padx 2m
frame $f.space -height 2m
label $f.vers -text [hfs version] -font $gThinFont
label $f.copy -text $copyright -font $gSmallFont
label $f.auth -text [hfs author] -font $gSmallFont
pack $f.banner $f.space $f.vers $f.copy $f.auth -side top
pack $f -expand 1
showdialog $w
}
proc help_license {} {
alert note "Software License" [fixtext [hfs license]] 190
}
proc help_options {} {
set text "\
Show All Files: Some files are normally hidden. To unhide\n\
invisible files on HFS volumes, or to unhide UNIX files which\n\
begin with a period, select this option.\n\n\
\
Ask Before Delete: To disable verification before deleting files\n\
and empty directories, deselect this option.\n\n\
\
Auto Mode Select: The transfer mode is normally selected\n\
automatically. To use manual selection, deselect this option.\n\n\
"
alert note "Options" [fixtext $text] 150
}
proc help_modes {} {
set text "\
MacBinary II: A popular format for binary file transfer. Both\n\
forks of the Macintosh file are preserved. This is the\n\
recommended mode for transferring arbitrary Macintosh files.\n\n\
\
BinHex: An alternative format for ASCII file transfer. Both\n\
forks of the Macintosh file are preserved.\n\n\
\
Text: Performs end-of-line translation. Only the data fork of\n\
the Macintosh file is copied.\n\n\
\
Raw Data: Performs no translation. Only the data fork of the\n\
Macintosh file is copied.\n\n\
\
Automatic: A mode will be chosen automatically for each\n\
file based on a set of predefined heuristics.\n\n\
"
alert note "Transfer Modes" [fixtext $text]
}
proc hfs_automode {vol array} {
upvar $array item
if {$item(kind) == "directory"} {
return auto
} elseif {[regexp {^TEXT|ttro$} $item(type)]} {
return text
} elseif {$item(rsize) == 0} {
return raw
} else {
return macb
}
}
proc ufs_automode {vol array} {
upvar $array item
set name $item(name)
if {$item(kind) == "directory"} {
return auto
} elseif {[regexp -nocase {\.bin$} $name]} {
return macb
} elseif {[regexp -nocase {\.hqx$} $name]} {
return binh
} elseif {[regexp -nocase {\.(txt|c|h|html?|rtf)$} $name]} {
return text
} elseif {[regexp -nocase {\.(sit|sea|cpt|tar|gz|Z|gif|jpg)$} $name]} {
return raw
} else {
cd [$vol cwd]
if {([catch {exec file -L $name} type] == 0 || \
[catch {exec file $name} type] == 0) && \
[regexp {text|commands} $type]} {
return text
}
return raw
}
}
proc watch_cursor {{w "."} {default "left_ptr"}} {
$w config -cursor watch
if {[string compare $w "."] != 0} {
. config -cursor watch
}
update idletasks
$w config -cursor $default
if {[string compare $w "."] != 0} {
. config -cursor left_ptr
}
}
proc updatelist {side} {
global gShowAllFiles
global dir vol fstype
set box $side.mid.lb
$box delete 0 end
if {! [info exists vol($side)]} {
return
}
if {$fstype($side) == "hfs"} {
set chartrans {nulltrans} ;# {hfs charlocal}
} else {
set chartrans {nulltrans}
}
set list [$vol($side) dir]
set dir($side) {}
set sepchar [$vol($side) sepchar]
foreach ent $list {
array set item $ent
set name [eval $chartrans [list $item(name)]]
if {$item(kind) == "directory"} {
set name "$name$sepchar"
}
if {$gShowAllFiles || [lsearch $item(flags) "invis"] < 0} {
$box insert end $name
lappend dir($side) $ent
}
}
set m $side.mb.pop.m
$m delete 0 end
set path [$vol($side) path]
set last 0
foreach elt $path {
set partial [abspath [$vol($side) sepchar] [lrange $path 0 $last]]
incr last
$m insert 0 command -label $elt -command [list mchdir $side $partial]
}
if {$fstype($side) == "hfs"} {
set what "Folder"
} else {
set what "Directory"
}
foreach menuitem {
{separator}
{command -label "New $what..." -command [list mmkdir $side]}
} {eval $m insert end $menuitem}
global cwd
set cwd($side) [lindex $path [expr [llength $path] - 1]]
set avail [lindex [$vol($side) size] 1]
$side.info.x.msg config -text "[size $avail] available"
}
proc clearlists {} {
.l.mid.lb selection clear 0 end
.r.mid.lb selection clear 0 end
.m.1.ops.view config -state disabled -text "View"
.m.1.ops.copy config -state disabled -text "Copy"
.m.2.ops.info config -state disabled
.m.2.ops.rena config -state disabled
.m.2.ops.dele config -state disabled
}
proc mchdir {side path} {
global vol
watch_cursor
$vol($side) chdir $path
updatelist $side; clearlists
}
proc open_volume {side} {
global gDefaultDevice gMountSide gTypeFont gThinFont
set gMountSide $side
set w [dialog "Open" "Cancel" "Open" 1]
$w.bot.ok config -command [list open2 $w]
frame $w.top.x
set lbl1 [frame $w.top.x.lbl1]
label $lbl1.path -text "Path to HFS Device"
pack $lbl1.path
set lbl2 [frame $w.top.x.lbl2]
# label $lbl2.or -text "or"
# label $lbl2.icon -bitmap sm_floppy
label $lbl2.dev -text "or UNIX Directory:"
pack $lbl2.dev -side left -padx 0.5m
entry $w.top.x.entry -width 25 -font $gTypeFont
frame $w.top.x.space -height 2m
set mount [frame $w.top.x.mount]
label $mount.lbl -text "Open:" -font $gThinFont
radiobutton $mount.l -var gMountSide -value ".l" \
-text "On Left" -font $gThinFont
radiobutton $mount.r -var gMountSide -value ".r" \
-text "On Right" -font $gThinFont
pack $mount.lbl $mount.l $mount.r -side left
pack $lbl1 $lbl2 $w.top.x.entry $w.top.x.space $mount \
-side top -pady 0.5m
pack $w.top.x -expand 1
$w.top.x.entry insert end $gDefaultDevice
$w.top.x.entry selection range 0 end
focus $w.top.x.entry
showdialog $w
}
proc open2 {w} {
global gMountSide
global vol
set path [$w.top.x.entry get]
watch_cursor $w
destroy $w
if {[string length $path] == 0} {
error "missing path"
}
mountdev $gMountSide $path
}
proc close_volume {side} {
global vol
watch_cursor
$vol($side) umount
unset vol($side)
pack forget $side.mb.pop $side.info.x.icon $side.info.x.lock
$side.info.x.msg config -text ""
$side.sub.open config -text "Open..." -command [list open_volume $side]
$side.sub.info config -state disabled
updatelist $side; clearlists
}
proc ctime {secs} {
return [clock format $secs -format "%c"]
}
proc volume_info {side} {
global gThinFont
global vol dev fstype media
set v $vol($side)
if {$side == ".l"} {
set eside "Left"
} else {
set eside "Right"
}
set w [window "$eside Info" "Info"]
set locked [$v islocked]
set icon $media($side)
set vname [$v vname]
if {$fstype($side) == "ufs"} {
set ishfs 0
set where [file dirname $vname]
set vname [file tail $vname]
set kind "UNIX Directory"
} else {
set ishfs 1
set kind "HFS Volume"
set where $dev($side)
set crdate [$v crdate]
}
wm iconbitmap $w $icon
wm iconmask $w ${icon}_mask
frame $w.x
pack $w.x -expand 1 -fill both -ipadx 4m -ipady 4m
set f [frame $w.x.f]
frame $f.name
frame $f.name.space -width 5
label $f.name.icon -bitmap $icon
label $f.name.text -text $vname -font $gThinFont
pack $f.name.space $f.name.icon $f.name.text -side left -padx 2m
if {$locked} {
label $f.name.lock -bitmap padlock
pack $f.name.lock -side left
}
frame $f.space1 -height 4m
frame $f.kind
label $f.kind.lbl -width 9 -text "Kind: " -anchor e
label $f.kind.val -text $kind -font $gThinFont
pack $f.kind.lbl $f.kind.val -side left
frame $f.size
label $f.size.lbl -width 9 -text "Size: " -anchor e
label $f.size.val -text [size [lindex [$v size] 0]] -font $gThinFont
pack $f.size.lbl $f.size.val -side left
frame $f.space2 -height 4m
frame $f.where
label $f.where.lbl -width 9 -text "Where: " -anchor e
label $f.where.val -text $where -font $gThinFont
pack $f.where.lbl $f.where.val -side left
pack $f.name $f.space1 $f.kind $f.size $f.space2 $f.where -side top -fill x
if {$ishfs} {
frame $f.space3 -height 4m
frame $f.cr
label $f.cr.lbl -width 9 -text "Created: " -anchor e
label $f.cr.val -text [ctime $crdate] -font $gThinFont
pack $f.cr.lbl $f.cr.val -side left
pack $f.space3 $f.cr -side top -fill x
}
frame $f.md
label $f.md.lbl -width 9 -text "Modified: " -anchor e
label $f.md.val -text [ctime [$v mddate]] -font $gThinFont
pack $f.md.lbl $f.md.val -side left
pack $f.md -side top -fill x
pack $f -expand 1
showwindow $w
}
proc mmkdir {side} {
global gTypeFont
global fstype
if {$fstype($side) == "hfs"} {
set what "folder"
set title "New Folder"
} else {
set what "directory"
set title "New Directory"
}
set w [dialog $title "Cancel" "Create" 1]
$w.bot.ok config -command [list mmkdir2 $w $side $what]
frame $w.top.x
frame $w.top.x.lbl
label $w.top.x.lbl.icon -bitmap sm_folder
label $w.top.x.lbl.text -text "Name for new $what:"
pack $w.top.x.lbl.icon $w.top.x.lbl.text -side left -padx 0.5m
entry $w.top.x.entry -width 25 -font $gTypeFont -exportsel 0
pack $w.top.x.lbl $w.top.x.entry -side top -pady 0.5m
pack $w.top.x -expand 1
$w.top.x.entry insert end "untitled $what"
$w.top.x.entry selection range 0 end
focus $w.top.x.entry
showdialog $w
}
proc mmkdir2 {w side what} {
global vol
set name [$w.top.x.entry get]
watch_cursor $w
destroy $w
if {[string length $name] == 0} {
error "cannot create $what with empty name"
}
$vol($side) mkdir $name
$vol($side) chdir $name
updatelist $side; clearlists
}
proc plural {count single plural} {
if {$count == 1} {
return $single
} else {
return $plural
}
}
proc nulltrans {str} {
return $str
}
proc show_file {side fname} {
global gTypeFont
global vol fstype
watch_cursor
if {$fstype($side) == "hfs"} {
set charset macroman
} else {
set charset latin1
}
# Make sure we can open the file before building the interface
set fh [$vol($side) open $fname]
set path [abspath [$vol($side) sepchar] \
[concat [$vol($side) path] [list $fname]] 1]
set w [window $path $fname]
frame $w.x
pack $w.x -expand 1 -fill both -padx 1m -pady 1m
set f [frame $w.x.f]
text $f.text -yscroll [list $f.scroll set] \
-height 30 -width 80 -wrap word -font $gTypeFont
scrollbar $f.scroll -orient vert -command [list $f.text yview]
pack $f.scroll -side right -fill y
pack $f.text -fill both -expand 1
pack $f -fill both -expand 1
watch_cursor $f.text xterm
after idle "[list $f.text config -state disabled]; [list showwindow $w]"
while {1} {
set buf [$fh read 512]
if {[string length $buf] == 0} {
$fh close
break
}
regsub -all "\r\n?" $buf "\n" buf
$f.text insert end [hfs chartrans $charset latin1 $buf]
}
}
proc commas {number} {
while {[regexp \
{([^,]+)(([0-9][0-9][0-9])(,[0-9][0-9][0-9])*([^0-9].*|$))} \
$number ignore pre rest]} {
set number "$pre,$rest"
}
return $number
}
proc size {bytes} {
if {$bytes == -1} {
return "unknown K"
}
set k 1024
set mb [expr 1024 * $k]
if {$bytes > $mb} {
set q $mb
set u "M"
} else {
set q $k
set u "K"
}
set amount [commas [format "%.1f" [expr ${bytes}.0 / $q]]]
regsub {\.0$} $amount "" amount
regsub {^0$} $amount "zero " amount
return "$amount$u"
}
proc show_info {vol list} {
global gTypeFont gThinFont
array set info $list
set isdir [expr [string compare $info(kind) "directory"] == 0]
set ishfs [info exists info(crdate)]
if {$ishfs} {
set chartrans {nulltrans} ;# {hfs charlocal}
} else {
set chartrans {nulltrans}
}
set name [eval $chartrans [list $info(name)]]
set w [window "$name Info" $name]
set icon "document"
if {$isdir} {
set icon "folder"
if {$ishfs} {
set info(kind) "folder"
}
} else {
if {$ishfs} {
if {[string compare $info(type) "APPL"] == 0} {
set icon "application"
set info(kind) "application"
} else {
set info(kind) "document"
}
} else {
cd [$vol cwd]
if {[file executable $info(name)]} {
set icon "application"
}
}
}
wm iconbitmap $w $icon
wm iconmask $w ${icon}_mask
if {$ishfs} {
set where "[join [$vol path] ":"]:"
} else {
set where [$vol cwd]
cd $where
if {[catch {exec file $info(name)} type] == 0} {
if {[string first "$info(name):" $type] == 0} {
set type [string range $type \
[expr [string length $info(name)] + 1] end]
}
regsub "^\[ \t]*" $type "" info(kind)
}
}
frame $w.x
pack $w.x -expand 1 -fill both -ipadx 4m -ipady 4m
set f [frame $w.x.f]
frame $f.name
frame $f.name.space -width 5
label $f.name.icon -bitmap $icon
label $f.name.text -text $name -font $gThinFont
pack $f.name.space $f.name.icon $f.name.text -side left -padx 2m
if {[lsearch $info(flags) "locked"] >= 0} {
label $f.name.lock -bitmap padlock
pack $f.name.lock -side left
}
frame $f.space1 -height 4m
frame $f.kind
label $f.kind.lbl -width 9 -text "Kind: " -anchor e
label $f.kind.val -text $info(kind) -font $gThinFont
pack $f.kind.lbl $f.kind.val -side left
set label "Size: "
if {$isdir} {
set label "Contains: "
set size $info(size)
set what [plural $size item items]
} elseif {$ishfs} {
set size [list $info(rsize) $info(dsize)]
set what [list \
[plural $info(rsize) byte bytes] \
[plural $info(dsize) byte bytes]]
} else {
set size $info(size)
set what [plural $size byte bytes]
}
frame $f.size
if {[llength $size] == 1} {
label $f.size.lbl -width 9 -text $label -anchor e
label $f.size.val -text "[commas $size] $what" -font $gThinFont
pack $f.size.lbl $f.size.val -side left
} else {
set rsize [lindex $size 0]
set rwhat [lindex $what 0]
set dsize [lindex $size 1]
set dwhat [lindex $what 1]
frame $f.size.r
label $f.size.r.lbl -width 9 -text "Resource: " -anchor e
label $f.size.r.val -text "[commas $rsize] $rwhat" -font $gThinFont
pack $f.size.r.lbl $f.size.r.val -side left
frame $f.size.d
label $f.size.d.lbl -width 9 -text "Data: " -anchor e
label $f.size.d.val -text "[commas $dsize] $dwhat" -font $gThinFont
pack $f.size.d.lbl $f.size.d.val -side left
pack $f.size.r $f.size.d -side top -fill x
}
frame $f.space2 -height 4m
frame $f.where
label $f.where.lbl -width 9 -text "Where: " -anchor e
label $f.where.val -text $where -font $gThinFont
pack $f.where.lbl $f.where.val -side left
pack $f.name $f.space1 $f.kind $f.size $f.space2 $f.where -side top -fill x
if {$ishfs && ! $isdir} {
frame $f.space3 -height 4m
frame $f.type
label $f.type.lbl -width 9 -text "Type: " -anchor e
label $f.type.val -text $info(type) -font $gTypeFont
pack $f.type.lbl $f.type.val -side left
frame $f.crea
label $f.crea.lbl -width 9 -text "Creator: " -anchor e
label $f.crea.val -text $info(creator) -font $gTypeFont
pack $f.crea.lbl $f.crea.val -side left
pack $f.space3 $f.type $f.crea -side top -fill x
}
if {$ishfs} {
frame $f.space4 -height 4m
frame $f.cr
label $f.cr.lbl -width 9 -text "Created: " -anchor e
label $f.cr.val -text [ctime $info(crdate)] -font $gThinFont
pack $f.cr.lbl $f.cr.val -side left
pack $f.space4 $f.cr -side top -fill x
}
frame $f.md
label $f.md.lbl -width 9 -text "Modified: " -anchor e
label $f.md.val -text [ctime $info(mddate)] -font $gThinFont
pack $f.md.lbl $f.md.val -side left
pack $f.md -side top -fill x
pack $f -expand 1
showwindow $w
}
proc abspath {sepchar path {asfile 0}} {
switch $sepchar {
":" {
if {$asfile} {
return [join $path ":"]
} else {
return "[join $path ":"]:"
}
}
"/" {
if {[string compare [lindex $path 0] "/"] == 0} {
set path [lreplace $path 0 0 ""]
}
if {[llength $path] == 1 && \
[string length [lindex $path 0]] == 0} {
return "/"
} else {
return [join $path "/"]
}
}
}
}
proc ufs_handle {dir} {
set handle "ufsvol[unique]"
global ufsdir
cd $dir
set ufsdir($handle) [pwd]
proc $handle {verb args} {
set handle [lindex [info level [info level]] 0]
global gAppCWD gDfCmd
global ufsdir
set cwd $ufsdir($handle)
switch -glob $verb {
vname {
return $cwd
}
umount {
cd $gAppCWD
unset ufsdir($handle)
rename $handle ""
}
path {
set path [split $cwd "/"]
if {[string compare [lindex $path 0] ""] == 0} {
set path [lreplace $path 0 0 "/"]
}
if {[llength $path] == 2 && \
[string length [lindex $path 1]] == 0} {
set path [lreplace $path 1 1]
}
return $path
}
dir {
cd $cwd
set list {}
set files [lsort [glob .* *]]
foreach name $files {
if {[string compare $name "."] == 0 || \
[string compare $name ".."] == 0} {
continue
}
if {[catch {file stat $name stat}]} {
continue
}
set kind [file type $name]
set mddate $stat(mtime)
if {[file isdirectory $name]} {
set kind "directory"
set size "?"
catch {
set size [expr [llength \
[glob $name/.* $name/*]] - 2]
}
} elseif {$kind == "file"} {
set size $stat(size)
} else {
continue
}
set flags {}
if {! [file writable $name]} {
lappend flags "locked"
}
if {[regexp {^\.} $name]} {
lappend flags "invis"
}
lappend list [list \
name $name \
kind $kind \
size $size \
flags $flags \
mddate $mddate]
}
return $list
}
chdir {
set path [lindex $args 0]
if {! [regexp {^/} $path]} {
cd $cwd
}
cd $path
set ufsdir($handle) [pwd]
}
cwd {
return $cwd
}
sepchar {
return "/"
}
mkdir {
set path [lindex $args 0]
cd $cwd
exec mkdir $path
}
rmdir {
set path [lindex $args 0]
cd $cwd
exec rmdir $path
}
open {
set path [lindex $args 0]
cd $cwd
set fh [open $path]
proc $fh {verb args} {
set fh [lindex [info level [info level]] 0]
switch -glob $verb {
read {
set len [lindex $args 0]
return [read $fh $len]
}
close {
close $fh
rename $fh ""
}
* {
error "unknown call to $fh $verb $args"
}
}
}
return $fh
}
copy {
set path [lindex $args 0]
set dest [lindex $args 1]
cd $cwd
exec cp -f $path $dest
}
rename {
set path [lindex $args 0]
set dest [lindex $args 1]
cd $cwd
exec mv -f $path $dest
}
delete {
set path [lindex $args 0]
cd $cwd
exec rm -f $path
}
size {
if {[catch {eval exec [join $gDfCmd " "] [list $cwd]} info]} {
return [list -1 -1]
}
set s "\[ \t]+"
set d "\[0-9]+"
if {[catch {
regexp "(.*)\n(\[^\n]*)\$" $info ignore head data
regexp "${s}($d)$s$d${s}($d)" $data ignore size avail
}]} {
return [list -1 -1]
}
set block 512
if {[regexp -nocase {kbyte|1024} $head]} {
set block 1024
}
return [list [expr $size * $block] [expr $avail * $block]]
}
mddate {
return [file mtime $cwd]
}
islocked {
return 0
}
* {
error "unknown call to $handle $verb $args"
}
}
}
return $handle
}
proc ufs {verb args} {
switch $verb {
mount {
set dir [lindex $args 0]
return [ufs_handle $dir]
}
* {
error "bad arg 1 to ufs"
}
}
}
proc mountdev {side device} {
global gAppCWD gXMode
global vol dev fstype media
if {[info exists vol($side)]} {
$vol($side) umount
}
cd $gAppCWD
if {! [file exists $device]} {
error "can't open $device (no such file or directory)"
}
if {[file isdirectory $device]} {
set type ufs
} else {
set type hfs
}
set v [$type mount $device]
set fstype($side) $type
set locked [$v islocked]
if {$type == "ufs"} {
set icon "folder"
} else {
set size [lindex [$v size] 0]
if {$size <= [expr 1440 * 1024]} {
set icon "floppy"
} elseif {$locked && \
$size >= [expr 100 * 1024 * 1024] && \
$size <= [expr 800 * 1024 * 1024]} {
set icon "cdrom"
} else {
set icon "harddisk"
}
}
$side.info.x.icon config -bitmap sm_$icon
pack $side.info.x.icon -before $side.info.x.msg -side left -padx 0.5m
if {$locked} {
pack $side.info.x.lock -side left -padx 0.5m
} else {
pack forget $side.info.x.lock
}
pack $side.mb.pop
$side.sub.open config -text "Close" -command [list close_volume $side]
$side.sub.info config -state normal
set vol($side) $v
set dev($side) $device
set media($side) $icon
updatelist $side; clearlists
if {$fstype(.l) == $fstype(.r)} {
set state disabled
set gXMode auto
} else {
set state normal
}
foreach mode {auto macb binh text raw} {
.m.3.mode.$mode config -state $state
}
}
proc flushtask {} {
global gFlushInterval
after $gFlushInterval flushtask
watch_cursor
hfs flushall
}
###############################################################################
# Apparently some versions of Tk don't grok this
if {$argc > 0 && [string compare [lindex $argv 0] "--"] == 0} {
incr argc -1
set argv [lreplace $argv 0 0]
}
if {[string compare [lindex $argv 0] "--license"] == 0} {
puts -nonewline stdout "\n[hfs license]"
exit
}
if {[string compare [lindex $argv 0] "--version"] == 0} {
puts stdout "[hfs version] - [hfs copyright]"
puts stdout "`$argv0 --license' for licensing information."
exit
}
###############################################################################
# Build the user interface
proc makelist {w click other} {
global gSmallFont
frame $w
frame $w.mb -height 30
menubutton $w.mb.pop -textvariable cwd($w) \
-indicatoron 1 -menu $w.mb.pop.m \
-relief raised -bd 2 -padx 4p -pady 4p \
-highlightthickness 2 -anchor c
menu $w.mb.pop.m -tearoff 0
frame $w.info -height 16
pack propagate $w.info 0
frame $w.info.x
pack $w.info.x -expand 1
label $w.info.x.icon
label $w.info.x.msg -font $gSmallFont -text ""
label $w.info.x.lock -bitmap padlock
pack $w.info.x.msg -side left -padx 0.5m
frame $w.mid -bd 0
listbox $w.mid.lb -xscroll "$w.bot.sb set" -yscroll "$w.mid.sb set" \
-exportselection 0 -height 16 -width 25 -selectmode extended
scrollbar $w.mid.sb -orient vert -command [list $w.mid.lb yview]
bind $w.mid.lb <Button-1> [list $other selection clear 0 end]
bind $w.mid.lb <KeyPress> [list $other selection clear 0 end]
bind $w.mid.lb <ButtonRelease-1> [list wclick $w $click]
bind $w.mid.lb <KeyRelease> [list wclick $w $click]
bind $w.mid.lb <Double-ButtonRelease-1> ".m.1.ops.view invoke"
bind $w.mid.lb <Key-Return> ".m.1.ops.view invoke"
pack $w.mid.sb -side right -fill y
pack $w.mid.lb -side right -fill both -expand 1
frame $w.bot
scrollbar $w.bot.sb -orient horiz -command [list $w.mid.lb xview]
frame $w.bot.c -height 24 -width 24
# label $w.bot.c.icon
# pack $w.bot.c.icon -expand 1
# pack propagate $w.bot.c 0
pack $w.bot.c -side right
pack $w.bot.sb -side bottom -fill x
frame $w.sub -relief sunken -bd 1
frame $w.sub.space1 -width 1m
button $w.sub.open -width 8 -text "Open..." \
-command [list open_volume $w]
button $w.sub.info -width 8 -text "Volume Info" -state disabled \
-command [list volume_info $w]
frame $w.sub.space2 -width 1m
pack $w.sub.space1 -side left
pack $w.sub.open $w.sub.info \
-side left -pady 1m -padx 0.5m -fill x -expand 1
pack $w.sub.space2 -side left
frame $w.space -height 2m
pack $w.space -side bottom
pack $w.mb -side top
pack $w.sub -side bottom -fill x -pady 1m -ipadx 2m
pack $w.info $w.bot -side bottom -fill x
pack $w.mid -side top -fill both -expand 1
}
proc wclick {side copytext} {
set box $side.mid.lb
set sel [$box curselection]
global gXMode gAutoModeSelect
global vol dir fstype
if {$side == ".l"} {
set other ".r"
} else {
set other ".l"
}
set cstate normal
if {! [info exists vol($other)]} {
set cstate disabled
}
if {[llength $sel] == 0} {
.m.1.ops.view config -state disabled -text "View"
.m.1.ops.copy config -state disabled -text "Copy"
.m.2.ops.info config -state disabled
.m.2.ops.rena config -state disabled
.m.2.ops.dele config -state disabled
} elseif {[llength $sel] == 1} {
array set item [lindex $dir($side) [lindex $sel 0]]
switch -glob $item(kind) {
directory {
set viewtext "Open"
set vstate normal
}
file {
set viewtext "View"
set mode [$fstype($side)_automode $vol($side) item]
if {$mode == "text" || $mode == "binh"} {
set vstate normal
} else {
set vstate disabled
}
}
}
.m.1.ops.view config -state $vstate -text $viewtext \
-command [list do_open $side]
.m.1.ops.copy config -state $cstate -text $copytext \
-command [list do_copy $side $other]
.m.2.ops.info config -state normal -command [list do_info $side]
.m.2.ops.rena config -state normal -command [list do_rename $side]
.m.2.ops.dele config -state normal -command [list do_delete $side]
if {$gAutoModeSelect && \
[.m.3.mode.auto cget -state] != "disabled"} {
if {[info exists mode]} {
set gXMode $mode
} else {
set gXMode [$fstype($side)_automode $vol($side) item]
}
}
} else {
.m.1.ops.view config -state disabled -text "View"
.m.1.ops.copy config -state $cstate -text $copytext \
-command [list do_copy $side $other]
.m.2.ops.info config -state normal -command [list do_info $side]
.m.2.ops.rena config -state disabled
.m.2.ops.dele config -state normal -command [list do_delete $side]
if {$gAutoModeSelect && \
[.m.3.mode.auto cget -state] != "disabled"} {
foreach ind $sel {
array set item [lindex $dir($side) $ind]
set auto [$fstype($side)_automode $vol($side) item]
if {[info exists mode]} {
if {$mode != $auto} {
set mode auto
break
}
} else {
set mode $auto
}
}
set gXMode $mode
}
}
}
# Middle controls
frame .m
frame .m.1
frame .m.1.ops -relief sunken -bd 1
frame .m.1.ops.space1 -height 0.5m
button .m.1.ops.view -state disabled -text "View"
button .m.1.ops.copy -state disabled -text "Copy"
frame .m.1.ops.space2 -height 0.5m
pack \
.m.1.ops.space1 \
.m.1.ops.view .m.1.ops.copy \
.m.1.ops.space2 \
-side top -fill x -padx 1m -pady 0.5m
pack .m.1.ops -side top -fill x
frame .m.2
frame .m.2.ops -relief sunken -bd 1
frame .m.2.ops.space1 -height 0.5m
button .m.2.ops.info -state disabled -text "Get Info"
button .m.2.ops.rena -state disabled -text "Rename..."
button .m.2.ops.dele -state disabled -text "Delete"
frame .m.2.ops.space2 -height 0.5m
pack \
.m.2.ops.space1 \
.m.2.ops.info .m.2.ops.rena .m.2.ops.dele \
.m.2.ops.space2 \
-side top -fill x -padx 1m -pady 0.5m
pack .m.2.ops -side top -fill x
frame .m.3
label .m.3.l -text "Mode"
frame .m.3.mode -relief groove -bd 2
frame .m.3.mode.space1 -height 1m
radiobutton .m.3.mode.macb -font $gThinFont -text "MacBinary II" \
-var gXMode -value macb -anchor w
radiobutton .m.3.mode.binh -font $gThinFont -text "BinHex" \
-var gXMode -value binh -anchor w
radiobutton .m.3.mode.text -font $gThinFont -text "Text" \
-var gXMode -value text -anchor w
radiobutton .m.3.mode.raw -font $gThinFont -text "Raw Data" \
-var gXMode -value raw -anchor w
radiobutton .m.3.mode.auto -font $gThinFont -text "Automatic" \
-var gXMode -value auto -anchor w
frame .m.3.mode.space2 -height 1m
set gXMode auto
pack \
.m.3.mode.space1 \
.m.3.mode.macb \
.m.3.mode.binh \
.m.3.mode.text \
.m.3.mode.raw \
.m.3.mode.auto \
.m.3.mode.space2 \
-side top -fill x -padx 1m
pack .m.3.l .m.3.mode -side top -fill x
pack .m.1 .m.2 .m.3 -side top -fill x -padx 2m -pady 2m
# Menu bar
frame .mb -relief raised -bd 2
frame .mbpad -height 3m
menubutton .mb.file -text "File" -menu .mb.file.m
menubutton .mb.opts -text "Options" -menu .mb.opts.m
menubutton .mb.help -bitmap help -menu .mb.help.m
pack .mb.file .mb.opts -side left
pack .mb.help -side right -fill y -ipadx 2m
menu .mb.file.m -tearoff 0
foreach menuitem {
{command -label "Open..." -command {open_volume $gMountSide}}
{separator}
{command -label "Format Disk..." -command "format_disk"}
{command -label "Eject Disk" -command "hfs flushall; exec eject"}
{separator}
{command -label "Quit" -command "destroy ."}
} {eval .mb.file.m add $menuitem}
menu .mb.opts.m -tearoff 0
foreach menuitem {
{checkbutton -label "Show All Files" -variable gShowAllFiles \
-command "watch_cursor; updatelist .l; updatelist .r; clearlists"}
{separator}
{checkbutton -label "Ask Before Delete" -variable gAskDelete}
{checkbutton -label "Auto Mode Select" -variable gAutoModeSelect}
} {eval .mb.opts.m add $menuitem}
set gAutoModeSelect 1
set gAskDelete 1
menu .mb.help.m -tearoff 0
foreach menuitem {
{command -label "About" -command "help_about"}
{command -label "Software License" -command "help_license"}
{separator}
{command -label "Options" -command "help_options"}
{command -label "Transfer Modes" -command "help_modes"}
} {eval .mb.help.m add $menuitem}
# Put it together
makelist .l ">> Copy >>" .r.mid.lb
makelist .r "<< Copy <<" .l.mid.lb
rename makelist ""
pack .mb .mbpad -side top -fill x
pack .l -side left -fill both -expand 1 -padx 3m
pack .r -side right -fill both -expand 1 -padx 3m
pack .m -side left
wm title . "Macintosh HFS Volume Browser"
wm iconbitmap . macdaemon
wm iconmask . macdaemon_mask
wm iconname . "HFS Browser"
. config -cursor left_ptr
###############################################################################
tk appname xhfs
set gAppCWD [pwd]
set fstype(.l) none
set fstype(.r) none
if {$argc > 2} {
puts stderr "Usage: $argv0 \[left-vol \[right-vol]]"
exit 1
}
if {$argc > 0} {
mountdev .l [lindex $argv 0]
if {$argc > 1} {
mountdev .r [lindex $argv 1]
}
}
if {$argc < 2} {
mountdev .r .
}
after $gFlushInterval flushtask