Path: blob/master/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java
66645 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.print;2627import java.net.URL;28import java.net.HttpURLConnection;29import java.io.OutputStream;30import java.io.InputStream;31import java.util.ArrayList;32import java.util.HashMap;33import sun.print.IPPPrintService;34import sun.print.CustomMediaSizeName;35import sun.print.CustomMediaTray;36import javax.print.attribute.standard.Media;37import javax.print.attribute.standard.MediaSizeName;38import javax.print.attribute.standard.MediaSize;39import javax.print.attribute.standard.MediaTray;40import javax.print.attribute.standard.MediaPrintableArea;41import javax.print.attribute.standard.PrinterResolution;42import javax.print.attribute.Size2DSyntax;43import javax.print.attribute.Attribute;44import javax.print.attribute.EnumSyntax;45import javax.print.attribute.standard.PrinterName;464748@SuppressWarnings("removal")49public class CUPSPrinter {50private static final String debugPrefix = "CUPSPrinter>> ";51private static final double PRINTER_DPI = 72.0;52private boolean initialized;53private static native String getCupsServer();54private static native int getCupsPort();55private static native String getCupsDefaultPrinter();56private static native String[] getCupsDefaultPrinters();57private static native boolean canConnect(String server, int port);58private static native boolean initIDs();59// These functions need to be synchronized as60// CUPS does not support multi-threading.61private static synchronized native String[] getMedia(String printer);62private static synchronized native float[] getPageSizes(String printer);63private static synchronized native void64getResolutions(String printer, ArrayList<Integer> resolutionList);65//public static boolean useIPPMedia = false; will be used later6667private MediaPrintableArea[] cupsMediaPrintables;68private MediaSizeName[] cupsMediaSNames;69private CustomMediaSizeName[] cupsCustomMediaSNames;70private MediaTray[] cupsMediaTrays;7172public int nPageSizes = 0;73public int nTrays = 0;74private String[] media;75private float[] pageSizes;76int[] resolutionsArray;77private String printer;7879private static boolean libFound;80private static String cupsServer = null;81private static String domainSocketPathname = null;82private static int cupsPort = 0;8384static {85// load awt library to access native code86java.security.AccessController.doPrivileged(87new java.security.PrivilegedAction<Void>() {88public Void run() {89System.loadLibrary("awt");90return null;91}92});93libFound = initIDs();94if (libFound) {95cupsServer = getCupsServer();96// Is this a local domain socket pathname?97if (cupsServer != null && cupsServer.startsWith("/")) {98if (isSandboxedApp()) {99domainSocketPathname = cupsServer;100}101cupsServer = "localhost";102}103cupsPort = getCupsPort();104}105}106107108CUPSPrinter (String printerName) {109if (printerName == null) {110throw new IllegalArgumentException("null printer name");111}112printer = printerName;113cupsMediaSNames = null;114cupsMediaPrintables = null;115cupsMediaTrays = null;116initialized = false;117118if (!libFound) {119throw new RuntimeException("cups lib not found");120} else {121// get page + tray names122media = getMedia(printer);123if (media == null) {124// either PPD file is not found or printer is unknown125throw new RuntimeException("error getting PPD");126}127128// get sizes129pageSizes = getPageSizes(printer);130if (pageSizes != null) {131nPageSizes = pageSizes.length/6;132133nTrays = media.length/2-nPageSizes;134assert (nTrays >= 0);135}136ArrayList<Integer> resolutionList = new ArrayList<>();137getResolutions(printer, resolutionList);138resolutionsArray = new int[resolutionList.size()];139for (int i=0; i < resolutionList.size(); i++) {140resolutionsArray[i] = resolutionList.get(i);141}142}143}144145146/**147* Returns array of MediaSizeNames derived from PPD.148*/149MediaSizeName[] getMediaSizeNames() {150initMedia();151return cupsMediaSNames;152}153154155/**156* Returns array of Custom MediaSizeNames derived from PPD.157*/158CustomMediaSizeName[] getCustomMediaSizeNames() {159initMedia();160return cupsCustomMediaSNames;161}162163public int getDefaultMediaIndex() {164return ((pageSizes.length >1) ? (int)(pageSizes[pageSizes.length -1]) : 0);165}166167/**168* Returns array of MediaPrintableArea derived from PPD.169*/170MediaPrintableArea[] getMediaPrintableArea() {171initMedia();172return cupsMediaPrintables;173}174175/**176* Returns array of MediaTrays derived from PPD.177*/178MediaTray[] getMediaTrays() {179initMedia();180return cupsMediaTrays;181}182183/**184* return the raw packed array of supported printer resolutions.185*/186int[] getRawResolutions() {187return resolutionsArray;188}189190/**191* Initialize media by translating PPD info to PrintService attributes.192*/193private synchronized void initMedia() {194if (initialized) {195return;196} else {197initialized = true;198}199200if (pageSizes == null) {201return;202}203204cupsMediaPrintables = new MediaPrintableArea[nPageSizes];205cupsMediaSNames = new MediaSizeName[nPageSizes];206cupsCustomMediaSNames = new CustomMediaSizeName[nPageSizes];207208CustomMediaSizeName msn;209MediaPrintableArea mpa;210float length, width, x, y, w, h;211212// initialize names and printables213for (int i=0; i<nPageSizes; i++) {214// media width and length215width = (float)(pageSizes[i*6]/PRINTER_DPI);216length = (float)(pageSizes[i*6+1]/PRINTER_DPI);217// media printable area218x = (float)(pageSizes[i*6+2]/PRINTER_DPI);219h = (float)(pageSizes[i*6+3]/PRINTER_DPI);220w = (float)(pageSizes[i*6+4]/PRINTER_DPI);221y = (float)(pageSizes[i*6+5]/PRINTER_DPI);222223msn = new CustomMediaSizeName(media[i*2], media[i*2+1],224width, length);225226// add to list of standard MediaSizeNames227if ((cupsMediaSNames[i] = msn.getStandardMedia()) == null) {228// add custom if no matching standard media229cupsMediaSNames[i] = msn;230231// add this new custom msn to MediaSize array232if ((width > 0.0) && (length > 0.0)) {233try {234new MediaSize(width, length,235Size2DSyntax.INCH, msn);236} catch (IllegalArgumentException e) {237/* PDF printer in Linux for Ledger paper causes238"IllegalArgumentException: X dimension > Y dimension".239We rotate based on IPP spec. */240new MediaSize(length, width, Size2DSyntax.INCH, msn);241}242}243}244245// add to list of custom MediaSizeName246// for internal use of IPPPrintService247cupsCustomMediaSNames[i] = msn;248249mpa = null;250try {251mpa = new MediaPrintableArea(x, y, w, h,252MediaPrintableArea.INCH);253} catch (IllegalArgumentException e) {254if (width > 0 && length > 0) {255mpa = new MediaPrintableArea(0, 0, width, length,256MediaPrintableArea.INCH);257}258}259cupsMediaPrintables[i] = mpa;260}261262// initialize trays263cupsMediaTrays = new MediaTray[nTrays];264265MediaTray mt;266for (int i=0; i<nTrays; i++) {267mt = new CustomMediaTray(media[(nPageSizes+i)*2],268media[(nPageSizes+i)*2+1]);269cupsMediaTrays[i] = mt;270}271272}273274/**275* Get CUPS default printer using IPP.276* Returns 2 values - index 0 is printer name, index 1 is the uri.277*/278static String[] getDefaultPrinter() {279// Try to get user/lpoptions-defined printer name from CUPS280// if not user-set, then go for server default destination281String[] printerInfo = new String[2];282printerInfo[0] = getCupsDefaultPrinter();283284if (printerInfo[0] != null) {285printerInfo[1] = null;286return printerInfo.clone();287}288try {289URL url = new URL("http", getServer(), getPort(), "");290final HttpURLConnection urlConnection =291IPPPrintService.getIPPConnection(url);292293if (urlConnection != null) {294OutputStream os = java.security.AccessController.295doPrivileged(new java.security.PrivilegedAction<OutputStream>() {296public OutputStream run() {297try {298return urlConnection.getOutputStream();299} catch (Exception e) {300IPPPrintService.debug_println(debugPrefix+e);301}302return null;303}304});305306if (os == null) {307return null;308}309310AttributeClass[] attCl = {311AttributeClass.ATTRIBUTES_CHARSET,312AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,313new AttributeClass("requested-attributes",314AttributeClass.TAG_URI,315"printer-uri")316};317318if (IPPPrintService.writeIPPRequest(os,319IPPPrintService.OP_CUPS_GET_DEFAULT,320attCl)) {321322HashMap<String, AttributeClass> defaultMap = null;323324InputStream is = urlConnection.getInputStream();325HashMap<String, AttributeClass>[] responseMap = IPPPrintService.readIPPResponse(326is);327is.close();328329if (responseMap != null && responseMap.length > 0) {330defaultMap = responseMap[0];331} else {332IPPPrintService.debug_println(debugPrefix+333" empty response map for GET_DEFAULT.");334}335336if (defaultMap == null) {337os.close();338urlConnection.disconnect();339340/* CUPS on OS X, as initially configured, considers the341* default printer to be the last one used that's342* presently available. So if no default was343* reported, exec lpstat -d which has all the Apple344* special behaviour for this built in.345*/346if (PrintServiceLookupProvider.isMac()) {347printerInfo[0] = PrintServiceLookupProvider.348getDefaultPrinterNameSysV();349printerInfo[1] = null;350return printerInfo.clone();351} else {352return null;353}354}355356357AttributeClass attribClass = defaultMap.get("printer-name");358359if (attribClass != null) {360printerInfo[0] = attribClass.getStringValue();361attribClass = defaultMap.get("printer-uri-supported");362IPPPrintService.debug_println(debugPrefix+363"printer-uri-supported="+attribClass);364if (attribClass != null) {365printerInfo[1] = attribClass.getStringValue();366} else {367printerInfo[1] = null;368}369os.close();370urlConnection.disconnect();371return printerInfo.clone();372}373}374os.close();375urlConnection.disconnect();376}377} catch (Exception e) {378}379return null;380}381382383/**384* Get list of all CUPS printers using IPP.385*/386static String[] getAllPrinters() {387388if (getDomainSocketPathname() != null) {389String[] printerNames = getCupsDefaultPrinters();390if (printerNames != null && printerNames.length > 0) {391String[] printerURIs = new String[printerNames.length];392for (int i=0; i< printerNames.length; i++) {393printerURIs[i] = String.format("ipp://%s:%d/printers/%s",394getServer(), getPort(), printerNames[i]);395}396return printerURIs;397}398return null;399}400401try {402URL url = new URL("http", getServer(), getPort(), "");403404final HttpURLConnection urlConnection =405IPPPrintService.getIPPConnection(url);406407if (urlConnection != null) {408OutputStream os = java.security.AccessController.409doPrivileged(new java.security.PrivilegedAction<OutputStream>() {410public OutputStream run() {411try {412return urlConnection.getOutputStream();413} catch (Exception e) {414}415return null;416}417});418419if (os == null) {420return null;421}422423AttributeClass[] attCl = {424AttributeClass.ATTRIBUTES_CHARSET,425AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,426new AttributeClass("requested-attributes",427AttributeClass.TAG_KEYWORD,428"printer-uri-supported")429};430431if (IPPPrintService.writeIPPRequest(os,432IPPPrintService.OP_CUPS_GET_PRINTERS, attCl)) {433434InputStream is = urlConnection.getInputStream();435HashMap<String, AttributeClass>[] responseMap =436IPPPrintService.readIPPResponse(is);437438is.close();439os.close();440urlConnection.disconnect();441442if (responseMap == null || responseMap.length == 0) {443return null;444}445446ArrayList<String> printerNames = new ArrayList<>();447for (int i=0; i< responseMap.length; i++) {448AttributeClass attribClass =449responseMap[i].get("printer-uri-supported");450451if (attribClass != null) {452String nameStr = attribClass.getStringValue();453printerNames.add(nameStr);454}455}456return printerNames.toArray(new String[] {});457} else {458os.close();459urlConnection.disconnect();460}461}462463} catch (Exception e) {464}465return null;466467}468469/**470* Returns CUPS server name.471*/472public static String getServer() {473return cupsServer;474}475476/**477* Returns CUPS port number.478*/479public static int getPort() {480return cupsPort;481}482483/**484* Returns CUPS domain socket pathname.485*/486private static String getDomainSocketPathname() {487return domainSocketPathname;488}489490@SuppressWarnings("removal")491private static boolean isSandboxedApp() {492if (PrintServiceLookupProvider.isMac()) {493return java.security.AccessController494.doPrivileged((java.security.PrivilegedAction<Boolean>) () ->495System.getenv("APP_SANDBOX_CONTAINER_ID") != null);496}497return false;498}499500501/**502* Detects if CUPS is running.503*/504public static boolean isCupsRunning() {505IPPPrintService.debug_println(debugPrefix+"libFound "+libFound);506if (libFound) {507String server = getDomainSocketPathname() != null508? getDomainSocketPathname()509: getServer();510IPPPrintService.debug_println(debugPrefix+"CUPS server "+server+511" port "+getPort()+512(getDomainSocketPathname() != null513? " use domain socket pathname"514: ""));515return canConnect(server, getPort());516} else {517return false;518}519}520521522}523524525