Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
choules committed Jun 21, 2021
0 parents commit b5dddf0
Show file tree
Hide file tree
Showing 2 changed files with 299 additions and 0 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# airprint-ppd.zsh

This script generates a PPD for AirPrint capable printers. It is based on the work of [Kevin M. Cox](https://www.kevinmcox.com/2020/12/airprint-generator/) and [Apizz](https://aporlebeke.wordpress.com/2019/10/30/configuring-printers-programmatically-for-airprint/) and solves the problem with missing Printer icons when working in root context.

It will fetch all printer icon files from the printer via HTTP and will then try to build an iconset, which will then be converted to a icns file and stored to `/Library/Printers/Icons` with a predefined name.

## Usage

This script queries the given printer url for a PPD and handles the icon generation, so that it may be run as root. A printer icon will be generated and saved to the default location with the expected name containing the printers UUID. You may optionally save a copy of the icons file to a different location.

airprint-ppd.zsh -p printer_url [-i icns_copy_dir] [-o ppd_output_dir] [-s]

### Parameters

* `-p printer_url`: IPP URL, for example ipp://FNCYPRNTR.local or ipp://192.168.1.244:443, required in all cases
* `-i icns_copy_dir`: Output directory to copy the printer icon to, required if not running with root privileges
* `-o ppd_output_dir`: Output dir for PPD, required if not running with root privileges. For root user this defaults to `/Library/Printers/PPDs/Contents/Resources`
* `-s`: Switch to secure mode, which won't ignore untrusted TLS certificates
* `-h`: Show usage message

So, when running this script without root privileges, you are required to specify all three parameters. Running as root enables you to use the default directories for the printer icon and the PPD file.

## System Requirements

This script was tested with macOS 10.15 Catalina and macOS 11 Big Sur. It should work with macOS 10.14 Mojave, but this is not verified.

It uses only default system components and (hopefully) no third party tools.

We have successfully worked with different make and models, but there may be some printers who react unexpectedly to our requests – so please test this script and provide some feedback when you encounter any problems.

## Caveats

Some printers have disabled unencrypted IPP traffic but do not present a trusted TLS certificate. To properly download the needed icon images anyway, we are running the `curl` command with the `insecure` option by default. So please, **query trusted IPP addresses, only**. You may use the `-s` option to force the certificate validation.
266 changes: 266 additions & 0 deletions airprint-ppd.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
#!/bin/zsh
#
# Tool to generate a proper PPD and Icon file for a given AirPrint supported printer
#
# Special props to:
# Kevin M. Cox: https://www.kevinmcox.com/2020/12/airprint-generator/
# Apizz: https://aporlebeke.wordpress.com/2019/10/30/configuring-printers-programmatically-for-airprint/
#
# Author: [email protected]
# Last Update: 2021-06-21
#
##################################################################

ICNS_COPY_DIR=""
PPD_OUTPUT_DIR="/Library/Printers/PPDs/Contents/Resources"
PRINTER_URL=""
SECURE_MODE=false

SCRIPT_NAME=`basename $0`
IPP2PPD="/System/Library/Printers/Libraries/ipp2ppd"
IPPTOOL="/usr/bin/ipptool"
AIRPRINT_PPD="/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/AirPrint.ppd"

usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} -p printer_url [-i icns_copy_dir] [-o ppd_output_dir] [-s]
This script queries the given printer url for a PPD and handles the icon generation, so that it may
be run as root. A printer icon will be generated and saved to the default location with the expected
name containing the printers UUID. You may optionally save a copy of the icons file to a different
location.
-p printer_url IPP URL, for example ipp://FNCYPRINT.local or ipp://192.168.1.244:443
-i icns_copy_dir Output dir for copy of icon, required if not running with root privileges
-o ppd_output_dir Output dir for PPD, required if not running with root privileges.
For root user this defaults to /Library/Printers/PPDs/Contents/Resources
-s Switch to secure mode, which won't ignore untrusted TLS certificates
-h Show this usage message
EOF
}

get_ppd_string() {
cat "$1" | grep -i "$2" | awk -F ": " '{ print $2 }' | tr -d '"'
}

get_ippinfo_string() {
cat "$1" | grep -i "$2" | awk -F " = " '{ print $2 }'
}

while getopts "p:i:o:sh" option
do
case $option in
"p")
PRINTER_URL="$OPTARG"
;;
"i")
ICNS_COPY_DIR="$OPTARG"
;;
"o")
PPD_OUTPUT_DIR="$OPTARG"
;;
"s")
SECURE_MODE=true
;;
"h" | *)
usage
exit 1
;;
esac
done
shift $(($OPTIND - 1))

if [ $# -ne 0 ]; then
usage
exit 1
fi

if [ "${PRINTER_URL}" = "" ]; then
echo "ERROR: No Printer URL given" 1>&2
echo ""
usage
exit 2
fi

case "$PRINTER_URL" in
*/)
;;
*)
PRINTER_URL="${PRINTER_URL}/"
;;
esac

if [ "${ICNS_COPY_DIR}" != "" ] && [ ! -d "${ICNS_COPY_DIR}" ]; then
echo "ERROR: Provided icns_copy_dir not a valid directory" 1>&2
echo ""
usage
exit 3
fi

if [ "${ICNS_COPY_DIR}" = "" ] && [ $EUID -ne 0 ]; then
echo "You have not provided a icns_copy_dir. Since you are not running this command as root"
echo "we will not be able to save the icns file to its default location. Please specify a"
echo "writeable icns_copy_dir."
echo ""
usage
exit 4
fi

if [ "${PPD_OUTPUT_DIR}" != "" ] && [ ! -d "${PPD_OUTPUT_DIR}" ]; then
echo "ERROR: Provided ppd_output_dir not a valid directory" 1>&2
echo ""
usage
exit 5
fi

if [ "${PPD_OUTPUT_DIR}" = "" ] && [ $EUID -ne 0 ]; then
echo "You have not provided a ppd_output_dir. Since you are not running this command as root"
echo "we will not be able to save the ppd file to its default location. Please specify a"
echo "writeable ppd_output_dir."
echo ""
usage
exit 6
fi

echo "Creating a temporary working directory..."
TEMP_DIR=`mktemp -d`

if [ $? -ne 0 ]; then
echo "$0: Can't create temporary directory, exiting..."
exit 500
fi

echo "Created temporary directory at: ${TEMP_DIR}"
PPD_FILE="${TEMP_DIR}/printer.ppd"

echo "Fetching the PPD using ipp2ppd..."
"${IPP2PPD}" "${PRINTER_URL}" "${AIRPRINT_PPD}" > "${PPD_FILE}"

if [ ! -s "${PPD_FILE}" ]; then
echo "ERROR: Fetched PPD is empty..."
exit 404
fi

MODEL_NAME=`get_ppd_string "${PPD_FILE}" "ModelName"`
MANUFACTURER=`get_ppd_string "${PPD_FILE}" "Manufacturer"`
DEFAULT_ICON_PATH=`get_ppd_string "${PPD_FILE}" "APPrinterIconPath"`

IPPINFO_FILE="${TEMP_DIR}/ippinfo"

echo "Fetching the IPP attributes using ipptool..."
"${IPPTOOL}" -tv "${PRINTER_URL}" get-printer-attributes.test > "${IPPINFO_FILE}"

if [ $? -ne 0 ]; then
echo "$0: Can't fetch IPP attributes, exiting..."
exit 1
fi

if [ ! -s "${IPPINFO_FILE}" ]; then
echo "ERROR: Fetched IPP info is empty..."
exit 500
fi

PRINTER_NAME=`get_ippinfo_string "${IPPINFO_FILE}" "printer-name (nameWithoutLanguage) ="`
PRINTER_LOCATION=`get_ippinfo_string "${IPPINFO_FILE}" "printer-location (textWithoutLanguage) = "`

PRINTER_ICONS_STRING=`get_ippinfo_string "${IPPINFO_FILE}" "printer-icons (1setOf uri) ="`
PRINTER_ICONS=(${(s:,:)PRINTER_ICONS_STRING})

ICON_DIR="${TEMP_DIR}/icons"
IMAGESET_DIR="${ICON_DIR}/printer.iconset"
ICNS_TMP_FILE="${ICON_DIR}/printer.icns"

mkdir -p "${IMAGESET_DIR}"

for ICON_URL in $PRINTER_ICONS
do
IMAGE_FILE_NAME=`basename $ICON_URL`

echo "Downloading the printer image file ${IMAGE_FILE_NAME}..."

if [ "$SECURE_MODE" = true ]; then
curl -s -o "${ICON_DIR}/${IMAGE_FILE_NAME}" $ICON_URL
CURL_RESULT=$?
else
curl -s -k -o "${ICON_DIR}/${IMAGE_FILE_NAME}" $ICON_URL
CURL_RESULT=$?
fi

if [ $CURL_RESULT -ne 0 ]; then

if [ $CURL_RESULT -eq 60 ]; then
echo "Error downloading image ${IMAGE_FILE_NAME} due to missing trust info."
echo "Please consider to not use the -s option if you are trusting the url"
echo "${ICON_URL}"
else
echo "Error downloading image ${ICON_URL}."
fi

else
IMAGE_RESOLUTION=`file "${ICON_DIR}/${IMAGE_FILE_NAME}" | awk -F "," '{print $2}' | tr -d ' '`

echo "This image has the following dimensions: $IMAGE_RESOLUTION"
SIZE=`echo ${IMAGE_RESOLUTION} | awk -F "x" '{print $1}'`

echo "Adding this image to the temporary macOS iconset..."
case "$SIZE" in
16)
mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/icon_16x16.png"
;;
32)
mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/[email protected]"
cp "${IMAGESET_DIR}/[email protected]" "${IMAGESET_DIR}/icon_32x32.png"
;;
64)
mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/[email protected]"
cp "${IMAGESET_DIR}/[email protected]" "${IMAGESET_DIR}/icon_64x64.png"
;;
128)
mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/[email protected]"
cp "${IMAGESET_DIR}/[email protected]" "${IMAGESET_DIR}/icon_128x128.png"
;;
256)
mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/[email protected]"
cp "${IMAGESET_DIR}/[email protected]" "${IMAGESET_DIR}/icon_256x256.png"
;;
512)
mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/[email protected]"
cp "${IMAGESET_DIR}/[email protected]" "${IMAGESET_DIR}/icon_512x512.png"
;;
1024)
mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/[email protected]"
;;
*)
echo "This image's dimensions do not fit into the needed iconset structure and will be skipped..."
esac
fi
done

ICON_FILE_COUNT=`ls -1 "${IMAGESET_DIR}" | wc -l | tr -d " "`

if [ $ICON_FILE_COUNT -gt 0 ];
then
echo "The iconset now contains $ICON_FILE_COUNT single images."

echo "Creating an icns file from the fetched printer images..."
iconutil -c icns -o "${ICNS_TMP_FILE}" "${IMAGESET_DIR}"

echo "Saving the icns file to ${DEFAULT_ICON_PATH}..."
cp "${ICNS_TMP_FILE}" "${DEFAULT_ICON_PATH}"

if [ "${ICNS_COPY_DIR}" != "" ]; then
echo "Saving a copy of the icns file to ${ICNS_COPY_DIR}/${MODEL_NAME}.icns..."
cp "${ICNS_TMP_FILE}" "${ICNS_COPY_DIR}/${MODEL_NAME}.icns"
fi
else
echo "The iconset does not contain any image files, so we will skip all other icon handling..."
fi

echo "Saving the PPD to ${PPD_OUTPUT_DIR}/${MODEL_NAME}.ppd..."
cp "${PPD_FILE}" "${PPD_OUTPUT_DIR}/${MODEL_NAME}.ppd"

echo "Removing the temporary directory..."
rm -rf "${TEMP_DIR}"

exit 0

0 comments on commit b5dddf0

Please sign in to comment.