#!/usr/bin/perl -w #============================================================================ # NAME: # dmgutil.pl # # DESCRIPTION: # Disk image creation utility. # # COPYRIGHT: # Copyright (c) 2006-2009, refNum Software # # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # o Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # o Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # o Neither the name of refNum Software nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================ # Imports #---------------------------------------------------------------------------- use strict; use Getopt::Long; #============================================================================ # Constants #---------------------------------------------------------------------------- my $kLogging = "-quiet"; my $Rez = "/Developer/Tools/Rez"; my $SetFile = "/Developer/Tools/SetFile"; my $kManPage = <$theFile") or die "Can't open $theFile for writing: $!\n"; print OUTPUT $theScript; close(OUTPUT); # And execute it system("osascript", $theFile); unlink($theFile); } #============================================================================ # isVolume : Is a path to the root of a volume? #---------------------------------------------------------------------------- sub isVolume { # Retrieve our parameters my ($thePath) = @_; # Check the state # # After stripping out the leading /Volumes, any further slashes # indicate we have a folder rather than a volume. $thePath =~ s/\/Volumes\///; my $isVolume = ($thePath =~ /.*\/.*/) ? 0 : 1; return($isVolume); } #============================================================================ # setFolderState : Set the state for a folder. #---------------------------------------------------------------------------- sub setFolderState { # Retrieve our parameters my ($thePath, $iconSize, $flagToolbar, $bgImage, $bgColor) = @_; # Initialise ourselves my $cmdBackground = ""; my $cmdIconSize = ""; my $cmdToolbar = ""; # Prepare the background # # As of 10.5, the Finder refuses to manipulate files whose names start with # a period (rdar://5582578). As such we need to use an underscore for the # image, then make it invisible using SetFile. if (-f $bgImage) { $bgImage =~ /.*\.(\w+)/; my $dstImage = "$thePath/_Background.$1"; `cp "$bgImage" "$dstImage"`; $cmdBackground .= "set theImage to posix file \"$dstImage\"\n"; $cmdBackground .= " set background picture of theOptions to theImage\n"; $cmdBackground .= " do shell script \"/Developer/Tools/SetFile -a V '$dstImage'\"\n"; } elsif ($bgColor ne "") { $cmdBackground = "set background color of theOptions to {$bgColor} as RGB color"; } # Prepare the icon size if ($iconSize != 0) { $cmdIconSize = "set icon size of theOptions to $iconSize"; } # Prepare the toolbar # # The window must be made visible in order to change the toolbar state. if ($flagToolbar ne "") { $cmdToolbar = " open theWindow \n"; $cmdToolbar .= " set toolbar visible of theWindow to $flagToolbar \n"; $cmdToolbar .= " close theWindow \n"; } # Identify the target # # AppleScript requires the correct nomenclature for the target item. my $theTarget = "folder \"$thePath\""; if (isVolume($thePath)) { $theTarget =~ s/folder "\/Volumes\//disk "/; } # Set the folder state # # Once a change has been made, it must be flushed to disk with update. my $theScript = ""; $theScript .= "tell application \"Finder\"\n"; $theScript .= " set theTarget to $theTarget \n"; $theScript .= " set theWindow to window of theTarget \n"; $theScript .= "\n"; $theScript .= " set current view of theWindow to icon view \n"; $theScript .= " set theOptions to icon view options of theWindow \n"; $theScript .= " set arrangement of theOptions to not arranged \n"; $theScript .= "\n"; $theScript .= " $cmdBackground\n"; $theScript .= " $cmdIconSize \n"; $theScript .= " $cmdToolbar \n"; $theScript .= "\n"; $theScript .= " update theTarget\n"; $theScript .= "end tell"; appleScript($theScript); } #============================================================================ # setCustomIcon : Set a custom icon. #---------------------------------------------------------------------------- sub setCustomIcon { # Retrieve our parameters my ($thePath, $theIcon) = @_; # Validate our state # # We require several tools inside /Developer/Tools. die("Setting an icon requires $Rez") if (! -e $Rez); die("Setting an icon requires $SetFile") if (! -e $SetFile); # Prepare the flags # # Prior to Mac OS X 10.4, SetFile can only set an attribute if it is # first cleared (rdar://3738867). my $sysVers = `uname -r`; my $setHidden = ($sysVers =~ /^[0-7]\./) ? "vV" : "V"; my $setIcon = ($sysVers =~ /^[0-7]\./) ? "cC" : "C"; # Set a volume icon # # Volume custom icons are contained in a .VolumeIcon.icns file. if (isVolume($thePath)) { my $iconFile = "$thePath/.VolumeIcon.icns"; `cp "$theIcon" "$iconFile"`; `$SetFile -a $setHidden "$iconFile"`; `$SetFile -a $setIcon "$thePath"`; } # Set a folder icon # # Folder custom icons are contained in an ('icns', -16455) resource, # placed in an invisible "Icon\r" file inside the folder. elsif (-d $thePath) { my $iconFile = "$thePath/Icon\r"; my $tmpR = "/tmp/dmgutil.r"; `echo "read 'icns' (-16455) \\"$theIcon\\";\n" > $tmpR`; `cd /tmp; $Rez dmgutil.r -append -o "$iconFile"`; `$SetFile -a $setHidden "$iconFile"`; `$SetFile -a $setIcon "$thePath"`; unlink($tmpR); } # Set a file icon # # File custom icons are contained in an ('icns', -16455) resource. else { my $tmpR = "/tmp/dmgutil.r"; `echo "read 'icns' (-16455) \\"$theIcon\\";\n" > $tmpR`; `cd /tmp; $Rez dmgutil.r -append -o "$thePath"`; `$SetFile -a $setIcon "$thePath"`; unlink($tmpR); } } #============================================================================ # setWindowPos : Set the position of a window. #---------------------------------------------------------------------------- sub setWindowPos { # Retrieve our parameters my ($thePath, $posX, $posY, $theWidth, $theHeight) = @_; # Initialise ourselves my $bottom = $posY + $theHeight; my $right = $posX + $theWidth; # Identify the target # # AppleScript requires the correct nomenclature for the target item. my $theTarget = "folder \"$thePath\""; if (isVolume($thePath)) { $theTarget =~ s/folder "\/Volumes\//disk "/; } # Set the window position # # In theory, the "set bounds" command is all that should be necessary # to set the bounds of a Finder window. # # Unfortunately, under 10.4 this will result in a window that will be # taller than the specified size when the window is next opened. # # To reliably set the bounds of a window we must open the window, show # the status bar, and set the window bounds to be 20 pixels taller (the # height of the status bar) than necessary. # # The status bar can then be hidden, the window closed, and the bounds # bounds will be the desired size when the window is next opened. my $theScript = ""; $theScript .= "tell application \"Finder\"\n"; $theScript .= " set theTarget to $theTarget \n"; $theScript .= " set theWindow to window of theTarget \n"; $theScript .= "\n"; $theScript .= " open theWindow \n"; $theScript .= " set statusbar visible of theWindow to true \n"; $theScript .= " set bounds of theWindow to {$posX, $posY, $right, $bottom+20} \n"; $theScript .= " set statusbar visible of theWindow to false \n"; $theScript .= " close theWindow \n"; $theScript .= "\n"; $theScript .= "end tell"; appleScript($theScript); } #============================================================================ # setIconPos : Set the position of an icon. #---------------------------------------------------------------------------- sub setIconPos { # Retrieve our parameters my ($theFile, $posX, $posY) = @_; # Identify the target # # Since the 'posix file' command follows symlinks, in order to set the # position of a symlink (vs its target) we need to use an HFS path and # reference it as a file rather than an alias. my $theTarget = "alias (posix file \"$theFile\")"; if (-l $theFile) { $theTarget = $theFile; $theTarget =~ s/\/Volumes\///; $theTarget =~ s/\//:/g; $theTarget = "file \"$theTarget\""; } # Set the icon position # # Once a change has been made, it must be flushed to disk with update. my $theScript = ""; $theScript .= "tell application \"Finder\"\n"; $theScript .= " set theTarget to $theTarget \n"; $theScript .= "\n"; $theScript .= " set position of theTarget to {$posX, $posY} \n"; $theScript .= " update theTarget\n"; $theScript .= "end tell"; appleScript($theScript); } #============================================================================ # doOpen : Open a new disk image. #---------------------------------------------------------------------------- sub doOpen { # Retrieve our parameters my ($dmgFile, $volName) = @_; # Clean up any previous image system("rm", "-f", "$dmgFile.sparseimage"); system("rm", "-f", "$dmgFile"); # Create the image # # A large sparse disk image is created, which will be shrunk down # and compressed when the disk image is finally closed. print " creating $dmgFile\n" if ($kLogging eq "-quiet"); system("hdiutil", "create", $dmgFile, "-volname", $volName, "-megabytes", "1000", "-type", "SPARSE", "-fs", "HFS+", $kLogging); system("hdiutil", "mount", $kLogging, "$dmgFile.sparseimage"); } #============================================================================ # doClose : Close a disk image. #---------------------------------------------------------------------------- sub doClose { # Retrieve our parameters my ($dmgFile, $volName, $theLicense) = @_; # Bless the volume # # Blessing the volume ensures that the volume always opens in the current # view, overriding the user's "Open new windows in column view" preference. system("bless", "--openfolder", "/Volumes/$volName"); # Compress the image # # On 10.5, the disk image must be ejected rather than unmounted to allow # it to be converted from a sparse image to a compressed image. print " compressing $dmgFile\n" if ($kLogging eq "-quiet"); system("hdiutil", "eject", $kLogging, "/Volumes/$volName"); if ($theLicense ne "") { system("hdiutil", "unflatten", $kLogging, "$dmgFile.sparseimage"); `$Rez -a "$theLicense" -o "$dmgFile.sparseimage"`; system("hdiutil", "flatten", $kLogging, "$dmgFile.sparseimage"); } system("hdiutil", "convert", "$dmgFile.sparseimage", "-format", "UDZO", "-o", $dmgFile, "-imagekey", "zlib-level=9", $kLogging); # Clean up system("rm", "-f", "$dmgFile.sparseimage"); } #============================================================================ # doSet : Set a file/folder state. #---------------------------------------------------------------------------- sub doSet { # Retrieve our parameters my ($thePath, $posX, $posY, $theWidth, $theHeight, $iconSize, $theIcon, $bgImage, $bgColor, $flagToolbar) = @_; # Set the custom icon if ($theIcon ne "") { setCustomIcon($thePath, $theIcon); } # Set the folder state if ($iconSize != 0 || $bgImage ne "" || $bgColor ne "" || $flagToolbar ne "") { setFolderState($thePath, $iconSize, $flagToolbar, $bgImage, $bgColor); } # Set the position # # Window position must be set after applying the folder state. if ($posX != 0 && $posY != 0) { if ($theWidth != 0 && $theHeight != 0) { setWindowPos($thePath, $posX, $posY, $theWidth, $theHeight); } else { setIconPos($thePath, $posX, $posY); } } } #============================================================================ # dmgUtil : Manipulate a disk image. #---------------------------------------------------------------------------- sub dmgUtil { # Retrieve our parameters my ($doOpen, $doClose, $doSet) = (0, 0, 0); my ($posX, $posY, $theWidth, $theHeight, $iconSize) = (0, 0, 0, 0, 0); my ($volName, $theIcon, $bgImage, $bgColor, $flagToolbar, $theLicense) = ("", "", "", "", "", ""); GetOptions( "--open+", => \$doOpen, "--close+", => \$doClose, "--set+", => \$doSet, "--volume=s", => \$volName, "--x=i", => \$posX, "--y=i", => \$posY, "--width=i", => \$theWidth, "--height=i", => \$theHeight, "--iconsize=i", => \$iconSize, "--icon=s", => \$theIcon, "--background=s", => \$bgImage, "--bgcol=s", => \$bgColor, "--toolbar=s", => \$flagToolbar, "--license=s", => \$theLicense); my ($thePath) = @ARGV; $thePath = "" if (!defined($thePath)); # Perform the action if ($doOpen && $thePath ne "" && $volName ne "") { doOpen($thePath, $volName); } elsif ($doClose && $thePath ne "" && $volName ne "") { doClose($thePath, $volName, $theLicense); } elsif ($doSet && $thePath ne "") { doSet($thePath, $posX, $posY, $theWidth, $theHeight, $iconSize, $theIcon, $bgImage, $bgColor, $flagToolbar); } else { print $kManPage; } } #============================================================================ # Script entry point #---------------------------------------------------------------------------- dmgUtil();