README: Options screenshot, captions, describe only what exists

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kfattic
2026-09-16 13:30:09 -05:00
parent e0d48e49e8
commit d81de4a531
4 changed files with 55 additions and 36 deletions

View File

@@ -1,12 +1,16 @@
# CUPS Admin for macOS # CUPS Admin for macOS
![CUPS Admin showing the jobs on a printer](docs/screenshots/app-jobs.png) ![CUPS Admin showing the jobs on a printer](docs/screenshots/app-jobs.png)
*Jobs: hold, release, move or cancel jobs on any queue.*
![CUPS Admin showing a Ricoh queue's options](docs/screenshots/app-options.png)
*Options: the queue's driver settings as a form, applied with `lpadmin` and read back.*
**CUPS Admin** is a native macOS app that puts back the CUPS web interface (`http://localhost:631`) Apple removed in macOS 27 — `cupsd`, `lpadmin` and IPP Everywhere queues all still work, only the browser admin page is gone. The sidebar lists every queue with its state and active jobs; select one to see its status, hold, release, move or cancel its jobs, and change its default options the way the old "Set Default Options" page did, including per-driver settings like Ricoh user codes. Common changes (color or black & white, duplex, Letter paper, a user code, the default printer) are one-click Quick Actions, and every change is read back from cupsd so nothing fails silently. It ships with `cupsadmin`, a command-line tool for scripts, Mosyle/Jamf/Munki commands and quick checks. No third-party dependencies; builds with the Xcode Command Line Tools alone. **CUPS Admin** is a native macOS app that puts back the CUPS web interface (`http://localhost:631`) Apple removed in macOS 27 — `cupsd`, `lpadmin` and IPP Everywhere queues all still work, only the browser admin page is gone. The sidebar lists every queue with its state and active jobs; select one to see its status, hold, release, move or cancel its jobs, and change its default options the way the old "Set Default Options" page did, including per-driver settings like Ricoh user codes. Common changes (color or black & white, duplex, Letter paper, a user code, the default printer) are one-click Quick Actions, and every change is read back from cupsd so nothing fails silently. It ships with `cupsadmin`, a command-line tool for scripts, Mosyle/Jamf/Munki commands and quick checks. No third-party dependencies; builds with the Xcode Command Line Tools alone.
## Install ## Install
> Status: the CLI is stable. The app (1.1) covers printers, jobs, queue options and quick actions; printer discovery, classes, the server page and the Attributes/Log tabs are still to come — see [Releases](../../releases). > Status: The CLI and app are both usable today; the app doesn't yet cover every page of the old web interface.
Download the signed, notarized PKG from the latest [release](../../releases). It installs: Download the signed, notarized PKG from the latest [release](../../releases). It installs:
@@ -60,7 +64,7 @@ Which keywords an action writes comes from a driver profile (`Sources/CupsKit/Re
| Ricoh M C251FW PS | ✓ color, black & white, duplex, Letter (no user code) | | Ricoh M C251FW PS | ✓ color, black & white, duplex, Letter (no user code) |
| Ricoh PCL (e.g. SP 3710DN) | ✓ duplex, Letter (generic) | | Ricoh PCL (e.g. SP 3710DN) | ✓ duplex, Letter (generic) |
| Any other PPD, IPP Everywhere / AirPrint | ✓ color, black & white, duplex, Letter where the queue supports them (generic) | | Any other PPD, IPP Everywhere / AirPrint | ✓ color, black & white, duplex, Letter where the queue supports them (generic) |
| Canon, HP, Xerox, Konica Minolta user/department codes | not yet — contribute a profile | | Canon, HP, Xerox, Konica Minolta user/department codes | no built-in profile — add one (below) |
### Adding your printer's driver ### Adding your printer's driver

View File

@@ -3,8 +3,9 @@
# (postinstall installs /usr/local/bin/cupsadmin and /Applications/CUPS Admin.app) -> notarize -> staple -> spctl. # (postinstall installs /usr/local/bin/cupsadmin and /Applications/CUPS Admin.app) -> notarize -> staple -> spctl.
# Usage: ./build.sh full pipeline (VERSION=1.2.0 ./build.sh to set the version) # Usage: ./build.sh full pipeline (VERSION=1.2.0 ./build.sh to set the version)
# ./build.sh --build-only build CLI and app, ad-hoc signed; no identities, no pkg, no notarization # ./build.sh --build-only build CLI and app, ad-hoc signed; no identities, no pkg, no notarization
# ./build.sh --screenshots build like --build-only, then regenerate docs/screenshots/app-jobs.png # ./build.sh --screenshots build like --build-only, then regenerate docs/screenshots/app-jobs.png and
# (creates three demo queues on 127.0.0.1 with held jobs, deletes them after) # app-options.png (creates four demo queues on 127.0.0.1 — one on Ricoh's
# IM C4500 PPD — with held jobs, and deletes them after)
# #
# Signing settings come from the environment, or from an untracked build.env next to this script # Signing settings come from the environment, or from an untracked build.env next to this script
# (plain KEY=value lines; a variable already set in the environment wins): # (plain KEY=value lines; a variable already set in the environment wins):
@@ -43,8 +44,10 @@ step() { STATUS="ERROR: failed at: $1"; echo; echo "==> $1"; }
# --- screenshots (--screenshots) ------------------------------------------------ # --- screenshots (--screenshots) ------------------------------------------------
# Demo queues with generic names so the README never shows real queue names or users. # Demo queues with generic names so the README never shows real queue names or users.
DEMO_QUEUES="Front_Office Library_Color Lab_Mono" DEMO_QUEUES="Copy_Room Front_Office Library_Color Lab_Mono"
SHOT="docs/screenshots/app-jobs.png" SHOT_JOBS="docs/screenshots/app-jobs.png"
SHOT_OPTIONS="docs/screenshots/app-options.png"
RICOH_DEMO_PPD="/Library/Printers/PPDs/Contents/Resources/RICOH IM C4500"
APP_DOMAIN="edu.wku.cupsadmin.app" APP_DOMAIN="edu.wku.cupsadmin.app"
SAVED_PREFS="" SAVED_PREFS=""
@@ -60,21 +63,57 @@ cleanup_screenshots() {
fi fi
} }
# capture_app <queue> <tab> <file>: launch the app on one queue and tab, capture its window.
capture_app() {
local queue="$1" tab="$2" file="$3" window="" finder
osascript -e 'quit app "CUPS Admin"' >/dev/null 2>&1 || true
for _ in 1 2 3 4 5; do pgrep -f "CUPS Admin.app/Contents/MacOS" >/dev/null || break; sleep 1; done
defaults write "$APP_DOMAIN" selectedQueue "$queue"
defaults write "$APP_DOMAIN" detailTab "$tab"
defaults write "$APP_DOMAIN" "NSWindow Frame main" "120 120 1400 860 $SCREEN "
open "$APP" --args --only-queues "${DEMO_QUEUES// /,}"
finder=$(mktemp -t cupsadmin-window).swift
cat > "$finder" <<'SWIFT'
import CoreGraphics
let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] ?? []
if let w = windows.first(where: { ($0[kCGWindowOwnerName as String] as? String) == "CUPS Admin" && ($0[kCGWindowLayer as String] as? Int) == 0 }),
let id = w[kCGWindowNumber as String] as? Int { print(id) }
SWIFT
for _ in $(seq 1 30); do window=$(swift "$finder" 2>/dev/null || true); [ -n "$window" ] && break; sleep 1; done
rm -f "$finder"
[ -n "$window" ] || { STATUS="ERROR: CUPS Admin window didn't appear on this Space"; exit 1; }
sleep 3 # let the sidebar, header and tab content load
# Capture it as the active window (colored traffic lights, accent-colored selection).
osascript -e "tell application id \"$APP_DOMAIN\" to activate" >/dev/null 2>&1 || true
open "$APP"
sleep 2
mkdir -p "$(dirname "$file")"
screencapture -x -o -l "$window" "$file" \
|| { STATUS="ERROR: screencapture failed (Terminal needs Screen Recording permission)"; exit 1; }
echo "captured $file ($(sips -g pixelWidth -g pixelHeight "$file" | awk '/pixel/ {print $2}' | paste -sd x -))"
}
take_screenshots() { take_screenshots() {
step "screenshots" step "screenshots"
[ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" != "Dark" ] \ [ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" != "Dark" ] \
|| { STATUS="ERROR: switch macOS to Light appearance first (the README screenshot is light mode)"; exit 1; } || { STATUS="ERROR: switch macOS to Light appearance first (the README screenshots are light mode)"; exit 1; }
[ -f "$RICOH_DEMO_PPD" ] \
|| { STATUS="ERROR: the Options screenshot needs Ricoh's driver ($RICOH_DEMO_PPD not found)"; exit 1; }
cleanup_screenshots cleanup_screenshots
# Save the user's app settings (window frame, selection, tab); restored in cleanup. # Save the user's app settings (window frame, selection, tab); restored in cleanup.
SAVED_PREFS=$(mktemp -t cupsadmin-prefs).plist SAVED_PREFS=$(mktemp -t cupsadmin-prefs).plist
defaults export "$APP_DOMAIN" "$SAVED_PREFS" 2>/dev/null || defaults export "$APP_DOMAIN" - >/dev/null 2>&1 || true defaults export "$APP_DOMAIN" "$SAVED_PREFS" 2>/dev/null || true
[ -s "$SAVED_PREFS" ] || printf '{}' | plutil -convert xml1 -o "$SAVED_PREFS" - [ -s "$SAVED_PREFS" ] || printf '{}' | plutil -convert xml1 -o "$SAVED_PREFS" -
for queue in $DEMO_QUEUES; do for queue in Front_Office Library_Color Lab_Mono; do
lpadmin -p "$queue" -v "lpd://127.0.0.1/$queue" -m drv:///sample.drv/generic.ppd \ lpadmin -p "$queue" -v "lpd://127.0.0.1/$queue" -m drv:///sample.drv/generic.ppd \
-D "${queue//_/ }" -L "Building A" -o printer-is-shared=false -E 2>&1 | grep -v deprecated || true -D "${queue//_/ }" -L "Building A" -o printer-is-shared=false -E 2>&1 | grep -v deprecated || true
done done
# A Ricoh queue for the Options tab, set to Letter + fit to nearest size (what a US site uses).
lpadmin -p Copy_Room -v lpd://127.0.0.1/Copy_Room -P "$RICOH_DEMO_PPD" -D "Copy Room" -L "Building A" \
-o printer-is-shared=false -o PageSize=Letter -o RIPaperPolicy=NearestSizeAdjust -E 2>&1 | grep -v deprecated || true
# Held jobs never leave the Mac; owners are generic names, not the person running the build. # Held jobs never leave the Mac; owners are generic names, not the person running the build.
lp -d Front_Office -U alex -H hold -t "Quarterly budget.pdf" /etc/hosts >/dev/null lp -d Front_Office -U alex -H hold -t "Quarterly budget.pdf" /etc/hosts >/dev/null
lp -d Front_Office -U jordan -H hold -t "Staff meeting agenda" /etc/hosts >/dev/null lp -d Front_Office -U jordan -H hold -t "Staff meeting agenda" /etc/hosts >/dev/null
@@ -83,32 +122,8 @@ take_screenshots() {
lp -d Library_Color -U jordan -H hold -t "Event poster" /etc/hosts >/dev/null lp -d Library_Color -U jordan -H hold -t "Event poster" /etc/hosts >/dev/null
SCREEN=$(osascript -e 'tell application "Finder" to get bounds of window of desktop' | tr -d ' ' | tr ',' ' ') SCREEN=$(osascript -e 'tell application "Finder" to get bounds of window of desktop' | tr -d ' ' | tr ',' ' ')
defaults write "$APP_DOMAIN" selectedQueue Front_Office capture_app Front_Office jobs "$SHOT_JOBS"
defaults write "$APP_DOMAIN" detailTab jobs capture_app Copy_Room options "$SHOT_OPTIONS"
defaults write "$APP_DOMAIN" "NSWindow Frame main" "120 120 1400 860 $SCREEN "
open "$APP" --args --only-queues "${DEMO_QUEUES// /,}"
FINDER=$(mktemp -t cupsadmin-window).swift
cat > "$FINDER" <<'SWIFT'
import CoreGraphics
let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] ?? []
if let w = windows.first(where: { ($0[kCGWindowOwnerName as String] as? String) == "CUPS Admin" && ($0[kCGWindowLayer as String] as? Int) == 0 }),
let id = w[kCGWindowNumber as String] as? Int { print(id) }
SWIFT
WINDOW=""
for _ in $(seq 1 30); do WINDOW=$(swift "$FINDER" 2>/dev/null || true); [ -n "$WINDOW" ] && break; sleep 1; done
rm -f "$FINDER"
[ -n "$WINDOW" ] || { STATUS="ERROR: CUPS Admin window didn't appear on this Space"; exit 1; }
sleep 3 # let the sidebar, header and jobs load
# Capture it as the active window (colored traffic lights, accent-colored selection).
osascript -e "tell application id \"$APP_DOMAIN\" to activate" >/dev/null 2>&1 || true
open "$APP"
sleep 2
mkdir -p "$(dirname "$SHOT")"
screencapture -x -o -l "$WINDOW" "$SHOT" \
|| { STATUS="ERROR: screencapture failed (Terminal needs Screen Recording permission)"; exit 1; }
SHOT_SIZE="$(sips -g pixelWidth -g pixelHeight "$SHOT" | awk '/pixel/ {print $2}' | paste -sd x -)"
echo "captured $SHOT ($SHOT_SIZE)"
} }
# --- configuration ------------------------------------------------------------- # --- configuration -------------------------------------------------------------
@@ -259,7 +274,7 @@ echo "app architectures: $(lipo -archs "$APP/Contents/MacOS/$APP_EXECUTABLE")"
if [ $SCREENSHOTS -eq 1 ]; then if [ $SCREENSHOTS -eq 1 ]; then
take_screenshots take_screenshots
STATUS="OK: regenerated $SHOT (${SHOT_SIZE})" STATUS="OK: regenerated $SHOT_JOBS and $SHOT_OPTIONS"
exit 0 exit 0
fi fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB