1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "stdafx.h"
6#include "win8/metro_driver/print_handler.h"
7
8#include <windows.graphics.display.h>
9
10#include "base/bind.h"
11#include "base/logging.h"
12#include "base/numerics/safe_conversions.h"
13#include "chrome/app/chrome_command_ids.h"
14#include "win8/metro_driver/chrome_app_view.h"
15#include "win8/metro_driver/winrt_utils.h"
16
17namespace {
18
19typedef winfoundtn::ITypedEventHandler<
20    wingfx::Printing::PrintManager*,
21    wingfx::Printing::PrintTaskRequestedEventArgs*> PrintRequestedHandler;
22
23typedef winfoundtn::ITypedEventHandler<
24    wingfx::Printing::PrintTask*,
25    wingfx::Printing::PrintTaskCompletedEventArgs*> PrintTaskCompletedHandler;
26
27typedef winfoundtn::ITypedEventHandler<
28    wingfx::Printing::PrintTask*, IInspectable*> PrintTaskInspectableHandler;
29
30typedef winfoundtn::ITypedEventHandler<
31    wingfx::Printing::PrintTask*,
32    wingfx::Printing::PrintTaskProgressingEventArgs*>
33    PrintTaskProgressingHandler;
34
35}  // namespace
36
37namespace metro_driver {
38
39mswr::ComPtr<PrintDocumentSource> PrintHandler::current_document_source_;
40bool PrintHandler::printing_enabled_ = false;
41base::Lock* PrintHandler::lock_ = NULL;
42base::Thread* PrintHandler::thread_ = NULL;
43
44PrintHandler::PrintHandler() {
45  DCHECK(lock_ == NULL);
46  lock_ = new  base::Lock();
47
48  DCHECK(thread_ == NULL);
49  thread_ = new base::Thread("Metro Print Handler");
50  thread_->Start();
51}
52
53PrintHandler::~PrintHandler() {
54  ClearPrintTask();
55  DCHECK(current_document_source_.Get() == NULL);
56
57  // Get all pending tasks to complete cleanly by Stopping the thread.
58  // They should complete quickly since current_document_source_ is NULL.
59  DCHECK(thread_ != NULL);
60  DCHECK(thread_->IsRunning());
61  thread_->Stop();
62  delete thread_;
63  thread_ = NULL;
64
65  DCHECK(lock_ != NULL);
66  delete lock_;
67  lock_ = NULL;
68}
69
70HRESULT PrintHandler::Initialize(winui::Core::ICoreWindow* window) {
71  // Register for Print notifications.
72  mswr::ComPtr<wingfx::Printing::IPrintManagerStatic> print_mgr_static;
73  HRESULT hr = winrt_utils::CreateActivationFactory(
74      RuntimeClass_Windows_Graphics_Printing_PrintManager,
75      print_mgr_static.GetAddressOf());
76  if (FAILED(hr)) {
77    LOG(ERROR) << "Failed to create PrintManagerStatic " << std::hex << hr;
78    return hr;
79  }
80
81  mswr::ComPtr<wingfx::Printing::IPrintManager> print_mgr;
82  hr = print_mgr_static->GetForCurrentView(&print_mgr);
83  if (FAILED(hr)) {
84    LOG(ERROR) << "Failed to get PrintManager for current view " << std::hex
85               << hr;
86    return hr;
87  }
88
89  hr = print_mgr->add_PrintTaskRequested(
90      mswr::Callback<PrintRequestedHandler>(
91          this, &PrintHandler::OnPrintRequested).Get(),
92      &print_requested_token_);
93  LOG_IF(ERROR, FAILED(hr)) << "Failed to register PrintTaskRequested "
94                            << std::hex << hr;
95
96  mswr::ComPtr<wingfx::Display::IDisplayPropertiesStatics> display_properties;
97  hr = winrt_utils::CreateActivationFactory(
98      RuntimeClass_Windows_Graphics_Display_DisplayProperties,
99      display_properties.GetAddressOf());
100  if (FAILED(hr)) {
101    LOG(ERROR) << "Failed to create DisplayPropertiesStatics " << std::hex
102               << hr;
103    return hr;
104  }
105
106  hr = display_properties->add_LogicalDpiChanged(
107      mswr::Callback<
108          wingfx::Display::IDisplayPropertiesEventHandler,
109          PrintHandler>(this, &PrintHandler::LogicalDpiChanged).Get(),
110      &dpi_change_token_);
111  LOG_IF(ERROR, FAILED(hr)) << "Failed to register LogicalDpiChanged "
112                            << std::hex << hr;
113
114  // This flag adds support for surfaces with a different color channel
115  // ordering than the API default. It is recommended usage, and is required
116  // for compatibility with Direct2D.
117  UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
118#if defined(_DEBUG)
119  creation_flags |= D3D11_CREATE_DEVICE_DEBUG;
120#endif
121
122  // This array defines the set of DirectX hardware feature levels we support.
123  // The ordering MUST be preserved. All applications are assumed to support
124  // 9.1 unless otherwise stated by the application, which is not our case.
125  D3D_FEATURE_LEVEL feature_levels[] = {
126    D3D_FEATURE_LEVEL_11_1,
127    D3D_FEATURE_LEVEL_11_0,
128    D3D_FEATURE_LEVEL_10_1,
129    D3D_FEATURE_LEVEL_10_0,
130    D3D_FEATURE_LEVEL_9_3,
131    D3D_FEATURE_LEVEL_9_2,
132    D3D_FEATURE_LEVEL_9_1 };
133
134  mswr::ComPtr<ID3D11Device> device;
135  mswr::ComPtr<ID3D11DeviceContext> context;
136  hr = D3D11CreateDevice(
137      NULL,  // Specify null to use the default adapter.
138      D3D_DRIVER_TYPE_HARDWARE,
139      0,  // Leave as 0 unless software device.
140      creation_flags,
141      feature_levels,
142      ARRAYSIZE(feature_levels),
143      D3D11_SDK_VERSION,  // Must always use this value in Metro apps.
144      &device,
145      NULL,  // Returns feature level of device created.
146      &context);
147  if (hr == DXGI_ERROR_UNSUPPORTED) {
148    // The hardware is not supported, try a reference driver instead.
149    hr = D3D11CreateDevice(
150        NULL,  // Specify null to use the default adapter.
151        D3D_DRIVER_TYPE_REFERENCE,
152        0,  // Leave as 0 unless software device.
153        creation_flags,
154        feature_levels,
155        ARRAYSIZE(feature_levels),
156        D3D11_SDK_VERSION,  // Must always use this value in Metro apps.
157        &device,
158        NULL,  // Returns feature level of device created.
159        &context);
160  }
161  if (FAILED(hr)) {
162    LOG(ERROR) << "Failed to create D3D11 device/context " << std::hex << hr;
163    return hr;
164  }
165
166  hr = device.As(&directx_context_.d3d_device);
167  if (FAILED(hr)) {
168    LOG(ERROR) << "Failed to QI D3D11 device " << std::hex << hr;
169    return hr;
170  }
171
172  D2D1_FACTORY_OPTIONS options;
173  ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
174
175#if defined(_DEBUG)
176  options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
177#endif
178
179  hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED,
180                         __uuidof(ID2D1Factory1),
181                         &options,
182                         &directx_context_.d2d_factory);
183  if (FAILED(hr)) {
184    LOG(ERROR) << "Failed to create D2D1 factory " << std::hex << hr;
185    return hr;
186  }
187
188  mswr::ComPtr<IDXGIDevice> dxgi_device;
189  hr = directx_context_.d3d_device.As(&dxgi_device);
190  if (FAILED(hr)) {
191    LOG(ERROR) << "Failed to QI for IDXGIDevice " << std::hex << hr;
192    return hr;
193  }
194
195  hr = directx_context_.d2d_factory->CreateDevice(
196      dxgi_device.Get(), &directx_context_.d2d_device);
197  if (FAILED(hr)) {
198    LOG(ERROR) << "Failed to Create D2DDevice " << std::hex << hr;
199    return hr;
200  }
201
202  hr = directx_context_.d2d_device->CreateDeviceContext(
203      D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
204      &directx_context_.d2d_context);
205  if (FAILED(hr)) {
206    LOG(ERROR) << "Failed to Create D2DDeviceContext " << std::hex << hr;
207    return hr;
208  }
209
210  hr = CoCreateInstance(CLSID_WICImagingFactory,
211                        NULL,
212                        CLSCTX_INPROC_SERVER,
213                        IID_PPV_ARGS(&directx_context_.wic_factory));
214  if (FAILED(hr)) {
215    LOG(ERROR) << "Failed to CoCreate WICImagingFactory " << std::hex << hr;
216    return hr;
217  }
218  return hr;
219}
220
221void PrintHandler::EnablePrinting(bool printing_enabled) {
222  thread_->message_loop()->PostTask(
223      FROM_HERE,
224      base::Bind(&PrintHandler::OnEnablePrinting, printing_enabled));
225}
226
227void PrintHandler::SetPageCount(size_t page_count) {
228  thread_->message_loop()->PostTask(
229      FROM_HERE,
230      base::Bind(&PrintHandler::OnSetPageCount, page_count));
231}
232
233void PrintHandler::AddPage(size_t page_number, IStream* metafile_stream) {
234  thread_->message_loop()->PostTask(
235      FROM_HERE,
236      base::Bind(&PrintHandler::OnAddPage,
237                 page_number,
238                 mswr::ComPtr<IStream>(metafile_stream)));
239}
240
241void PrintHandler::ShowPrintUI() {
242  // Post the print UI request over to the metro thread.
243  DCHECK(globals.appview_msg_loop != NULL);
244  bool posted = globals.appview_msg_loop->PostTask(
245      FROM_HERE, base::Bind(&metro_driver::PrintHandler::OnShowPrintUI));
246  DCHECK(posted);
247}
248
249HRESULT PrintHandler::OnPrintRequested(
250    wingfx::Printing::IPrintManager* print_mgr,
251    wingfx::Printing::IPrintTaskRequestedEventArgs* event_args) {
252  DVLOG(1) << __FUNCTION__;
253
254  HRESULT hr = S_OK;
255  if (printing_enabled_) {
256    mswr::ComPtr<wingfx::Printing::IPrintTaskRequest> print_request;
257    hr = event_args->get_Request(print_request.GetAddressOf());
258    if (FAILED(hr)) {
259      LOG(ERROR) << "Failed to get the Print Task request " << std::hex << hr;
260      return hr;
261    }
262
263    mswrw::HString title;
264    title.Attach(MakeHString(L"Printing"));
265    hr = print_request->CreatePrintTask(
266        title.Get(),
267        mswr::Callback<
268            wingfx::Printing::IPrintTaskSourceRequestedHandler,
269            PrintHandler>(this, &PrintHandler::OnPrintTaskSourceRequest).Get(),
270        print_task_.GetAddressOf());
271    if (FAILED(hr)) {
272      LOG(ERROR) << "Failed to create the Print Task " << std::hex << hr;
273      return hr;
274    }
275
276    hr = print_task_->add_Completed(
277        mswr::Callback<PrintTaskCompletedHandler>(
278            this, &PrintHandler::OnCompleted).Get(), &print_completed_token_);
279    LOG_IF(ERROR, FAILED(hr)) << "Failed to create the Print Task " << std::hex
280                              << hr;
281  }
282  return hr;
283}
284
285HRESULT PrintHandler::OnPrintTaskSourceRequest(
286    wingfx::Printing::IPrintTaskSourceRequestedArgs* args) {
287  DVLOG(1) << __FUNCTION__;
288  mswr::ComPtr<PrintDocumentSource> print_document_source;
289  HRESULT hr = mswr::MakeAndInitialize<PrintDocumentSource>(
290      &print_document_source, directx_context_, lock_);
291  if (FAILED(hr)) {
292    LOG(ERROR) << "Failed to create document source " << std::hex << hr;
293    return hr;
294  }
295
296  print_document_source->ResetDpi(GetLogicalDpi());
297
298  mswr::ComPtr<wingfx::Printing::IPrintDocumentSource> print_source;
299  hr = print_document_source.As(&print_source);
300  if (FAILED(hr)) {
301    LOG(ERROR) << "Failed to cast document Source " << std::hex << hr;
302    return hr;
303  }
304
305  hr = args->SetSource(print_source.Get());
306  if (FAILED(hr)) {
307    LOG(ERROR) << "Failed to set document Source " << std::hex << hr;
308    return hr;
309  }
310
311  thread_->message_loop()->PostTask(
312      FROM_HERE,
313      base::Bind(&PrintHandler::SetPrintDocumentSource,
314                 print_document_source));
315
316  return hr;
317}
318
319HRESULT PrintHandler::OnCompleted(
320    wingfx::Printing::IPrintTask* task,
321    wingfx::Printing::IPrintTaskCompletedEventArgs* args) {
322  DVLOG(1) << __FUNCTION__;
323  DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
324  ClearPrintTask();
325  thread_->message_loop()->PostTask(
326      FROM_HERE,
327      base::Bind(&PrintHandler::ReleasePrintDocumentSource));
328
329  return S_OK;
330}
331
332void PrintHandler::ClearPrintTask() {
333  if (!print_task_.Get())
334    return;
335
336  HRESULT hr = print_task_->remove_Completed(print_completed_token_);
337  LOG_IF(ERROR, FAILED(hr)) << "Failed to remove completed event from Task "
338                            << std::hex << hr;
339  print_task_.Reset();
340}
341
342float PrintHandler::GetLogicalDpi() {
343  mswr::ComPtr<wingfx::Display::IDisplayPropertiesStatics> display_properties;
344  HRESULT hr = winrt_utils::CreateActivationFactory(
345      RuntimeClass_Windows_Graphics_Display_DisplayProperties,
346      display_properties.GetAddressOf());
347  if (FAILED(hr)) {
348    LOG(ERROR) << "Failed to get display properties " << std::hex << hr;
349    return 0.0;
350  }
351
352  FLOAT dpi = 0.0;
353  hr = display_properties->get_LogicalDpi(&dpi);
354  LOG_IF(ERROR, FAILED(hr)) << "Failed to get Logical DPI " << std::hex << hr;
355
356  return dpi;
357}
358
359HRESULT PrintHandler::LogicalDpiChanged(IInspectable *sender) {
360  DVLOG(1) << __FUNCTION__;
361  thread_->message_loop()->PostTask(
362      FROM_HERE,
363      base::Bind(&PrintHandler::OnLogicalDpiChanged, GetLogicalDpi()));
364  return S_OK;
365}
366
367void PrintHandler::OnLogicalDpiChanged(float dpi) {
368  DCHECK(base::MessageLoop::current() == thread_->message_loop());
369  // No need to protect the access to the static variable,
370  // since it's set/released in this same thread.
371  if (current_document_source_.Get() != NULL)
372    current_document_source_->ResetDpi(dpi);
373}
374
375void PrintHandler::SetPrintDocumentSource(
376    const mswr::ComPtr<PrintDocumentSource>& print_document_source) {
377  DCHECK(base::MessageLoop::current() == thread_->message_loop());
378  DCHECK(current_document_source_.Get() == NULL);
379  {
380    // Protect against the other thread which might try to access it.
381    base::AutoLock lock(*lock_);
382    current_document_source_ = print_document_source;
383  }
384  // Start generating the images to print.
385  // TODO(mad): Use a registered message with more information about the print
386  // request, and at a more appropriate time too, and maybe one page at a time.
387  ::PostMessageW(globals.host_windows.front().first,
388                 WM_SYSCOMMAND,
389                 IDC_PRINT_TO_DESTINATION,
390                 0);
391}
392
393void PrintHandler::ReleasePrintDocumentSource() {
394  DCHECK(base::MessageLoop::current() == thread_->message_loop());
395  mswr::ComPtr<PrintDocumentSource> print_document_source;
396  {
397    // Must wait for other thread to be done with the pointer first.
398    base::AutoLock lock(*lock_);
399    current_document_source_.Swap(print_document_source);
400  }
401  // This may happen before we get a chance to set the value.
402  if (print_document_source.Get() != NULL)
403    print_document_source->Abort();
404}
405
406void PrintHandler::OnEnablePrinting(bool printing_enabled) {
407  DCHECK(base::MessageLoop::current() == thread_->message_loop());
408  base::AutoLock lock(*lock_);
409  printing_enabled_ = printing_enabled;
410  // Don't abort if we are being disabled since we may be finishing a previous
411  // print request which was valid and should be finished. We just need to
412  // prevent any new print requests. And don't assert that we are NOT printing
413  // if we are becoming enabled since we may be finishing a long print while
414  // we got disabled and then enabled again...
415}
416
417void PrintHandler::OnSetPageCount(size_t page_count) {
418  DCHECK(base::MessageLoop::current() == thread_->message_loop());
419  // No need to protect the access to the static variable,
420  // since it's set/released in this same thread.
421  if (current_document_source_.Get() != NULL)
422    current_document_source_->SetPageCount(page_count);
423}
424
425void PrintHandler::OnAddPage(size_t page_number,
426                             mswr::ComPtr<IStream> metafile_stream) {
427  DCHECK(base::MessageLoop::current() == thread_->message_loop());
428  // No need to protect the access to the static variable,
429  // since it's set/released in this same thread.
430  if (current_document_source_.Get() != NULL)
431    current_document_source_->AddPage(page_number, metafile_stream.Get());
432}
433
434void PrintHandler::OnShowPrintUI() {
435  DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
436  mswr::ComPtr<wingfx::Printing::IPrintManagerStatic> print_mgr_static;
437  HRESULT hr = winrt_utils::CreateActivationFactory(
438      RuntimeClass_Windows_Graphics_Printing_PrintManager,
439      print_mgr_static.GetAddressOf());
440  if (SUCCEEDED(hr)) {
441    DCHECK(print_mgr_static.Get() != NULL);
442    // Note that passing NULL to ShowPrintUIAsync crashes,
443    // so we need to setup a temp pointer.
444    mswr::ComPtr<winfoundtn::IAsyncOperation<bool>> unused_async_op;
445    hr = print_mgr_static->ShowPrintUIAsync(unused_async_op.GetAddressOf());
446    LOG_IF(ERROR, FAILED(hr)) << "Failed to ShowPrintUIAsync "
447                              << std::hex << std::showbase << hr;
448  } else {
449    LOG(ERROR) << "Failed to create PrintManagerStatic "
450               << std::hex << std::showbase << hr;
451  }
452}
453
454}  // namespace metro_driver
455
456void MetroEnablePrinting(BOOL printing_enabled) {
457  metro_driver::PrintHandler::EnablePrinting(!!printing_enabled);
458}
459
460void MetroSetPrintPageCount(size_t page_count) {
461  DVLOG(1) << __FUNCTION__ << " Page count: " << page_count;
462  metro_driver::PrintHandler::SetPageCount(page_count);
463}
464
465void MetroSetPrintPageContent(size_t page_number,
466                              void* data,
467                              size_t data_size) {
468  DVLOG(1) << __FUNCTION__ << " Page number: " << page_number;
469  DCHECK(data != NULL);
470  DCHECK(data_size > 0);
471  mswr::ComPtr<IStream> metafile_stream;
472  HRESULT hr = ::CreateStreamOnHGlobal(
473      NULL, TRUE, metafile_stream.GetAddressOf());
474  if (metafile_stream.Get() != NULL) {
475    ULONG bytes_written = 0;
476    hr = metafile_stream->Write(data,
477                                base::checked_cast<ULONG>(data_size),
478                                &bytes_written);
479    LOG_IF(ERROR, FAILED(hr)) << "Failed to Write to Stream " << std::hex << hr;
480    DCHECK(bytes_written == data_size);
481
482    metro_driver::PrintHandler::AddPage(page_number, metafile_stream.Get());
483  } else {
484    NOTREACHED() << "Failed to CreateStreamOnHGlobal " << std::hex << hr;
485  }
486}
487
488void MetroShowPrintUI() {
489  metro_driver::PrintHandler::ShowPrintUI();
490}
491