#!/usr/bin/perl -w
#============================================================================
# NAME:
# dmgutil.pl
#
# DESCRIPTION:
# Disk image creation utility.
#
# COPYRIGHT:
# Copyright (c) 2006-2008, 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) = @_;
# 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");
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) = ("", "", "", "", "");
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);
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);
}
elsif ($doSet && $thePath ne "")
{
doSet($thePath, $posX, $posY, $theWidth, $theHeight, $iconSize, $theIcon, $bgImage, $bgColor, $flagToolbar);
}
else
{
print $kManPage;
}
}
#============================================================================
# Script entry point
#----------------------------------------------------------------------------
dmgUtil();