-->

Finding if printer is online and ready to print

2019-04-11 19:34发布

问题:

The following 4 questions didn't help, therefore this isn't a duplicate:

ONE, TWO, THREE, FOUR


I need to find a way to discover if the Printer that my system reports is available to print or not.

Printer page:

In the picture, the printer "THERMAL" is available to print, but "HPRT PPTII-A(USB)" isn't available to print. The System shows me that, by making the non-available printer shaded

Using the following code, I'm able to find all the printers in the system

public static List<String> getAvailablePrinters() {
    DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
    PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();

    PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, aset);
    ArrayList<String> names = new ArrayList<String>();
    for (PrintService p : services) {
        Attribute at = p.getAttribute(PrinterIsAcceptingJobs.class);
        if (at == PrinterIsAcceptingJobs.ACCEPTING_JOBS) {
            names.add(p.getName());
        }

    }
    return names;
}

output:

[HPRT PPTII-A(USB), THERMAL]

The problem is: This code shows all the printers that the system have ever installed.

What I need: This list should contain only the really available printers to print. In this example, it should only show "THERMAL", and not show "HPRT PPTII-A(USB)"

How can this be achieved?

回答1:

If it is okay that the solution is Windows-specific, try WMI4Java. Here is my situation:

As you can see, my default printer "Kyocera Mita FS-1010" is inactive (greyed out) because I simply switched it off.

Now add this to your Maven POM:

<dependency>
  <groupId>com.profesorfalken</groupId>
  <artifactId>WMI4Java</artifactId>
  <version>1.4.2</version>
</dependency>

Then it is as easy as this to list all printers with their respective status:

package de.scrum_master.app;

import com.profesorfalken.wmi4java.WMI4Java;
import com.profesorfalken.wmi4java.WMIClass;

import java.util.Arrays;

public class Printer {
    public static void main(String[] args) {
        System.out.println(
            WMI4Java
                .get()
                .properties(Arrays.asList("Name", "WorkOffline"))
                .getRawWMIObjectOutput(WMIClass.WIN32_PRINTER)
        );
    }
}

The console log looks as follows:

Name        : WEB.DE Club SmartFax
WorkOffline : False

Name        : Send To OneNote 2016
WorkOffline : False

Name        : Microsoft XPS Document Writer
WorkOffline : False

Name        : Microsoft Print to PDF
WorkOffline : False

Name        : Kyocera Mita FS-1010 KX
WorkOffline : True

Name        : FreePDF
WorkOffline : False

Name        : FinePrint
WorkOffline : False

Name        : Fax
WorkOffline : False

Please note that WorkOffline is True for the Kyocera printer. Probably this is what you wanted to find out.

And now a little modification in order to filter the printers list so as to only show active printers:

            WMI4Java
                .get()
                .properties(Arrays.asList("Name", "WorkOffline"))
                .filters(Arrays.asList("$_.WorkOffline -eq 0"))
                .getRawWMIObjectOutput(WMIClass.WIN32_PRINTER)

Update: I was asked how to get a list of active printer names. Well, this is not so easy due to a shortcoming in WMI4Java for which I have just filed a pull request. It causes us to parse and filter the raw WMI output, but the code is still pretty straightforward:

package de.scrum_master.app;

import com.profesorfalken.wmi4java.WMI4Java;
import com.profesorfalken.wmi4java.WMIClass;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Printer {
    public static void main(String[] args) {
        String rawOutput = WMI4Java
            .get()
            .properties(Arrays.asList("Name", "WorkOffline"))
            .filters(Arrays.asList("$_.WorkOffline -eq 0"))
            .getRawWMIObjectOutput(WMIClass.WIN32_PRINTER);
        List<String> printers = Arrays.stream(rawOutput.split("(\r?\n)"))
            .filter(line -> line.startsWith("Name"))
            .map(line -> line.replaceFirst(".* : ", ""))
            .sorted()
            .collect(Collectors.toList());
        System.out.println(printers);
    }
}

The console output looks like this:

[Fax, FinePrint, FreePDF, Microsoft Print to PDF, Microsoft XPS Document Writer, Send To OneNote 2016, WEB.DE Club SmartFax]


回答2:

Windows solution, query WMI "win32_printer" object:

public static void main(String[] args) {
    // select printer that have state = 0 and status = 3, which indicates that printer can print
    ProcessBuilder builder = new ProcessBuilder("powershell.exe", "get-wmiobject -class win32_printer | Select-Object Name, PrinterState, PrinterStatus | where {$_.PrinterState -eq 0 -And $_.PrinterStatus -eq 3}");
    String fullStatus = null;
    Process reg;
    builder.redirectErrorStream(true);
    try {
        reg = builder.start();
        fullStatus = getStringFromInputStream(reg.getInputStream());
        reg.destroy();
    } catch (IOException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    System.out.print(fullStatus);
}

For converting InputStream to String look here: comprehensive StackOverflow answer, or you can simply use:

public static String getStringFromInputStream(InputStream is) {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length;
    try {
        while ((length = is.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }
    } catch (IOException e1) {
        e1.printStackTrace();
    }
    // StandardCharsets.UTF_8.name() > JDK 7
    String finalResult = "";
    try {
        finalResult = result.toString("UTF-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return finalResult;
}

Output:

Name                          PrinterState PrinterStatus
----                          ------------ -------------
Foxit Reader PDF Printer                 0             3
Send to OneNote 2010                     0             3
Microsoft XPS Document Writer            0             3
Microsoft Print to PDF                   0             3
Fax                                      0             3
\\192.168.50.192\POS_PRINTER             0             3

As you can see, you now have all the printers that are in working state in the string.

You can use your existing method (getAvailablePrinters()) and e.g. add something like this:

    ArrayList<String> workingPrinter = new ArrayList<String>();
    System.out.println("Working printers:");
    for(String printer : getAvailablePrinters()){
        if(fullStatus.contains("\n" + printer + " ")){  // add a newline character before the printer name and space after so that it catches exact name 
            workingPrinter.add(printer);
            System.out.println(printer);
        }
    }

And now you will have a nice list of working printers.

Console output:

Working printers:
Send to OneNote 2010
Foxit Reader PDF Printer
Microsoft XPS Document Writer
Microsoft Print to PDF
Fax
\\192.168.50.192\POS_PRINTER

Of course you have to be careful with the names with this approach - e.g. if "POS_PRINTER" is in all printers but not in working printers list, it could still get added to the workingPrinters list, if there is a working printer named "POS_PRINTER 1" as that name contains "\nPOS_PRINTER " string...