diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bffd6b --- /dev/null +++ b/README.md @@ -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. diff --git a/airprint-ppd.zsh b/airprint-ppd.zsh new file mode 100755 index 0000000..f813882 --- /dev/null +++ b/airprint-ppd.zsh @@ -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: choules@wycomco.de +# 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 <&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}/icon_16x16@2x.png" + cp "${IMAGESET_DIR}/icon_16x16@2x.png" "${IMAGESET_DIR}/icon_32x32.png" + ;; + 64) + mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/icon_32x32@2x.png" + cp "${IMAGESET_DIR}/icon_32x32@2x.png" "${IMAGESET_DIR}/icon_64x64.png" + ;; + 128) + mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/icon_64x64@2x.png" + cp "${IMAGESET_DIR}/icon_64x64@2x.png" "${IMAGESET_DIR}/icon_128x128.png" + ;; + 256) + mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/icon_128x128@2x.png" + cp "${IMAGESET_DIR}/icon_128x128@2x.png" "${IMAGESET_DIR}/icon_256x256.png" + ;; + 512) + mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/icon_256x256@2x.png" + cp "${IMAGESET_DIR}/icon_256x256@2x.png" "${IMAGESET_DIR}/icon_512x512.png" + ;; + 1024) + mv "${ICON_DIR}/${IMAGE_FILE_NAME}" "${IMAGESET_DIR}/icon_512x512@2x.png" + ;; + *) + 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 \ No newline at end of file