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
26package sun.print;
27
28import java.net.URL;
29import java.net.HttpURLConnection;
30import java.io.OutputStream;
31import java.io.InputStream;
32import java.util.ArrayList;
33import java.util.HashMap;
34import sun.print.IPPPrintService;
35import sun.print.CustomMediaSizeName;
36import sun.print.CustomMediaTray;
37import javax.print.attribute.standard.Media;
38import javax.print.attribute.standard.MediaSizeName;
39import javax.print.attribute.standard.MediaSize;
40import javax.print.attribute.standard.MediaTray;
41import javax.print.attribute.standard.MediaPrintableArea;
42import javax.print.attribute.standard.PrinterResolution;
43import javax.print.attribute.Size2DSyntax;
44import javax.print.attribute.Attribute;
45import javax.print.attribute.EnumSyntax;
46import javax.print.attribute.standard.PrinterName;
47
48
49public 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}
476