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