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