Merge pull request #2646 from ludovic-henry/fix-processwatch-dispose
[mono.git] / mcs / class / System.Drawing / System.Drawing.Printing / PrintingServicesUnix.cs
1 //#define PrintDebug
2 //
3 // Copyright (C) 2005, 2007 Novell, Inc. http://www.novell.com
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 // Author:
25 //
26 //      Jordi Mas i Hernandez, jordimash@gmail.com
27 //
28
29 using System.Runtime.InteropServices;
30 using System.Collections;
31 using System.Collections.Specialized;
32 using System.Drawing.Printing;
33 using System.ComponentModel;
34 using System.Drawing.Imaging;
35 using System.Text;
36 using System.IO;
37
38 namespace System.Drawing.Printing
39 {
40         internal class PrintingServicesUnix : PrintingServices
41         {
42                 #region Private Fields
43                 
44                 private static Hashtable doc_info = new Hashtable ();
45                 private static bool cups_installed;
46                 
47                 //private string printer_name;
48                 
49                 private static Hashtable installed_printers;
50                 private static string default_printer = String.Empty;
51                                 
52                 #endregion
53
54                 #region Constructor
55
56                 internal PrintingServicesUnix () {
57                 }
58                 
59                 static PrintingServicesUnix () {
60                         installed_printers = new Hashtable ();
61                         CheckCupsInstalled ();
62                 }
63                 
64                 #endregion
65
66                 #region Properties
67
68                 internal static PrinterSettings.StringCollection InstalledPrinters {
69                         get {
70                                 LoadPrinters();
71                                 PrinterSettings.StringCollection list = new PrinterSettings.StringCollection (new string[] {});
72                                 foreach (object key in installed_printers.Keys) {
73                                         list.Add (key.ToString());
74                                 }
75                                 return list;
76                         }
77                 }
78
79                 internal override string DefaultPrinter {
80                         get {
81                                 if (installed_printers.Count == 0)
82                                         LoadPrinters();
83                                 return default_printer;
84                         }
85                 }
86                 
87                 #endregion
88
89
90                 #region Methods
91
92                 /// <summary>
93                 /// Do a cups call to check if it is installed
94                 /// </summary>
95                 private static void CheckCupsInstalled ()
96                 {
97                         try {
98                                 cupsGetDefault ();
99                         }
100                         catch (DllNotFoundException) {
101                                 Console.WriteLine("libcups not found. To have printing support, you need cups installed");
102                                 cups_installed = false;
103                                 return;
104                         }
105
106                         cups_installed = true;
107                 }
108
109                 /// <summary>
110                 /// Open the printer's PPD file
111                 /// </summary>
112                 /// <param name="printer">Printer name, returned from cupsGetDests</param>
113                 private IntPtr OpenPrinter (string printer)
114                 {
115                         try {
116                                 IntPtr ptr = cupsGetPPD (printer);
117                                 string ppd_filename = Marshal.PtrToStringAnsi (ptr);
118                                 IntPtr ppd_handle = ppdOpenFile (ppd_filename);
119                                 return ppd_handle;
120                         }
121                         catch (Exception) {
122                                 Console.WriteLine ("There was an error opening the printer {0}. Please check your cups installation.");
123                         }
124                         return IntPtr.Zero;
125                 }
126
127                 /// <summary>
128                 /// Close the printer file
129                 /// </summary>
130                 /// <param name="handle">PPD handle</param>
131                 private void ClosePrinter (ref IntPtr handle)
132                 {
133                         try {
134                                 if (handle != IntPtr.Zero)
135                                         ppdClose (handle);
136                         }
137                         finally {
138                                 handle = IntPtr.Zero;
139                         }
140                 }
141
142                 private static int OpenDests (ref IntPtr ptr) {
143                         try {
144                                 return cupsGetDests (ref ptr);
145                         }
146                         catch {
147                                 ptr = IntPtr.Zero;
148                         }
149                         return 0;
150                 }
151
152                 private static void CloseDests (ref IntPtr ptr, int count) {
153                         try {
154                                 if (ptr != IntPtr.Zero)
155                                         cupsFreeDests (count, ptr);
156                         }
157                         finally {
158                                 ptr = IntPtr.Zero;
159                         }
160                 }
161
162                 /// <summary>
163                 /// Checks if a printer has a valid PPD file. Caches the result unless force is true
164                 /// </summary>
165                 /// <param name="force">Does the check disregarding the last cached value if true</param>
166                 internal override bool IsPrinterValid(string printer)
167                 {
168                         if (!cups_installed || printer == null | printer == String.Empty)
169                                 return false;
170                                 
171                         return installed_printers.Contains (printer);
172 /*
173                         if (!force && this.printer_name != null && String.Intern(this.printer_name).Equals(printer))
174                                 return is_printer_valid;
175
176                         IntPtr ptr = cupsGetPPD (printer);
177                         string ppd_filename = Marshal.PtrToStringAnsi (ptr);
178                         is_printer_valid = ppd_filename != null;
179                         this.printer_name = printer; 
180                         return is_printer_valid;
181 */                      
182                 }
183         
184                 /// <summary>
185                 /// Loads the printer settings and initializes the PrinterSettings and PageSettings fields
186                 /// </summary>
187                 /// <param name="printer">Printer name</param>
188                 /// <param name="settings">PrinterSettings object to initialize</param>
189                 internal override void LoadPrinterSettings (string printer, PrinterSettings settings)
190                 {
191                         if (cups_installed == false || (printer == null) || (printer == String.Empty))
192                                 return;
193
194                         if (installed_printers.Count == 0)
195                                 LoadPrinters ();
196
197                         if (((SysPrn.Printer)installed_printers[printer]).Settings != null) {
198                                 SysPrn.Printer p = (SysPrn.Printer) installed_printers[printer];
199                                 settings.can_duplex = p.Settings.can_duplex;
200                                 settings.is_plotter = p.Settings.is_plotter;
201                                 settings.landscape_angle = p.Settings.landscape_angle;
202                                 settings.maximum_copies = p.Settings.maximum_copies;
203                                 settings.paper_sizes = p.Settings.paper_sizes;
204                                 settings.paper_sources = p.Settings.paper_sources;
205                                 settings.printer_capabilities = p.Settings.printer_capabilities;
206                                 settings.printer_resolutions = p.Settings.printer_resolutions;
207                                 settings.supports_color = p.Settings.supports_color;
208                                 return;
209                         }
210
211                         settings.PrinterCapabilities.Clear ();
212
213                         IntPtr dests = IntPtr.Zero, ptr = IntPtr.Zero, ptr_printer, ppd_handle = IntPtr.Zero;
214                         string name = String.Empty;
215                         CUPS_DESTS printer_dest;
216                         PPD_FILE ppd;
217                         int ret = 0, cups_dests_size;
218                         NameValueCollection options, paper_names, paper_sources;
219
220                         try {
221                                 ret = OpenDests (ref dests);
222                                 if (ret == 0)
223                                         return;
224
225                                 cups_dests_size = Marshal.SizeOf (typeof(CUPS_DESTS));
226                                 ptr = dests;
227                                 for (int i = 0; i < ret; i++) {
228                                         ptr_printer = (IntPtr) Marshal.ReadIntPtr (ptr);
229                                         if (Marshal.PtrToStringAnsi (ptr_printer).Equals(printer)) {
230                                                 name = printer;
231                                                 break;
232                                         }
233                                         ptr = (IntPtr) ((long)ptr + cups_dests_size);
234                                 }
235                         
236                                 if (!name.Equals(printer)) {
237                                         return;
238                                 }
239
240                                 ppd_handle = OpenPrinter (printer);
241                                 if (ppd_handle == IntPtr.Zero)
242                                         return;
243
244                                 printer_dest = (CUPS_DESTS) Marshal.PtrToStructure (ptr, typeof (CUPS_DESTS));
245                                 options = new NameValueCollection();
246                                 paper_names = new NameValueCollection();
247                                 paper_sources = new NameValueCollection();
248                                 string defsize;
249                                 string defsource;
250                                 LoadPrinterOptions (printer_dest.options, printer_dest.num_options, ppd_handle, options,
251                                         paper_names, out defsize,
252                                         paper_sources, out defsource);
253
254                                 if (settings.paper_sizes == null)
255                                         settings.paper_sizes = new PrinterSettings.PaperSizeCollection (new PaperSize [] {});
256                                 else
257                                         settings.paper_sizes.Clear();
258                         
259                                 if (settings.paper_sources == null)                             
260                                         settings.paper_sources = new PrinterSettings.PaperSourceCollection (new PaperSource [] {});
261                                 else
262                                         settings.paper_sources.Clear();
263
264                                 settings.DefaultPageSettings.PaperSource = LoadPrinterPaperSources (settings, defsource, paper_sources);
265                                 settings.DefaultPageSettings.PaperSize = LoadPrinterPaperSizes (ppd_handle, settings, defsize, paper_names);
266                                 LoadPrinterResolutionsAndDefault (printer, settings, ppd_handle);
267
268                                 ppd = (PPD_FILE) Marshal.PtrToStructure (ppd_handle, typeof (PPD_FILE));
269                                 settings.landscape_angle = ppd.landscape;
270                                 settings.supports_color = (ppd.color_device == 0) ? false : true;
271                                 settings.can_duplex = options["Duplex"] != null;
272
273                                 ClosePrinter (ref ppd_handle);
274                         
275                                 ((SysPrn.Printer)installed_printers[printer]).Settings = settings;
276                         }
277                         finally {
278                                 CloseDests (ref dests, ret);
279                         }
280                 }
281                 
282                 /// <summary>
283                 /// Loads the global options of a printer plus the paper types and trays supported,
284                 /// and sets the default paper size and source tray.
285                 /// </summary>
286                 /// <param name="options">The options field of a printer's CUPS_DESTS structure</param>
287                 /// <param name="numOptions">The number of options of the printer</param>
288                 /// <param name="ppd">A ppd handle for the printer, returned by ppdOpen</param>
289                 /// <param name="list">The list of options</param>
290                 /// <param name="paper_names">A list of types of paper (PageSize)</param>
291                 /// <param name="defsize">The default paper size, set by LoadOptionList</param>
292                 /// <param name="paper_sources">A list of trays(InputSlot) </param>
293                 /// <param name="defsource">The default source tray, set by LoadOptionList</param>
294                 private static void LoadPrinterOptions(IntPtr options, int numOptions, IntPtr ppd, 
295                                                                                  NameValueCollection list, 
296                                                                                  NameValueCollection paper_names, out string defsize,
297                                                                                  NameValueCollection paper_sources, out string defsource)
298                 {
299                         CUPS_OPTIONS cups_options;
300                         string option_name, option_value;
301                         int cups_size = Marshal.SizeOf(typeof(CUPS_OPTIONS));
302
303                         LoadOptionList (ppd, "PageSize", paper_names, out defsize);
304                         LoadOptionList (ppd, "InputSlot", paper_sources, out defsource);
305                         
306                         for (int j = 0; j < numOptions; j++)
307                         {
308                                 cups_options = (CUPS_OPTIONS) Marshal.PtrToStructure(options, typeof(CUPS_OPTIONS));
309                                 option_name = Marshal.PtrToStringAnsi(cups_options.name);
310                                 option_value = Marshal.PtrToStringAnsi(cups_options.val);
311
312                                 if (option_name == "PageSize") defsize = option_value;
313                                 else if (option_name == "InputSlot") defsource = option_value;
314                                 #if PrintDebug
315                                 Console.WriteLine("{0} = {1}", option_name, option_value);
316                                 #endif
317                                 
318                                 list.Add(option_name, option_value);
319
320                                 options = (IntPtr) ((long)options + cups_size);
321                         }
322                 }
323                 
324                 /// <summary>
325                 /// Loads the global options of a printer. 
326                 /// </summary>
327                 /// <param name="options">The options field of a printer's CUPS_DESTS structure</param>
328                 /// <param name="numOptions">The number of options of the printer</param>
329                 private static NameValueCollection LoadPrinterOptions(IntPtr options, int numOptions)
330                 {
331                         CUPS_OPTIONS cups_options;
332                         string option_name, option_value;
333                         int cups_size = Marshal.SizeOf (typeof(CUPS_OPTIONS));
334                         NameValueCollection list = new NameValueCollection ();
335                         for (int j = 0; j < numOptions; j++)
336                         {
337                                 cups_options = (CUPS_OPTIONS) Marshal.PtrToStructure(options, typeof(CUPS_OPTIONS));
338                                 option_name = Marshal.PtrToStringAnsi (cups_options.name);
339                                 option_value = Marshal.PtrToStringAnsi (cups_options.val);
340                                 
341                                 #if PrintDebug
342                                 Console.WriteLine("{0} = {1}", option_name, option_value);
343                                 #endif
344                                 
345                                 list.Add (option_name, option_value);
346
347                                 options = (IntPtr) ((long)options + cups_size);
348                         }
349                         return list;
350                 }
351
352                 /// <summary>
353                 /// Loads a printer's options (selection of paper sizes, paper sources, etc)
354                 /// and sets the default option from the selected list.
355                 /// </summary>
356                 /// <param name="ppd">Printer ppd file handle</param>
357                 /// <param name="option_name">Name of the option group to load</param>
358                 /// <param name="list">List of loaded options</param>
359                 /// <param name="defoption">The default option from the loaded options list</param>
360                 private static void LoadOptionList (IntPtr ppd, string option_name, NameValueCollection list, out string defoption) {
361
362                         IntPtr ptr = IntPtr.Zero;
363                         PPD_OPTION ppd_option;
364                         PPD_CHOICE choice;
365                         int choice_size = Marshal.SizeOf(typeof(PPD_CHOICE)); 
366                         defoption = null;
367                         
368                         ptr = ppdFindOption (ppd, option_name);
369                         if (ptr != IntPtr.Zero)
370                         {
371                                 ppd_option = (PPD_OPTION) Marshal.PtrToStructure (ptr, typeof (PPD_OPTION));
372                                 #if PrintDebug
373                                 Console.WriteLine (" OPTION  key:{0} def:{1} text: {2}", ppd_option.keyword, ppd_option.defchoice, ppd_option.text);
374                                 #endif
375                                 defoption = ppd_option.defchoice;
376                                 ptr = ppd_option.choices;
377                                 for (int c = 0; c < ppd_option.num_choices; c++) {
378                                         choice = (PPD_CHOICE) Marshal.PtrToStructure (ptr, typeof (PPD_CHOICE));
379                                         list.Add(choice.choice, choice.text);
380                                         #if PrintDebug
381                                         Console.WriteLine ("       choice:{0} - text: {1}", choice.choice, choice.text);
382                                         #endif
383
384                                         ptr = (IntPtr) ((long)ptr + choice_size);
385                                 }
386                         }               
387                 }
388
389                 /// <summary>
390                 /// Loads a printer's available resolutions
391                 /// </summary>
392                 /// <param name="printer">Printer name</param>
393                 /// <param name="settings">PrinterSettings object to fill</param>
394                 internal override void LoadPrinterResolutions (string printer, PrinterSettings settings)
395                 {
396                         IntPtr ppd_handle = OpenPrinter (printer);
397                         if (ppd_handle == IntPtr.Zero)
398                                 return;
399
400                         LoadPrinterResolutionsAndDefault (printer, settings, ppd_handle);
401
402                         ClosePrinter (ref ppd_handle);
403                 }
404
405                 /// <summary>
406                 /// Create a PrinterResolution from a string Resolution that is set in the PPD option.
407                 /// An example of Resolution is "600x600dpi" or "600dpi". Returns null if malformed or "Unknown".
408                 /// </summary>
409                 private PrinterResolution ParseResolution (string resolution)
410                 {
411                         if (String.IsNullOrEmpty (resolution))
412                                 return null;
413
414                         int dpiIndex = resolution.IndexOf ("dpi");
415                         if (dpiIndex == -1)
416                         {
417                                 // Resolution is "Unknown" or unparsable
418                                 return null;
419                         }
420                         resolution = resolution.Substring (0, dpiIndex);
421
422                         int x_resolution, y_resolution;
423                         try {
424                                 if (resolution.Contains ("x")) {
425                                         string[] resolutions = resolution.Split (new[] {'x'});
426                                         x_resolution = Convert.ToInt32 (resolutions [0]);
427                                         y_resolution = Convert.ToInt32 (resolutions [1]);
428                                 } else {
429                                         x_resolution = Convert.ToInt32 (resolution);
430                                         y_resolution = x_resolution;
431                                 }
432                         } catch (Exception) {
433                                 return null;
434                         }
435
436                         return new PrinterResolution (x_resolution, y_resolution, PrinterResolutionKind.Custom);
437                 }
438
439                 /// <summary>
440                 /// Loads a printer's paper sizes. Returns the default PaperSize, and fills a list of paper_names for use in dialogues
441                 /// </summary>
442                 /// <param name="ppd_handle">PPD printer file handle</param>
443                 /// <param name="settings">PrinterSettings object to fill</param>
444                 /// <param name="def_size">Default paper size, from the global options of the printer</param>
445                 /// <param name="paper_names">List of available paper sizes that gets filled</param>
446                 private PaperSize LoadPrinterPaperSizes(IntPtr ppd_handle, PrinterSettings settings, 
447                                                                                                 string def_size, NameValueCollection paper_names)
448                 {
449                         IntPtr ptr;
450                         string real_name;
451                         PPD_FILE ppd;
452                         PPD_SIZE size;
453                         PaperSize ps;
454
455                         PaperSize defsize = new PaperSize ("A4", 827, 1169, GetPaperKind (827, 1169), true);
456                         ppd = (PPD_FILE) Marshal.PtrToStructure (ppd_handle, typeof (PPD_FILE));
457                         ptr = ppd.sizes;
458                         float w, h;
459                         for (int i = 0; i < ppd.num_sizes; i++) {
460                                 size = (PPD_SIZE) Marshal.PtrToStructure (ptr, typeof (PPD_SIZE));
461                                 real_name = paper_names[size.name];
462                                 w = size.width * 100 / 72;
463                                 h = size.length * 100 / 72;
464                                 PaperKind kind = GetPaperKind ((int) w, (int) h);
465                                 ps = new PaperSize (real_name, (int) w, (int) h, kind, def_size == kind.ToString ());
466                                 ps.SetKind (kind);
467                                 if (def_size == ps.Kind.ToString ())
468                                         defsize = ps;
469                                 settings.paper_sizes.Add (ps);
470                                 ptr = (IntPtr) ((long)ptr + Marshal.SizeOf (size));
471                         }
472                         
473                         return defsize;
474
475                 }
476                 
477                 /// <summary>
478                 /// Loads a printer's paper sources (trays). Returns the default PaperSource, and fills a list of paper_sources for use in dialogues
479                 /// </summary>
480                 /// <param name="settings">PrinterSettings object to fill</param>
481                 /// <param name="def_source">Default paper source, from the global options of the printer</param>
482                 /// <param name="paper_sources">List of available paper sizes that gets filled</param>
483                 private PaperSource LoadPrinterPaperSources (PrinterSettings settings, string def_source, 
484                                                                                                         NameValueCollection paper_sources)
485                 {
486                         PaperSourceKind kind;
487                         PaperSource defsource = null;
488                         foreach(string source in paper_sources) {
489                                 switch (source)
490                                 {
491                                         case "Auto":
492                                                 kind = PaperSourceKind.AutomaticFeed;
493                                                 break;
494                                         case "Standard":
495                                                 kind = PaperSourceKind.AutomaticFeed;
496                                                 break;
497                                         case "Tray":
498                                                 kind = PaperSourceKind.AutomaticFeed;
499                                                 break;
500                                         case "Envelope":
501                                                 kind = PaperSourceKind.Envelope;
502                                                 break;
503                                         case "Manual":
504                                                 kind = PaperSourceKind.Manual;
505                                                 break;
506                                         default:
507                                                 kind = PaperSourceKind.Custom;
508                                                 break;
509                                 }
510                                 settings.paper_sources.Add (new PaperSource (paper_sources[source], kind, def_source == source));
511                                 if (def_source == source)
512                                         defsource = settings.paper_sources[settings.paper_sources.Count-1];
513                         }
514                         
515                         if (defsource == null && settings.paper_sources.Count > 0)
516                                 return settings.paper_sources[0];
517                         return defsource;
518                 }
519
520                 /// <summary>
521                 /// Sets the available resolutions and default resolution from a
522                 /// printer's PPD file into settings.
523                 /// </summary>
524                 private void LoadPrinterResolutionsAndDefault (string printer,
525                         PrinterSettings settings, IntPtr ppd_handle)
526                 {
527                         if (settings.printer_resolutions == null)
528                                 settings.printer_resolutions = new PrinterSettings.PrinterResolutionCollection (new PrinterResolution [] {});
529                         else
530                                 settings.printer_resolutions.Clear ();
531
532                         var printer_resolutions = new NameValueCollection ();
533                         string defresolution;
534                         LoadOptionList (ppd_handle, "Resolution", printer_resolutions, out defresolution);
535                         foreach (var resolution in printer_resolutions.Keys) {
536                                 var new_resolution = ParseResolution (resolution.ToString ());
537                                 settings.PrinterResolutions.Add (new_resolution);
538                         }
539
540                         var default_resolution = ParseResolution (defresolution);
541
542                         if (default_resolution == null)
543                                 default_resolution = ParseResolution ("300dpi");
544                         if (printer_resolutions.Count == 0)
545                                 settings.PrinterResolutions.Add (default_resolution);
546
547                         settings.DefaultPageSettings.PrinterResolution = default_resolution;
548                 }
549
550                 /// <summary>
551                 /// </summary>
552                 /// <param name="load"></param>
553                 /// <param name="def_printer"></param>
554                 private static void LoadPrinters()
555                 {
556                         installed_printers.Clear ();            
557                         if (cups_installed == false)
558                                 return;
559                         
560                         IntPtr dests = IntPtr.Zero, ptr_printers;
561                         CUPS_DESTS printer;
562                         int n_printers = 0;
563                         int cups_dests_size = Marshal.SizeOf(typeof(CUPS_DESTS));
564                         string name, first, type, status, comment;
565                         first = type = status = comment = String.Empty;
566                         int state = 0;
567                         
568                         try {
569                                 n_printers = OpenDests (ref dests);
570
571                                 ptr_printers = dests;
572                                 for (int i = 0; i < n_printers; i++) {
573                                         printer = (CUPS_DESTS) Marshal.PtrToStructure (ptr_printers, typeof (CUPS_DESTS));
574                                         name = Marshal.PtrToStringAnsi (printer.name);
575
576                                         if (printer.is_default == 1)
577                                                 default_printer = name;
578                                 
579                                         if (first.Equals (String.Empty))
580                                                 first = name;
581                         
582                                         NameValueCollection options = LoadPrinterOptions(printer.options, printer.num_options);
583                                 
584                                         if (options["printer-state"] != null)
585                                                 state = Int32.Parse(options["printer-state"]);
586                                 
587                                         if (options["printer-comment"] != null)
588                                                 comment = options["printer-state"];
589
590                                         switch(state) {
591                                                 case 4:
592                                                         status = "Printing";
593                                                         break;
594                                                 case 5:
595                                                         status = "Stopped";
596                                                         break;
597                                                 default:
598                                                         status =  "Ready";
599                                                         break;
600                                         }
601                                 
602                                         installed_printers.Add (name, new SysPrn.Printer (String.Empty, type, status, comment));
603
604                                         ptr_printers = (IntPtr) ((long)ptr_printers + cups_dests_size);
605                                 }
606                         
607                         }
608                         finally {
609                                 CloseDests (ref dests, n_printers);
610                         }
611                         
612                         if (default_printer.Equals (String.Empty))
613                                 default_printer = first;
614                 }
615
616                 /// <summary>
617                 /// Gets a printer's settings for use in the print dialogue
618                 /// </summary>
619                 /// <param name="printer"></param>
620                 /// <param name="port"></param>
621                 /// <param name="type"></param>
622                 /// <param name="status"></param>
623                 /// <param name="comment"></param>
624                 internal override void GetPrintDialogInfo (string printer, ref string port, ref string type, ref string status, ref string comment) {
625                         int count = 0, state = -1;
626                         bool found = false;
627                         CUPS_DESTS cups_dests;
628                         IntPtr dests = IntPtr.Zero, ptr_printers, ptr_printer;
629                         int cups_dests_size = Marshal.SizeOf(typeof(CUPS_DESTS));
630                         
631                         if (cups_installed == false)
632                                 return;
633
634                         try {
635                                 count = OpenDests (ref dests);
636
637                                 if (count == 0)
638                                         return;
639
640                                 ptr_printers = dests;
641
642                                 for (int i = 0; i < count; i++) {
643                                         ptr_printer = (IntPtr) Marshal.ReadIntPtr (ptr_printers);
644                                         if (Marshal.PtrToStringAnsi (ptr_printer).Equals(printer)) {
645                                                 found = true;
646                                                 break;
647                                         }
648                                         ptr_printers = (IntPtr) ((long)ptr_printers + cups_dests_size);                         
649                                 }
650                         
651                                 if (!found)
652                                         return;
653                         
654                                 cups_dests = (CUPS_DESTS) Marshal.PtrToStructure (ptr_printers, typeof (CUPS_DESTS));
655                         
656                                 NameValueCollection options = LoadPrinterOptions(cups_dests.options, cups_dests.num_options);
657
658                                 if (options["printer-state"] != null)
659                                         state = Int32.Parse(options["printer-state"]);
660                         
661                                 if (options["printer-comment"] != null)
662                                         comment = options["printer-state"];
663
664                                 switch(state) {
665                                         case 4:
666                                                 status = "Printing";
667                                                 break;
668                                         case 5:
669                                                 status = "Stopped";
670                                                 break;
671                                         default:
672                                                 status =  "Ready";
673                                                 break;
674                                 }
675                         }
676                         finally {
677                                 CloseDests (ref dests, count);
678                         }
679                 }
680
681                 /// <summary>
682                 /// Returns the appropriate PaperKind for the width and height
683                 /// </summary>
684                 /// <param name="width"></param>
685                 /// <param name="height"></param>
686                 private PaperKind GetPaperKind (int width, int height)
687                 {
688                         if (width == 827 && height == 1169)
689                                 return PaperKind.A4;
690                         if (width == 583 && height == 827)
691                                 return PaperKind.A5;
692                         if (width == 717 && height == 1012)
693                                 return PaperKind.B5;
694                         if (width == 693 && height == 984)
695                                 return PaperKind.B5Envelope;
696                         if (width == 638 && height == 902)
697                                 return PaperKind.C5Envelope;
698                         if (width == 449 && height == 638)
699                                 return PaperKind.C6Envelope;
700                         if (width == 1700 && height == 2200)
701                                 return PaperKind.CSheet;
702                         if (width == 433 && height == 866)
703                                 return PaperKind.DLEnvelope;
704                         if (width == 2200 && height == 3400)
705                                 return PaperKind.DSheet;
706                         if (width == 3400 && height == 4400)
707                                 return PaperKind.ESheet;
708                         if (width == 725 && height == 1050)
709                                 return PaperKind.Executive;
710                         if (width == 850 && height == 1300)
711                                 return PaperKind.Folio;
712                         if (width == 850 && height == 1200)
713                                 return PaperKind.GermanStandardFanfold;
714                         if (width == 1700 && height == 1100)
715                                 return PaperKind.Ledger;
716                         if (width == 850 && height == 1400)
717                                 return PaperKind.Legal;
718                         if (width == 927 && height == 1500)
719                                 return PaperKind.LegalExtra;
720                         if (width == 850 && height == 1100)
721                                 return PaperKind.Letter;
722                         if (width == 927 && height == 1200)
723                                 return PaperKind.LetterExtra;
724                         if (width == 850 && height == 1269)
725                                 return PaperKind.LetterPlus;
726                         if (width == 387 && height == 750)
727                                 return PaperKind.MonarchEnvelope;
728                         if (width == 387 && height == 887)
729                                 return PaperKind.Number9Envelope;
730                         if (width == 413 && height == 950)
731                                 return PaperKind.Number10Envelope;
732                         if (width == 450 && height == 1037)
733                                 return PaperKind.Number11Envelope;
734                         if (width == 475 && height == 1100)
735                                 return PaperKind.Number12Envelope;
736                         if (width == 500 && height == 1150)
737                                 return PaperKind.Number14Envelope;
738                         if (width == 363 && height == 650)
739                                 return PaperKind.PersonalEnvelope;
740                         if (width == 1000 && height == 1100)
741                                 return PaperKind.Standard10x11;
742                         if (width == 1000 && height == 1400)
743                                 return PaperKind.Standard10x14;
744                         if (width == 1100 && height == 1700)
745                                 return PaperKind.Standard11x17;
746                         if (width == 1200 && height == 1100)
747                                 return PaperKind.Standard12x11;
748                         if (width == 1500 && height == 1100)
749                                 return PaperKind.Standard15x11;
750                         if (width == 900 && height == 1100)
751                                 return PaperKind.Standard9x11;
752                         if (width == 550 && height == 850)
753                                 return PaperKind.Statement;
754                         if (width == 1100 && height == 1700)
755                                 return PaperKind.Tabloid;
756                         if (width == 1487 && height == 1100)
757                                 return PaperKind.USStandardFanfold;
758
759                         return PaperKind.Custom;
760                 }
761
762                 #endregion
763
764                 #region Print job methods
765
766                 static string tmpfile;
767
768                 /// <summary>
769                 /// Gets a pointer to an options list parsed from the printer's current settings, to use when setting up the printing job
770                 /// </summary>
771                 /// <param name="printer_settings"></param>
772                 /// <param name="page_settings"></param>
773                 /// <param name="options"></param>
774                 internal static int GetCupsOptions (PrinterSettings printer_settings, PageSettings page_settings, out IntPtr options)
775                 {
776                         options = IntPtr.Zero;
777
778                         PaperSize size = page_settings.PaperSize;
779                         int width = size.Width * 72 / 100;
780                         int height = size.Height * 72 / 100;
781
782                         StringBuilder sb = new StringBuilder();
783                         sb.Append(
784                                 "copies=" + printer_settings.Copies + " " + 
785                                 "Collate=" + printer_settings.Collate + " " +
786                                 "ColorModel=" + (page_settings.Color ? "Color" : "Black") + " " +
787                                 "PageSize=" + String.Format ("Custom.{0}x{1}", width, height) + " " +
788                                 "landscape=" + page_settings.Landscape
789                         );
790                         
791                         if (printer_settings.CanDuplex)
792                         {
793                                 if (printer_settings.Duplex == Duplex.Simplex)
794                                         sb.Append(" Duplex=None");
795                                 else
796                                         sb.Append(" Duplex=DuplexNoTumble");                            
797                         }
798
799                         return cupsParseOptions (sb.ToString(), 0, ref options);
800                 }
801
802                 internal static bool StartDoc (GraphicsPrinter gr, string doc_name, string output_file)
803                 {
804                         DOCINFO doc = (DOCINFO) doc_info[gr.Hdc];
805                         doc.title = doc_name;
806                         return true;
807                 }
808
809                 internal static bool EndDoc (GraphicsPrinter gr)
810                 {
811                         DOCINFO doc = (DOCINFO) doc_info[gr.Hdc];
812
813                         gr.Graphics.Dispose (); // Dispose object to force surface finish
814                         
815                         IntPtr options;
816                         int options_count = GetCupsOptions (doc.settings, doc.default_page_settings, out options);
817
818                         cupsPrintFile (doc.settings.PrinterName, doc.filename, doc.title, options_count, options);
819                         cupsFreeOptions (options_count, options);
820                         doc_info.Remove (gr.Hdc);
821                         if (tmpfile != null) {
822                                 try { File.Delete (tmpfile); }
823                                 catch { }
824                         }
825                         return true;
826                 }
827
828                 internal static bool StartPage (GraphicsPrinter gr)
829                 {
830                         return true;
831                 }
832
833                 internal static bool EndPage (GraphicsPrinter gr)
834                 {
835                         GdipGetPostScriptSavePage (gr.Hdc);
836                         return true;
837                 }
838
839                 // Unfortunately, PrinterSettings and PageSettings couldn't be referencing each other,
840                 // thus we need to pass them separately
841                 internal static IntPtr CreateGraphicsContext (PrinterSettings settings, PageSettings default_page_settings)
842                 {
843                         IntPtr graphics = IntPtr.Zero;
844                         string name;
845                         if (!settings.PrintToFile) {
846                                 StringBuilder sb = new StringBuilder (1024);
847                                 int length = sb.Capacity;
848                                 cupsTempFd (sb, length);
849                                 name = sb.ToString ();
850                                 tmpfile = name;
851                         }
852                         else
853                                 name = settings.PrintFileName;
854
855                         PaperSize psize = default_page_settings.PaperSize;
856                         int width, height;
857                         if (default_page_settings.Landscape) { // Swap in case of landscape
858                                 width = psize.Height;
859                                 height = psize.Width;
860                         } else {
861                                 width = psize.Width;
862                                 height = psize.Height;
863                         }
864
865                         GdipGetPostScriptGraphicsContext (name,
866                                 width * 72 / 100,
867                                 height * 72 / 100, 
868                                 default_page_settings.PrinterResolution.X,
869                                 default_page_settings.PrinterResolution.Y, ref graphics);
870
871                         DOCINFO doc = new DOCINFO ();
872                         doc.filename = name;
873                         doc.settings = settings;
874                         doc.default_page_settings = default_page_settings;
875                         doc_info.Add (graphics, doc);
876
877                         return graphics;
878                 }
879
880                 #endregion
881
882                 #region DllImports
883
884                 [DllImport("libcups", CharSet=CharSet.Ansi)]
885                 static extern int cupsGetDests (ref IntPtr dests);
886
887 //              [DllImport("libcups", CharSet=CharSet.Ansi)]
888 //              static extern void cupsGetDest (string name, string instance, int num_dests, ref IntPtr dests);
889
890                 [DllImport("libcups")]
891                 static extern void cupsFreeDests (int num_dests, IntPtr dests);
892
893                 [DllImport("libcups", CharSet=CharSet.Ansi)]
894                 static extern IntPtr cupsTempFd (StringBuilder sb, int len);
895
896                 [DllImport("libcups", CharSet=CharSet.Ansi)]
897                 static extern IntPtr cupsGetDefault ();
898
899                 [DllImport("libcups", CharSet=CharSet.Ansi)]
900                 static extern int cupsPrintFile (string printer, string filename, string title, int num_options, IntPtr options);
901
902                 [DllImport("libcups", CharSet=CharSet.Ansi)]
903                 static extern IntPtr cupsGetPPD (string printer);
904
905                 [DllImport("libcups", CharSet=CharSet.Ansi)]
906                 static extern IntPtr ppdOpenFile (string filename);
907
908                 [DllImport("libcups", CharSet=CharSet.Ansi)]
909                 static extern IntPtr ppdFindOption (IntPtr ppd_file, string keyword);
910
911                 [DllImport("libcups")]
912                 static extern void ppdClose (IntPtr ppd);
913
914                 [DllImport ("libcups", CharSet=CharSet.Ansi)]
915                 static extern int cupsParseOptions (string arg, int number_of_options, ref IntPtr options);
916
917                 [DllImport("libcups")]
918                 static extern void cupsFreeOptions (int number_options, IntPtr options);
919
920                 [DllImport("gdiplus.dll", CharSet=CharSet.Ansi)]
921                 static extern int GdipGetPostScriptGraphicsContext (string filename, int with, int height, double dpix, double dpiy, ref IntPtr graphics);
922
923                 [DllImport("gdiplus.dll")]
924                 static extern int GdipGetPostScriptSavePage (IntPtr graphics);
925
926                 #endregion
927
928                 #region Struct
929                 public struct DOCINFO
930                 {
931                         public PrinterSettings settings;
932                         public PageSettings default_page_settings;
933                         public string title;
934                         public string filename;
935                 }
936
937                 public struct PPD_SIZE
938                 {
939                         public  int marked;
940                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=42)]
941                         public  string name;
942                         public  float width;
943                         public  float length;
944                         public  float left;
945                         public  float bottom;
946                         public  float right;
947                         public  float top;
948                 }
949
950                 public struct PPD_GROUP
951                 {
952                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=40)]
953                         public string text;
954                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=42)]
955                         public string name;
956                         public int num_options;
957                         public IntPtr options;
958                         public int num_subgroups;
959                         public IntPtr subgrups;
960                 }
961
962                 public struct PPD_OPTION
963                 {
964                         public byte     conflicted;
965                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=41)]
966                         public string   keyword;
967                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=41)]
968                         public string   defchoice;
969                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=81)]
970                         public string   text;
971                         public int      ui;
972                         public int      section;
973                         public float    order;
974                         public int      num_choices;
975                         public IntPtr   choices;
976                 }
977
978                 public struct PPD_CHOICE
979                 {
980                         public byte     marked;
981                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=41)]
982                         public string   choice;
983                         [MarshalAs(UnmanagedType.ByValTStr, SizeConst=81)]
984                         public string   text;
985                         public IntPtr   code;
986                         public IntPtr   option;
987                 }
988
989                 public struct PPD_FILE
990                 {
991                         public int      language_level;
992                         public int      color_device;
993                         public int      variable_sizes;
994                         public int      accurate_screens;
995                         public int      contone_only;
996                         public int      landscape;
997                         public int      model_number;
998                         public int      manual_copies;
999                         public int      throughput;
1000                         public int      colorspace;
1001                         public IntPtr   patches;
1002                         public int      num_emulations;
1003                         public IntPtr   emulations;
1004                         public IntPtr   jcl_begin;
1005                         public IntPtr   jcl_ps;
1006                         public IntPtr   jcl_end;
1007                         public IntPtr   lang_encoding;
1008                         public IntPtr   lang_version;
1009                         public IntPtr   modelname;
1010                         public IntPtr   ttrasterizer;
1011                         public IntPtr   manufacturer;
1012                         public IntPtr   product;
1013                         public IntPtr   nickname;
1014                         public IntPtr   shortnickname;
1015                         public int      num_groups;
1016                         public IntPtr   groups;
1017                         public int      num_sizes;
1018                         public IntPtr   sizes;
1019
1020                         /* There is more data after this that we are not using*/
1021                 }
1022
1023
1024                 public struct CUPS_OPTIONS
1025                 {
1026                         public IntPtr name;
1027                         public IntPtr val;
1028                 }
1029                 
1030                 public struct CUPS_DESTS
1031                 {
1032                         public IntPtr   name;
1033                         public IntPtr   instance;
1034                         public int      is_default;
1035                         public int      num_options;
1036                         public IntPtr   options;
1037                 }
1038                 
1039                 #endregion
1040         }
1041
1042         class GlobalPrintingServicesUnix : GlobalPrintingServices
1043         {
1044                 internal override PrinterSettings.StringCollection InstalledPrinters {
1045                         get {
1046                                 return PrintingServicesUnix.InstalledPrinters;
1047                         }
1048                 }
1049
1050                 internal override IntPtr CreateGraphicsContext (PrinterSettings settings, PageSettings default_page_settings)
1051                 {
1052                         return PrintingServicesUnix.CreateGraphicsContext (settings, default_page_settings);
1053                 }
1054
1055                 internal override bool StartDoc (GraphicsPrinter gr, string doc_name, string output_file)
1056                 {
1057                         return PrintingServicesUnix.StartDoc (gr, doc_name, output_file);
1058                 }
1059
1060                 internal override bool EndDoc (GraphicsPrinter gr)
1061                 {
1062                         return PrintingServicesUnix.EndDoc (gr);
1063                 }
1064
1065                 internal override bool StartPage (GraphicsPrinter gr)
1066                 {
1067                         return PrintingServicesUnix.StartPage (gr);
1068                 }
1069
1070                 internal override bool EndPage (GraphicsPrinter gr)
1071                 {
1072                         return PrintingServicesUnix.EndPage (gr);
1073                 }
1074         }
1075 }
1076