Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Portable way to determining of printer is physical or virtual

I need direct-to-printer functionality for my website, with the ability to distinguish a physical printer from a virtual printer (file).

Coupons.com has this functionality via a native binary which must be installed by the user. I'd prefer to avoid that.

SmartSource.com does it via Java applet:

Smart Source Java Applet

Does anybody know how this is done? I dug through that Java APIs a bit, and don't see anything that would let you determine physical vs virtual, except looking at the name (that seems prone to misidentification). It would be nice to be able to do it in Java, because I already know how to write Java applets. Failing that, is there a way to do this in Flash or Silverlight?

Thanks in advance.

EDIT: Well deserved bounty awarded to Jason Sperske who worked out an elegant solution. Thanks to those of you who shared ideas, as well as those who actually investigated SmartSource.com's solution (like Adrian).

like image 663
Mud Avatar asked Feb 25 '11 01:02

Mud


3 Answers

OK here is what I've found so far (this is not an exhaustive test by any means, but it has been a fun problem to try and tackle). it seems that there might be some help by looking at how the validatePage() method in the PrinterJob class works. It seems that if a printer job is virtual than any attempt to set a page's ImageableArea will always return a value exactly equal to the default pages ImageableArea while an attempt to to the same to a real printer will return slightly smaller values (to account for the edges of the paper being held by the printer mechanics. What helps for this problem is that if you just ask a printer for it's default characteristics before calling validate you get an optimistic result, and if you compare this to a validated response you can do a simple if test. I've written some code for this that seems to work with the image printers and real printers I have on my desktop (again this isn't exhaustive, but it could work as a starting point)

import java.awt.print.*;

import javax.print.PrintService;
import javax.print.attribute.Attribute;

public class DetectFilePrinter {

  public static void main(String[] args) {
    PrinterJob job = PrinterJob.getPrinterJob();
    PrintService printer = job.getPrintService();
    System.out.println("Printer Name:"+printer.getName());

    System.out.println(printer.toString());
    PageFormat page = job.defaultPage();
    double default_width = page.getWidth();
    double default_height = page.getHeight();

    Paper paper = new Paper();
    paper.setImageableArea(0, 0, Double.MAX_VALUE, Double.MAX_VALUE);
    page.setPaper(paper);

    PageFormat fixed_page = job.validatePage(page);

    double fixed_width = fixed_page.getImageableWidth();
    double fixed_height = fixed_page.getImageableHeight();

    //So far all of my tested "image printers" return the same 
    //height and width after calling validatePage()
    if(default_height == fixed_height && default_width == fixed_width) {
      System.out.println("This looks like a \"image printer\"");
    } else {
      System.out.println("This looks like a \"real printer\"");
    }
  }
}
like image 59
Jason Sperske Avatar answered Oct 22 '22 08:10

Jason Sperske


As you say, there appear to be ways of gleaning a bit of information about the printer:

javax.print.attribute.standard.PrinterMakeAndModel looks promising.

Disallowing file: Destinations on any printer, and printing to any printer with the word PDF in the printer make and model would likely cover 90% of the cases judging by this list of virtual print software. You realistically aren't going to run into false positives on the word PDF either.

If this feature isn't perfect, your client isn't likely to notice; your competitors likely also have some horrible kludge because they know just as well this feature is more "security theater" than actual security.

like image 41
Kevin Stricker Avatar answered Oct 22 '22 08:10

Kevin Stricker


I know how to do it very easily on Win32. There is a structure called PRINTER_INFO_2 which has a field called pPortName. You can string compare it with the string "FILE" and get all the queues which are connected to the "FILE" port. And you can similarly add in more parsing logic for detecting other virtual printers. This structure is filled up by Windows when you call GetPrinter with the printer handle. Now the question is how to do it in .NET. I saw one way which is using marshalling. I saw some code snippets in http://vbcity.com/forums/t/66183.aspx. You can also look at C# interop using pInvoke. http://pinvoke.net/default.aspx/Structures/PRINTER_INFO_2.html

Hope this helps.

like image 40
Nitin Unni Avatar answered Oct 22 '22 08:10

Nitin Unni