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