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