1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import java.net.URL;
  29 import java.net.HttpURLConnection;
  30 import java.io.OutputStream;
  31 import java.io.InputStream;
  32 import java.util.ArrayList;
  33 import java.util.HashMap;
  34 import sun.print.IPPPrintService;
  35 import sun.print.CustomMediaSizeName;
  36 import sun.print.CustomMediaTray;
  37 import javax.print.attribute.standard.Media;
  38 import javax.print.attribute.standard.MediaSizeName;
  39 import javax.print.attribute.standard.MediaSize;
  40 import javax.print.attribute.standard.MediaTray;
  41 import javax.print.attribute.standard.MediaPrintableArea;
  42 import javax.print.attribute.standard.PrinterResolution;
  43 import javax.print.attribute.Size2DSyntax;
  44 import javax.print.attribute.Attribute;
  45 import javax.print.attribute.EnumSyntax;
  46 import javax.print.attribute.standard.PrinterName;
  47 
  48 
  49 public class CUPSPrinter  {
  50     private static final String debugPrefix = "CUPSPrinter>> ";
  51     private static final double PRINTER_DPI = 72.0;
  52     private boolean initialized;
  53     private static native String getCupsServer();
  54     private static native int getCupsPort();
  55     private static native boolean canConnect(String server, int port);
  56     private static native boolean initIDs();
  57     // These functions need to be synchronized as
  58     // CUPS does not support multi-threading.
  59     private static synchronized native String[] getMedia(String printer);
  60     private static synchronized native float[] getPageSizes(String printer);
  61     private static synchronized native void
  62         getResolutions(String printer, ArrayList<Integer> resolutionList);
  63     //public static boolean useIPPMedia = false; will be used later
  64 
  65     private MediaPrintableArea[] cupsMediaPrintables;
  66     private MediaSizeName[] cupsMediaSNames;
  67     private CustomMediaSizeName[] cupsCustomMediaSNames;
  68     private MediaTray[] cupsMediaTrays;
  69 
  70     public  int nPageSizes = 0;
  71     public  int nTrays = 0;
  72     private  String[] media;
  73     private  float[] pageSizes;
  74     int[]   resolutionsArray;
  75     private String printer;
  76 
  77     private static boolean libFound;
  78     private static String cupsServer = null;
  79     private static int cupsPort = 0;
  80 
  81     static {
  82         // load awt library to access native code
  83         java.security.AccessController.doPrivileged(
  84             new java.security.PrivilegedAction<Void>() {
  85                 public Void run() {
  86                     System.loadLibrary("awt");
  87                     return null;
  88                 }
  89             });
  90         libFound = initIDs();
  91         if (libFound) {
  92            cupsServer = getCupsServer();
  93            cupsPort = getCupsPort();
  94         }
  95     }
  96 
  97 
  98     CUPSPrinter (String printerName) {
  99         if (printerName == null) {
 100             throw new IllegalArgumentException("null printer name");
 101         }
 102         printer = printerName;
 103         cupsMediaSNames = null;
 104         cupsMediaPrintables = null;
 105         cupsMediaTrays = null;
 106         initialized = false;
 107 
 108         if (!libFound) {
 109             throw new RuntimeException("cups lib not found");
 110         } else {
 111             // get page + tray names
 112             media =  getMedia(printer);
 113             if (media == null) {
 114                 // either PPD file is not found or printer is unknown
 115                 throw new RuntimeException("error getting PPD");
 116             }
 117 
 118             // get sizes
 119             pageSizes = getPageSizes(printer);
 120             if (pageSizes != null) {
 121                 nPageSizes = pageSizes.length/6;
 122 
 123                 nTrays = media.length/2-nPageSizes;
 124                 assert (nTrays >= 0);
 125             }
 126             ArrayList<Integer> resolutionList = new ArrayList<>();
 127             getResolutions(printer, resolutionList);
 128             resolutionsArray = new int[resolutionList.size()];
 129             for (int i=0; i < resolutionList.size(); i++) {
 130                 resolutionsArray[i] = resolutionList.get(i);
 131             }
 132         }
 133     }
 134 
 135 
 136     /**
 137      * Returns array of MediaSizeNames derived from PPD.
 138      */
 139     MediaSizeName[] getMediaSizeNames() {
 140         initMedia();
 141         return cupsMediaSNames;
 142     }
 143 
 144 
 145     /**
 146      * Returns array of Custom MediaSizeNames derived from PPD.
 147      */
 148     CustomMediaSizeName[] getCustomMediaSizeNames() {
 149         initMedia();
 150         return cupsCustomMediaSNames;
 151     }
 152 
 153     public int getDefaultMediaIndex() {
 154         return ((pageSizes.length >1) ? (int)(pageSizes[pageSizes.length -1]) : 0);
 155     }
 156 
 157     /**
 158      * Returns array of MediaPrintableArea derived from PPD.
 159      */
 160     MediaPrintableArea[] getMediaPrintableArea() {
 161         initMedia();
 162         return cupsMediaPrintables;
 163     }
 164 
 165     /**
 166      * Returns array of MediaTrays derived from PPD.
 167      */
 168     MediaTray[] getMediaTrays() {
 169         initMedia();
 170         return cupsMediaTrays;
 171     }
 172 
 173     /**
 174      * return the raw packed array of supported printer resolutions.
 175      */
 176     int[] getRawResolutions() {
 177         return resolutionsArray;
 178     }
 179 
 180     /**
 181      * Initialize media by translating PPD info to PrintService attributes.
 182      */
 183     private synchronized void initMedia() {
 184         if (initialized) {
 185             return;
 186         } else {
 187             initialized = true;
 188         }
 189 
 190         if (pageSizes == null) {
 191             return;
 192         }
 193 
 194         cupsMediaPrintables = new MediaPrintableArea[nPageSizes];
 195         cupsMediaSNames = new MediaSizeName[nPageSizes];
 196         cupsCustomMediaSNames = new CustomMediaSizeName[nPageSizes];
 197 
 198         CustomMediaSizeName msn;
 199         MediaPrintableArea mpa;
 200         float length, width, x, y, w, h;
 201 
 202         // initialize names and printables
 203         for (int i=0; i<nPageSizes; i++) {
 204             // media width and length
 205             width = (float)(pageSizes[i*6]/PRINTER_DPI);
 206             length = (float)(pageSizes[i*6+1]/PRINTER_DPI);
 207             // media printable area
 208             x = (float)(pageSizes[i*6+2]/PRINTER_DPI);
 209             h = (float)(pageSizes[i*6+3]/PRINTER_DPI);
 210             w = (float)(pageSizes[i*6+4]/PRINTER_DPI);
 211             y = (float)(pageSizes[i*6+5]/PRINTER_DPI);
 212 
 213             msn = new CustomMediaSizeName(media[i*2], media[i*2+1],
 214                                           width, length);
 215 
 216             // add to list of standard MediaSizeNames
 217             if ((cupsMediaSNames[i] = msn.getStandardMedia()) == null) {
 218                 // add custom if no matching standard media
 219                 cupsMediaSNames[i] = msn;
 220 
 221                 // add this new custom msn to MediaSize array
 222                 if ((width > 0.0) && (length > 0.0)) {
 223                     try {
 224                     new MediaSize(width, length,
 225                                   Size2DSyntax.INCH, msn);
 226                     } catch (IllegalArgumentException e) {
 227                         /* PDF printer in Linux for Ledger paper causes
 228                         "IllegalArgumentException: X dimension > Y dimension".
 229                         We rotate based on IPP spec. */
 230                         new MediaSize(length, width, Size2DSyntax.INCH, msn);
 231                     }
 232                 }
 233             }
 234 
 235             // add to list of custom MediaSizeName
 236             // for internal use of IPPPrintService
 237             cupsCustomMediaSNames[i] = msn;
 238 
 239             mpa = null;
 240             try {
 241                 mpa = new MediaPrintableArea(x, y, w, h,
 242                                              MediaPrintableArea.INCH);
 243             } catch (IllegalArgumentException e) {
 244                 if (width > 0 && length > 0) {
 245                     mpa = new MediaPrintableArea(0, 0, width, length,
 246                                              MediaPrintableArea.INCH);
 247                 }
 248             }
 249             cupsMediaPrintables[i] = mpa;
 250         }
 251 
 252         // initialize trays
 253         cupsMediaTrays = new MediaTray[nTrays];
 254 
 255         MediaTray mt;
 256         for (int i=0; i<nTrays; i++) {
 257             mt = new CustomMediaTray(media[(nPageSizes+i)*2],
 258                                      media[(nPageSizes+i)*2+1]);
 259             cupsMediaTrays[i] = mt;
 260         }
 261 
 262     }
 263 
 264     /**
 265      * Get CUPS default printer using IPP.
 266      * Returns 2 values - index 0 is printer name, index 1 is the uri.
 267      */
 268     static String[] getDefaultPrinter() {
 269         try {
 270             URL url = new URL("http", getServer(), getPort(), "");
 271             final HttpURLConnection urlConnection =
 272                 IPPPrintService.getIPPConnection(url);
 273 
 274             if (urlConnection != null) {
 275                 OutputStream os = java.security.AccessController.
 276                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
 277                         public OutputStream run() {
 278                             try {
 279                                 return urlConnection.getOutputStream();
 280                             } catch (Exception e) {
 281                                IPPPrintService.debug_println(debugPrefix+e);
 282                             }
 283                             return null;
 284                         }
 285                     });
 286 
 287                 if (os == null) {
 288                     return null;
 289                 }
 290 
 291                 AttributeClass attCl[] = {
 292                     AttributeClass.ATTRIBUTES_CHARSET,
 293                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
 294                     new AttributeClass("requested-attributes",
 295                                        AttributeClass.TAG_URI,
 296                                        "printer-uri")
 297                 };
 298 
 299                 if (IPPPrintService.writeIPPRequest(os,
 300                                         IPPPrintService.OP_CUPS_GET_DEFAULT,
 301                                         attCl)) {
 302 
 303                     HashMap<String, AttributeClass> defaultMap = null;
 304                     String[] printerInfo = new String[2];
 305                     InputStream is = urlConnection.getInputStream();
 306                     HashMap<String, AttributeClass>[] responseMap = IPPPrintService.readIPPResponse(
 307                                          is);
 308                     is.close();
 309 
 310                     if (responseMap != null && responseMap.length > 0) {
 311                         defaultMap = responseMap[0];
 312                     } else {
 313                        IPPPrintService.debug_println(debugPrefix+
 314                            " empty response map for GET_DEFAULT.");
 315                     }
 316 
 317                     if (defaultMap == null) {
 318                         os.close();
 319                         urlConnection.disconnect();
 320 
 321                         /* CUPS on OS X, as initially configured, considers the
 322                          * default printer to be the last one used that's
 323                          * presently available. So if no default was
 324                          * reported, exec lpstat -d which has all the Apple
 325                          * special behaviour for this built in.
 326                          */
 327                          if (PrintServiceLookupProvider.isMac()) {
 328                              printerInfo[0] = PrintServiceLookupProvider.
 329                                                    getDefaultPrinterNameSysV();
 330                              printerInfo[1] = null;
 331                              return printerInfo.clone();
 332                          } else {
 333                              return null;
 334                          }
 335                     }
 336 
 337 
 338                     AttributeClass attribClass = defaultMap.get("printer-name");
 339 
 340                     if (attribClass != null) {
 341                         printerInfo[0] = attribClass.getStringValue();
 342                         attribClass = defaultMap.get("printer-uri-supported");
 343                         IPPPrintService.debug_println(debugPrefix+
 344                           "printer-uri-supported="+attribClass);
 345                         if (attribClass != null) {
 346                             printerInfo[1] = attribClass.getStringValue();
 347                         } else {
 348                             printerInfo[1] = null;
 349                         }
 350                         os.close();
 351                         urlConnection.disconnect();
 352                         return printerInfo.clone();
 353                     }
 354                 }
 355                 os.close();
 356                 urlConnection.disconnect();
 357             }
 358         } catch (Exception e) {
 359         }
 360         return null;
 361     }
 362 
 363 
 364     /**
 365      * Get list of all CUPS printers using IPP.
 366      */
 367     static String[] getAllPrinters() {
 368         try {
 369             URL url = new URL("http", getServer(), getPort(), "");
 370 
 371             final HttpURLConnection urlConnection =
 372                 IPPPrintService.getIPPConnection(url);
 373 
 374             if (urlConnection != null) {
 375                 OutputStream os = java.security.AccessController.
 376                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
 377                         public OutputStream run() {
 378                             try {
 379                                 return urlConnection.getOutputStream();
 380                             } catch (Exception e) {
 381                             }
 382                             return null;
 383                         }
 384                     });
 385 
 386                 if (os == null) {
 387                     return null;
 388                 }
 389 
 390                 AttributeClass attCl[] = {
 391                     AttributeClass.ATTRIBUTES_CHARSET,
 392                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
 393                     new AttributeClass("requested-attributes",
 394                                        AttributeClass.TAG_KEYWORD,
 395                                        "printer-uri-supported")
 396                 };
 397 
 398                 if (IPPPrintService.writeIPPRequest(os,
 399                                 IPPPrintService.OP_CUPS_GET_PRINTERS, attCl)) {
 400 
 401                     InputStream is = urlConnection.getInputStream();
 402                     HashMap<String, AttributeClass>[] responseMap =
 403                         IPPPrintService.readIPPResponse(is);
 404 
 405                     is.close();
 406                     os.close();
 407                     urlConnection.disconnect();
 408 
 409                     if (responseMap == null || responseMap.length == 0) {
 410                         return null;
 411                     }
 412 
 413                     ArrayList<String> printerNames = new ArrayList<>();
 414                     for (int i=0; i< responseMap.length; i++) {
 415                         AttributeClass attribClass =
 416                             responseMap[i].get("printer-uri-supported");
 417 
 418                         if (attribClass != null) {
 419                             String nameStr = attribClass.getStringValue();
 420                             printerNames.add(nameStr);
 421                         }
 422                     }
 423                     return printerNames.toArray(new String[] {});
 424                 } else {
 425                     os.close();
 426                     urlConnection.disconnect();
 427                 }
 428             }
 429 
 430         } catch (Exception e) {
 431         }
 432         return null;
 433 
 434     }
 435 
 436     /**
 437      * Returns CUPS server name.
 438      */
 439     public static String getServer() {
 440         return cupsServer;
 441     }
 442 
 443     /**
 444      * Returns CUPS port number.
 445      */
 446     public static int getPort() {
 447         return cupsPort;
 448     }
 449 
 450     /**
 451      * Detects if CUPS is running.
 452      */
 453     public static boolean isCupsRunning() {
 454         IPPPrintService.debug_println(debugPrefix+"libFound "+libFound);
 455         if (libFound) {
 456             IPPPrintService.debug_println(debugPrefix+"CUPS server "+getServer()+
 457                                           " port "+getPort());
 458             return canConnect(getServer(), getPort());
 459         } else {
 460             return false;
 461         }
 462     }
 463 
 464 
 465 }