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#include <jni.h>
27#include <jni_util.h>
28#include <jvm_md.h>
29#include <dlfcn.h>
30#include <cups/cups.h>
31#include <cups/ppd.h>
32/*
33 * CUPS #define's __attribute__(x) to be empty unless __GNUC__ is defined.
34 * However OpenJDK officially uses the SunStudio compiler on Solaris.
35 * We need to #undef this else it breaks use of this keyword used by JNIEXPORT.
36 * See: https://github.com/apple/cups/issues/5349
37 */
38#ifdef __SUNPRO_C
39#undef __attribute__
40#endif
41
42
43//#define CUPS_DEBUG
44
45#ifdef CUPS_DEBUG
46#define DPRINTF(x, y) fprintf(stderr, x, y);
47#else
48#define DPRINTF(x, y)
49#endif
50
51typedef const char* (*fn_cupsServer)(void);
52typedef int (*fn_ippPort)(void);
53typedef http_t* (*fn_httpConnect)(const char *, int);
54typedef void (*fn_httpClose)(http_t *);
55typedef char* (*fn_cupsGetPPD)(const char *);
56typedef cups_dest_t* (*fn_cupsGetDest)(const char *name,
57 const char *instance, int num_dests, cups_dest_t *dests);
58typedef int (*fn_cupsGetDests)(cups_dest_t **dests);
59typedef void (*fn_cupsFreeDests)(int num_dests, cups_dest_t *dests);
60typedef ppd_file_t* (*fn_ppdOpenFile)(const char *);
61typedef void (*fn_ppdClose)(ppd_file_t *);
62typedef ppd_option_t* (*fn_ppdFindOption)(ppd_file_t *, const char *);
63typedef ppd_size_t* (*fn_ppdPageSize)(ppd_file_t *, char *);
64
65fn_cupsServer j2d_cupsServer;
66fn_ippPort j2d_ippPort;
67fn_httpConnect j2d_httpConnect;
68fn_httpClose j2d_httpClose;
69fn_cupsGetPPD j2d_cupsGetPPD;
70fn_cupsGetDest j2d_cupsGetDest;
71fn_cupsGetDests j2d_cupsGetDests;
72fn_cupsFreeDests j2d_cupsFreeDests;
73fn_ppdOpenFile j2d_ppdOpenFile;
74fn_ppdClose j2d_ppdClose;
75fn_ppdFindOption j2d_ppdFindOption;
76fn_ppdPageSize j2d_ppdPageSize;
77
78
79/*
80 * Initialize library functions.
81 * // REMIND : move tab , add dlClose before return
82 */
83JNIEXPORT jboolean JNICALL
84Java_sun_print_CUPSPrinter_initIDs(JNIEnv *env,
85 jobject printObj) {
86 void *handle = dlopen(VERSIONED_JNI_LIB_NAME("cups", "2"),
87 RTLD_LAZY | RTLD_GLOBAL);
88
89 if (handle == NULL) {
90 handle = dlopen(JNI_LIB_NAME("cups"), RTLD_LAZY | RTLD_GLOBAL);
91 if (handle == NULL) {
92 return JNI_FALSE;
93 }
94 }
95
96 j2d_cupsServer = (fn_cupsServer)dlsym(handle, "cupsServer");
97 if (j2d_cupsServer == NULL) {
98 dlclose(handle);
99 return JNI_FALSE;
100 }
101
102 j2d_ippPort = (fn_ippPort)dlsym(handle, "ippPort");
103 if (j2d_ippPort == NULL) {
104 dlclose(handle);
105 return JNI_FALSE;
106 }
107
108 j2d_httpConnect = (fn_httpConnect)dlsym(handle, "httpConnect");
109 if (j2d_httpConnect == NULL) {
110 dlclose(handle);
111 return JNI_FALSE;
112 }
113
114 j2d_httpClose = (fn_httpClose)dlsym(handle, "httpClose");
115 if (j2d_httpClose == NULL) {
116 dlclose(handle);
117 return JNI_FALSE;
118 }
119
120 j2d_cupsGetPPD = (fn_cupsGetPPD)dlsym(handle, "cupsGetPPD");
121 if (j2d_cupsGetPPD == NULL) {
122 dlclose(handle);
123 return JNI_FALSE;
124 }
125
126 j2d_cupsGetDest = (fn_cupsGetDest)dlsym(handle, "cupsGetDest");
127 if (j2d_cupsGetDest == NULL) {
128 dlclose(handle);
129 return JNI_FALSE;
130 }
131
132 j2d_cupsGetDests = (fn_cupsGetDests)dlsym(handle, "cupsGetDests");
133 if (j2d_cupsGetDests == NULL) {
134 dlclose(handle);
135 return JNI_FALSE;
136 }
137
138 j2d_cupsFreeDests = (fn_cupsFreeDests)dlsym(handle, "cupsFreeDests");
139 if (j2d_cupsFreeDests == NULL) {
140 dlclose(handle);
141 return JNI_FALSE;
142 }
143
144 j2d_ppdOpenFile = (fn_ppdOpenFile)dlsym(handle, "ppdOpenFile");
145 if (j2d_ppdOpenFile == NULL) {
146 dlclose(handle);
147 return JNI_FALSE;
148
149 }
150
151 j2d_ppdClose = (fn_ppdClose)dlsym(handle, "ppdClose");
152 if (j2d_ppdClose == NULL) {
153 dlclose(handle);
154 return JNI_FALSE;
155
156 }
157
158 j2d_ppdFindOption = (fn_ppdFindOption)dlsym(handle, "ppdFindOption");
159 if (j2d_ppdFindOption == NULL) {
160 dlclose(handle);
161 return JNI_FALSE;
162 }
163
164 j2d_ppdPageSize = (fn_ppdPageSize)dlsym(handle, "ppdPageSize");
165 if (j2d_ppdPageSize == NULL) {
166 dlclose(handle);
167 return JNI_FALSE;
168 }
169
170 return JNI_TRUE;
171}
172
173/*
174 * Gets CUPS server name.
175 *
176 */
177JNIEXPORT jstring JNICALL
178Java_sun_print_CUPSPrinter_getCupsServer(JNIEnv *env,
179 jobject printObj)
180{
181 jstring cServer = NULL;
182 const char* server = j2d_cupsServer();
183 if (server != NULL) {
184 // Is this a local domain socket?
185 if (strncmp(server, "/", 1) == 0) {
186 cServer = JNU_NewStringPlatform(env, "localhost");
187 } else {
188 cServer = JNU_NewStringPlatform(env, server);
189 }
190 }
191 return cServer;
192}
193
194/*
195 * Gets CUPS port name.
196 *
197 */
198JNIEXPORT jint JNICALL
199Java_sun_print_CUPSPrinter_getCupsPort(JNIEnv *env,
200 jobject printObj)
201{
202 int port = j2d_ippPort();
203 return (jint) port;
204}
205
206
207/*
208 * Gets CUPS default printer name.
209 *
210 */
211JNIEXPORT jstring JNICALL
212Java_sun_print_CUPSPrinter_getCupsDefaultPrinter(JNIEnv *env,
213 jobject printObj)
214{
215 jstring cDefPrinter = NULL;
216 cups_dest_t *dests;
217 char *defaultPrinter = NULL;
218 int num_dests = j2d_cupsGetDests(&dests);
219 int i = 0;
220 cups_dest_t *dest = j2d_cupsGetDest(NULL, NULL, num_dests, dests);
221 if (dest != NULL) {
222 defaultPrinter = dest->name;
223 if (defaultPrinter != NULL) {
224 cDefPrinter = JNU_NewStringPlatform(env, defaultPrinter);
225 }
226 }
227 j2d_cupsFreeDests(num_dests, dests);
228 return cDefPrinter;
229}
230
231/*
232 * Checks if connection can be made to the server.
233 *
234 */
235JNIEXPORT jboolean JNICALL
236Java_sun_print_CUPSPrinter_canConnect(JNIEnv *env,
237 jobject printObj,
238 jstring server,
239 jint port)
240{
241 const char *serverName;
242 serverName = (*env)->GetStringUTFChars(env, server, NULL);
243 if (serverName != NULL) {
244 http_t *http = j2d_httpConnect(serverName, (int)port);
245 (*env)->ReleaseStringUTFChars(env, server, serverName);
246 if (http != NULL) {
247 j2d_httpClose(http);
248 return JNI_TRUE;
249 }
250 }
251 return JNI_FALSE;
252}
253
254
255/*
256 * Returns list of media: pages + trays
257 */
258JNIEXPORT jobjectArray JNICALL
259Java_sun_print_CUPSPrinter_getMedia(JNIEnv *env,
260 jobject printObj,
261 jstring printer)
262{
263 ppd_file_t *ppd;
264 ppd_option_t *optionTray, *optionPage;
265 ppd_choice_t *choice;
266 const char *name;
267 const char *filename;
268 int i, nTrays=0, nPages=0, nTotal=0;
269 jstring utf_str;
270 jclass cls;
271 jobjectArray nameArray = NULL;
272
273 name = (*env)->GetStringUTFChars(env, printer, NULL);
274 if (name == NULL) {
275 (*env)->ExceptionClear(env);
276 JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
277 return NULL;
278 }
279
280 // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
281 // unlink() must be caled to remove the file when finished using it.
282 filename = j2d_cupsGetPPD(name);
283 (*env)->ReleaseStringUTFChars(env, printer, name);
284 CHECK_NULL_RETURN(filename, NULL);
285
286 cls = (*env)->FindClass(env, "java/lang/String");
287 CHECK_NULL_RETURN(cls, NULL);
288
289 if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
290 unlink(filename);
291 DPRINTF("CUPSfuncs::unable to open PPD %s\n", filename);
292 return NULL;
293 }
294
295 optionPage = j2d_ppdFindOption(ppd, "PageSize");
296 if (optionPage != NULL) {
297 nPages = optionPage->num_choices;
298 }
299
300 optionTray = j2d_ppdFindOption(ppd, "InputSlot");
301 if (optionTray != NULL) {
302 nTrays = optionTray->num_choices;
303 }
304
305 if ((nTotal = (nPages+nTrays) *2) > 0) {
306 nameArray = (*env)->NewObjectArray(env, nTotal, cls, NULL);
307 if (nameArray == NULL) {
308 unlink(filename);
309 j2d_ppdClose(ppd);
310 DPRINTF("CUPSfuncs::bad alloc new array\n", "")
311 (*env)->ExceptionClear(env);
312 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
313 return NULL;
314 }
315
316 for (i = 0; optionPage!=NULL && i<nPages; i++) {
317 choice = (optionPage->choices)+i;
318 utf_str = JNU_NewStringPlatform(env, choice->text);
319 if (utf_str == NULL) {
320 unlink(filename);
321 j2d_ppdClose(ppd);
322 DPRINTF("CUPSfuncs::bad alloc new string ->text\n", "")
323 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
324 return NULL;
325 }
326 (*env)->SetObjectArrayElement(env, nameArray, i*2, utf_str);
327 (*env)->DeleteLocalRef(env, utf_str);
328 utf_str = JNU_NewStringPlatform(env, choice->choice);
329 if (utf_str == NULL) {
330 unlink(filename);
331 j2d_ppdClose(ppd);
332 DPRINTF("CUPSfuncs::bad alloc new string ->choice\n", "")
333 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
334 return NULL;
335 }
336 (*env)->SetObjectArrayElement(env, nameArray, i*2+1, utf_str);
337 (*env)->DeleteLocalRef(env, utf_str);
338 }
339
340 for (i = 0; optionTray!=NULL && i<nTrays; i++) {
341 choice = (optionTray->choices)+i;
342 utf_str = JNU_NewStringPlatform(env, choice->text);
343 if (utf_str == NULL) {
344 unlink(filename);
345 j2d_ppdClose(ppd);
346 DPRINTF("CUPSfuncs::bad alloc new string text\n", "")
347 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
348 return NULL;
349 }
350 (*env)->SetObjectArrayElement(env, nameArray,
351 (nPages+i)*2, utf_str);
352 (*env)->DeleteLocalRef(env, utf_str);
353 utf_str = JNU_NewStringPlatform(env, choice->choice);
354 if (utf_str == NULL) {
355 unlink(filename);
356 j2d_ppdClose(ppd);
357 DPRINTF("CUPSfuncs::bad alloc new string choice\n", "")
358 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
359 return NULL;
360 }
361 (*env)->SetObjectArrayElement(env, nameArray,
362 (nPages+i)*2+1, utf_str);
363 (*env)->DeleteLocalRef(env, utf_str);
364 }
365 }
366 j2d_ppdClose(ppd);
367 unlink(filename);
368 return nameArray;
369}
370
371
372/*
373 * Returns list of page sizes and imageable area.
374 */
375JNIEXPORT jfloatArray JNICALL
376Java_sun_print_CUPSPrinter_getPageSizes(JNIEnv *env,
377 jobject printObj,
378 jstring printer)
379{
380 ppd_file_t *ppd;
381 ppd_option_t *option;
382 ppd_choice_t *choice;
383 ppd_size_t *size;
384 const char *filename = NULL;
385 int i;
386 jobjectArray sizeArray = NULL;
387 jfloat *dims;
388
389 const char *name = (*env)->GetStringUTFChars(env, printer, NULL);
390 if (name == NULL) {
391 (*env)->ExceptionClear(env);
392 JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
393 return NULL;
394 }
395
396 // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
397 // unlink() must be called to remove the file after using it.
398 filename = j2d_cupsGetPPD(name);
399 (*env)->ReleaseStringUTFChars(env, printer, name);
400 CHECK_NULL_RETURN(filename, NULL);
401 if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
402 unlink(filename);
403 DPRINTF("unable to open PPD %s\n", filename)
404 return NULL;
405 }
406 option = j2d_ppdFindOption(ppd, "PageSize");
407 if (option != NULL && option->num_choices > 0) {
408 // create array of dimensions - (num_choices * 6)
409 //to cover length & height
410 DPRINTF( "CUPSfuncs::option->num_choices %d\n", option->num_choices)
411 // +1 is for storing the default media index
412 sizeArray = (*env)->NewFloatArray(env, option->num_choices*6+1);
413 if (sizeArray == NULL) {
414 unlink(filename);
415 j2d_ppdClose(ppd);
416 DPRINTF("CUPSfuncs::bad alloc new float array\n", "")
417 (*env)->ExceptionClear(env);
418 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
419 return NULL;
420 }
421
422 dims = (*env)->GetFloatArrayElements(env, sizeArray, NULL);
423 if (dims == NULL) {
424 unlink(filename);
425 j2d_ppdClose(ppd);
426 (*env)->ExceptionClear(env);
427 JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
428 return NULL;
429 }
430 for (i = 0; i<option->num_choices; i++) {
431 choice = (option->choices)+i;
432 // get the index of the default page
433 if (!strcmp(choice->choice, option->defchoice)) {
434 dims[option->num_choices*6] = (float)i;
435 }
436 size = j2d_ppdPageSize(ppd, choice->choice);
437 if (size != NULL) {
438 // paper width and height
439 dims[i*6] = size->width;
440 dims[(i*6)+1] = size->length;
441 // paper printable area
442 dims[(i*6)+2] = size->left;
443 dims[(i*6)+3] = size->top;
444 dims[(i*6)+4] = size->right;
445 dims[(i*6)+5] = size->bottom;
446 }
447 }
448
449 (*env)->ReleaseFloatArrayElements(env, sizeArray, dims, 0);
450 }
451
452 j2d_ppdClose(ppd);
453 unlink(filename);
454 return sizeArray;
455}
456
457/*
458 * Populates the supplied ArrayList<Integer> with resolutions.
459 * The first pair of elements will be the default resolution.
460 * If resolution isn't supported the list will be empty.
461 * If needed we can add a 2nd ArrayList<String> which would
462 * be populated with the corresponding UI name.
463 * PPD specifies the syntax for resolution as either "Ndpi" or "MxNdpi",
464 * eg 300dpi or 600x600dpi. The former is a shorthand where xres==yres.
465 * We will always expand to the latter as we use a single array list.
466 * Note: getMedia() and getPageSizes() both open the ppd file
467 * This is not going to scale forever so if we add anymore we
468 * should look to consolidate this.
469 */
470JNIEXPORT void JNICALL
471Java_sun_print_CUPSPrinter_getResolutions(JNIEnv *env,
472 jobject printObj,
473 jstring printer,
474 jobject arrayList)
475{
476 ppd_file_t *ppd = NULL;
477 ppd_option_t *resolution;
478 int defx = 0, defy = 0;
479 int resx = 0, resy = 0;
480 jclass intCls, cls;
481 jmethodID intCtr, arrListAddMID;
482 int i;
483 const char *name = NULL;
484 const char *filename = NULL;
485
486 intCls = (*env)->FindClass(env, "java/lang/Integer");
487 CHECK_NULL(intCls);
488 intCtr = (*env)->GetMethodID(env, intCls, "<init>", "(I)V");
489 CHECK_NULL(intCtr);
490 cls = (*env)->FindClass(env, "java/util/ArrayList");
491 CHECK_NULL(cls);
492 arrListAddMID =
493 (*env)->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
494 CHECK_NULL(arrListAddMID);
495
496 name = (*env)->GetStringUTFChars(env, printer, NULL);
497 if (name == NULL) {
498 (*env)->ExceptionClear(env);
499 JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
500 return;
501 }
502
503
504 // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
505 // unlink() must be called to remove the file after using it.
506 filename = j2d_cupsGetPPD(name);
507 (*env)->ReleaseStringUTFChars(env, printer, name);
508 CHECK_NULL(filename);
509 if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
510 unlink(filename);
511 DPRINTF("unable to open PPD %s\n", filename)
512 }
513 resolution = j2d_ppdFindOption(ppd, "Resolution");
514 if (resolution != NULL) {
515 int matches = sscanf(resolution->defchoice, "%dx%ddpi", &defx, &defy);
516 if (matches == 2) {
517 if (defx <= 0 || defy <= 0) {
518 defx = 0;
519 defy = 0;
520 }
521 } else {
522 matches = sscanf(resolution->defchoice, "%ddpi", &defx);
523 if (matches == 1) {
524 if (defx <= 0) {
525 defx = 0;
526 } else {
527 defy = defx;
528 }
529 }
530 }
531 if (defx > 0) {
532 jobject rxObj, ryObj;
533 rxObj = (*env)->NewObject(env, intCls, intCtr, defx);
534 CHECK_NULL(rxObj);
535 ryObj = (*env)->NewObject(env, intCls, intCtr, defy);
536 CHECK_NULL(ryObj);
537 (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, rxObj);
538 (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, ryObj);
539 }
540
541 for (i = 0; i < resolution->num_choices; i++) {
542 char *resStr = resolution->choices[i].choice;
543 int matches = sscanf(resStr, "%dx%ddpi", &resx, &resy);
544 if (matches == 2) {
545 if (resx <= 0 || resy <= 0) {
546 resx = 0;
547 resy = 0;
548 }
549 } else {
550 matches = sscanf(resStr, "%ddpi", &resx);
551 if (matches == 1) {
552 if (resx <= 0) {
553 resx = 0;
554 } else {
555 resy = resx;
556 }
557 }
558 }
559 if (resx > 0 && (resx != defx || resy != defy )) {
560 jobject rxObj, ryObj;
561 rxObj = (*env)->NewObject(env, intCls, intCtr, resx);
562 CHECK_NULL(rxObj);
563 ryObj = (*env)->NewObject(env, intCls, intCtr, resy);
564 CHECK_NULL(ryObj);
565 (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, rxObj);
566 (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, ryObj);
567 }
568 }
569 }
570
571 j2d_ppdClose(ppd);
572 unlink(filename);
573}
574