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