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 51 typedef const char* (*fn_cupsServer)(void); 52 typedef int (*fn_ippPort)(void); 53 typedef http_t* (*fn_httpConnect)(const char *, int); 54 typedef void (*fn_httpClose)(http_t *); 55 typedef char* (*fn_cupsGetPPD)(const char *); 56 typedef cups_dest_t* (*fn_cupsGetDest)(const char *name, 57 const char *instance, int num_dests, cups_dest_t *dests); 58 typedef int (*fn_cupsGetDests)(cups_dest_t **dests); 59 typedef void (*fn_cupsFreeDests)(int num_dests, cups_dest_t *dests); 60 typedef ppd_file_t* (*fn_ppdOpenFile)(const char *); 61 typedef void (*fn_ppdClose)(ppd_file_t *); 62 typedef ppd_option_t* (*fn_ppdFindOption)(ppd_file_t *, const char *); 63 typedef ppd_size_t* (*fn_ppdPageSize)(ppd_file_t *, char *); 64 65 fn_cupsServer j2d_cupsServer; 66 fn_ippPort j2d_ippPort; 67 fn_httpConnect j2d_httpConnect; 68 fn_httpClose j2d_httpClose; 69 fn_cupsGetPPD j2d_cupsGetPPD; 70 fn_cupsGetDest j2d_cupsGetDest; 71 fn_cupsGetDests j2d_cupsGetDests; 72 fn_cupsFreeDests j2d_cupsFreeDests; 73 fn_ppdOpenFile j2d_ppdOpenFile; 74 fn_ppdClose j2d_ppdClose; 75 fn_ppdFindOption j2d_ppdFindOption; 76 fn_ppdPageSize j2d_ppdPageSize; 77 78 79 /* 80 * Initialize library functions. 81 * // REMIND : move tab , add dlClose before return 82 */ 83 JNIEXPORT jboolean JNICALL 84 Java_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 */ 177 JNIEXPORT jstring JNICALL 178 Java_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 */ 198 JNIEXPORT jint JNICALL 199 Java_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 */ 211 JNIEXPORT jstring JNICALL 212 Java_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 */ 235 JNIEXPORT jboolean JNICALL 236 Java_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 */ 258 JNIEXPORT jobjectArray JNICALL 259 Java_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 */ 375 JNIEXPORT jfloatArray JNICALL 376 Java_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 */ 470 JNIEXPORT void JNICALL 471 Java_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 }