1// Copyright 2013 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 <atlbase.h>
6#include <atlapp.h>  // NOLINT
7
8#include "base/at_exit.h"
9#include "base/bind.h"
10#include "base/callback_helpers.h"
11#include "base/command_line.h"
12#include "base/files/file_util.h"
13#include "base/message_loop/message_loop.h"
14#include "base/message_loop/message_pump_dispatcher.h"
15#include "base/run_loop.h"
16#include "base/strings/string16.h"
17#include "base/threading/thread.h"
18#include "chrome/common/chrome_constants.h"
19#include "cloud_print/common/win/cloud_print_utils.h"
20#include "cloud_print/resources.h"
21#include "cloud_print/service/service_state.h"
22#include "cloud_print/service/win/chrome_launcher.h"
23#include "cloud_print/service/win/service_controller.h"
24#include "cloud_print/service/win/service_utils.h"
25#include "cloud_print/service/win/setup_listener.h"
26
27using cloud_print::LoadLocalString;
28using cloud_print::GetErrorMessage;
29
30class SetupDialog : public base::RefCounted<SetupDialog>,
31                    public ATL::CDialogImpl<SetupDialog> {
32 public:
33  // Enables accelerators.
34  class Dispatcher : public base::MessagePumpDispatcher {
35   public:
36    explicit Dispatcher(SetupDialog* dialog) : dialog_(dialog) {}
37    virtual ~Dispatcher() {};
38
39    // MessagePumpDispatcher:
40    virtual uint32_t Dispatch(const MSG& msg) OVERRIDE {
41      MSG msg2 = msg;
42      uint32_t action = POST_DISPATCH_NONE;
43      if (!dialog_->IsDialogMessage(&msg2))
44        action = POST_DISPATCH_PERFORM_DEFAULT;
45      return action;
46    }
47
48   private:
49    scoped_refptr<SetupDialog> dialog_;
50  };
51
52  typedef ATL::CDialogImpl<SetupDialog> Base;
53  enum { IDD = IDD_SETUP_DIALOG };
54
55  BEGIN_MSG_MAP(SetupDialog)
56    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
57    MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnCtrColor)
58    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
59    COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
60    COMMAND_ID_HANDLER(IDC_START, OnStart)
61    COMMAND_ID_HANDLER(IDC_INSTALL, OnInstall)
62    COMMAND_ID_HANDLER(IDC_LOGGING, OnLogging)
63  END_MSG_MAP()
64
65  SetupDialog();
66 private:
67  // Window Message Handlers
68  LRESULT OnInitDialog(UINT message, WPARAM wparam, LPARAM lparam,
69                       BOOL& handled);
70  LRESULT OnCtrColor(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
71  LRESULT OnCancel(UINT, INT nIdentifier, HWND, BOOL& handled);
72  LRESULT OnStart(UINT, INT nIdentifier, HWND, BOOL& handled);
73  LRESULT OnInstall(UINT, INT nIdentifier, HWND, BOOL& handled);
74  LRESULT OnLogging(UINT, INT nIdentifier, HWND, BOOL& handled);
75  LRESULT OnDestroy(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
76
77  void PostUITask(const base::Closure& task);
78  void PostIOTask(const base::Closure& task);
79
80  // UI Calls.
81
82  // Disables all controls after users actions.
83  void DisableControls();
84  // Updates state of controls after when we received service status.
85  void SetState(ServiceController::State state, const base::string16& user,
86                 bool is_logging_enabled);
87  // Show message box with error.
88  void ShowErrorMessageBox(const base::string16& error_message);
89  // Show use message box instructions how to deal with opened Chrome window.
90  void AskToCloseChrome();
91  base::string16 GetDlgItemText(int id) const;
92  base::string16 GetUser() const;
93  base::string16 GetPassword() const;
94  bool IsLoggingEnabled() const;
95  bool IsInstalled() const {
96    return state_ > ServiceController::STATE_NOT_FOUND;
97  }
98
99  // IO Calls.
100  // Installs service.
101  void Install(const base::string16& user, const base::string16& password,
102               bool enable_logging);
103  // Starts service.
104  void Start();
105  // Stops service.
106  void Stop();
107  // Uninstall service.
108  void Uninstall();
109  // Update service state.
110  void UpdateState();
111  // Posts task to UI thread to show error using string id.
112  void ShowError(int string_id);
113  // Posts task to UI thread to show error using string.
114  void ShowError(const base::string16& error_message);
115  // Posts task to UI thread to show error using error code.
116  void ShowError(HRESULT hr);
117
118  ServiceController::State state_;
119  base::Thread worker_;
120
121  base::MessageLoop* ui_loop_;
122  base::MessageLoop* io_loop_;
123
124  ServiceController controller_;
125};
126
127SetupDialog::SetupDialog()
128    : state_(ServiceController::STATE_NOT_FOUND),
129      worker_("worker") {
130  ui_loop_ = base::MessageLoop::current();
131  DCHECK(base::MessageLoopForUI::IsCurrent());
132
133  worker_.StartWithOptions(
134      base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
135  io_loop_ = worker_.message_loop();
136  DCHECK(io_loop_->IsType(base::MessageLoop::TYPE_IO));
137}
138
139void SetupDialog::PostUITask(const base::Closure& task) {
140  ui_loop_->PostTask(FROM_HERE, task);
141}
142
143void SetupDialog::PostIOTask(const base::Closure& task) {
144  io_loop_->PostTask(FROM_HERE, task);
145}
146
147void SetupDialog::ShowErrorMessageBox(const base::string16& error_message) {
148  DCHECK(base::MessageLoopForUI::IsCurrent());
149  MessageBox(error_message.c_str(),
150             LoadLocalString(IDS_OPERATION_FAILED_TITLE).c_str(),
151             MB_ICONERROR | MB_OK);
152}
153
154void SetupDialog::AskToCloseChrome() {
155  DCHECK(base::MessageLoopForUI::IsCurrent());
156  MessageBox(LoadLocalString(IDS_ADD_PRINTERS_USING_CHROME).c_str(),
157             LoadLocalString(IDS_CONTINUE_IN_CHROME_TITLE).c_str(),
158             MB_OK);
159}
160
161void SetupDialog::SetState(ServiceController::State status,
162                           const base::string16& user,
163                           bool is_logging_enabled) {
164  DCHECK(base::MessageLoopForUI::IsCurrent());
165  state_ = status;
166
167  DWORD status_string = 0;
168  switch(status) {
169  case ServiceController::STATE_NOT_FOUND:
170    status_string = IDS_SERVICE_NOT_FOUND;
171    break;
172  case ServiceController::STATE_STOPPED:
173    status_string = IDS_SERVICE_STOPPED;
174    break;
175  case ServiceController::STATE_RUNNING:
176    status_string = IDS_SERVICE_RUNNING;
177    break;
178  }
179  SetDlgItemText(IDC_STATUS,
180                 status_string ? LoadLocalString(status_string).c_str() : L"");
181  if (IsInstalled()) {
182    SetDlgItemText(IDC_USER, user.c_str());
183    CheckDlgButton(IDC_LOGGING,
184                   is_logging_enabled ? BST_CHECKED : BST_UNCHECKED);
185  }
186
187  ATL::CWindow start_button = GetDlgItem(IDC_START);
188  DWORD start_string = (status == ServiceController::STATE_STOPPED) ?
189                       IDS_SERVICE_START : IDS_SERVICE_STOP;
190  start_button.SetWindowText(LoadLocalString(start_string).c_str());
191  start_button.ShowWindow(IsInstalled() ? SW_SHOW : SW_HIDE);
192  start_button.EnableWindow(TRUE);
193
194  ATL::CWindow install_button = GetDlgItem(IDC_INSTALL);
195  DWORD install_string = IsInstalled() ? IDS_SERVICE_UNINSTALL :
196                                         IDS_SERVICE_INSTALL;
197  install_button.SetWindowText(LoadLocalString(install_string).c_str());
198  install_button.ShowWindow(SW_SHOW);
199  install_button.EnableWindow(TRUE);
200
201  if (!IsInstalled()) {
202    GetDlgItem(IDC_USER).EnableWindow(TRUE);
203    GetDlgItem(IDC_PASSWORD).EnableWindow(TRUE);
204    GetDlgItem(IDC_LOGGING).EnableWindow(TRUE);
205  }
206}
207
208LRESULT SetupDialog::OnInitDialog(UINT message, WPARAM wparam, LPARAM lparam,
209                                  BOOL& handled) {
210  ATLVERIFY(CenterWindow());
211
212  WTL::CIcon icon;
213  if (icon.LoadIcon(MAKEINTRESOURCE(IDI_ICON))) {
214    SetIcon(icon);
215  }
216
217  SetWindowText(LoadLocalString(IDS_SETUP_PROGRAM_NAME).c_str());
218  SetDlgItemText(IDC_STATE_LABEL, LoadLocalString(IDS_STATE_LABEL).c_str());
219  SetDlgItemText(IDC_USER_LABEL, LoadLocalString(IDS_USER_LABEL).c_str());
220  SetDlgItemText(IDC_PASSWORD_LABEL,
221                 LoadLocalString(IDS_PASSWORD_LABEL).c_str());
222  SetDlgItemText(IDC_LOGGING, LoadLocalString(IDS_LOGGING_LABEL).c_str());
223  SetDlgItemText(IDCANCEL, LoadLocalString(IDS_CLOSE).c_str());
224
225  SetState(ServiceController::STATE_UNKNOWN, L"", false);
226  DisableControls();
227
228  SetDlgItemText(IDC_USER, GetCurrentUserName().c_str());
229
230  PostIOTask(base::Bind(&SetupDialog::UpdateState, this));
231
232  return 0;
233}
234
235LRESULT SetupDialog::OnCtrColor(UINT message, WPARAM wparam, LPARAM lparam,
236                                BOOL& handled) {
237  HWND window = reinterpret_cast<HWND>(lparam);
238  if (GetDlgItem(IDC_LOGO).m_hWnd == window) {
239    return reinterpret_cast<LRESULT>(::GetStockObject(WHITE_BRUSH));
240  }
241  return 0;
242}
243
244LRESULT SetupDialog::OnStart(UINT, INT nIdentifier, HWND, BOOL& handled) {
245  DisableControls();
246  DCHECK(IsInstalled());
247  if (state_ == ServiceController::STATE_RUNNING)
248    PostIOTask(base::Bind(&SetupDialog::Stop, this));
249  else
250    PostIOTask(base::Bind(&SetupDialog::Start, this));
251  return 0;
252}
253
254LRESULT SetupDialog::OnInstall(UINT, INT nIdentifier, HWND, BOOL& handled) {
255  DisableControls();
256  if (IsInstalled()) {
257    PostIOTask(base::Bind(&SetupDialog::Uninstall, this));
258  } else {
259    PostIOTask(base::Bind(&SetupDialog::Install, this, GetUser(),
260                          GetPassword(), IsLoggingEnabled()));
261  }
262  return 0;
263}
264
265LRESULT SetupDialog::OnLogging(UINT, INT nIdentifier, HWND, BOOL& handled) {
266  CheckDlgButton(IDC_LOGGING, IsLoggingEnabled()? BST_UNCHECKED : BST_CHECKED);
267  return 0;
268}
269
270LRESULT SetupDialog::OnCancel(UINT, INT nIdentifier, HWND, BOOL& handled) {
271  DestroyWindow();
272  return 0;
273}
274
275LRESULT SetupDialog::OnDestroy(UINT message, WPARAM wparam, LPARAM lparam,
276                               BOOL& handled) {
277  base::MessageLoop::current()->PostTask(FROM_HERE,
278                                         base::MessageLoop::QuitClosure());
279  return 1;
280}
281
282void SetupDialog::DisableControls() {
283  GetDlgItem(IDC_START).EnableWindow(FALSE);
284  GetDlgItem(IDC_INSTALL).EnableWindow(FALSE);
285  GetDlgItem(IDC_USER).EnableWindow(FALSE);
286  GetDlgItem(IDC_PASSWORD).EnableWindow(FALSE);
287  GetDlgItem(IDC_LOGGING).EnableWindow(FALSE);
288}
289
290base::string16 SetupDialog::GetDlgItemText(int id) const {
291  const ATL::CWindow& item = GetDlgItem(id);
292  size_t length = item.GetWindowTextLength();
293  base::string16 result(length + 1, L'\0');
294  result.resize(item.GetWindowText(&result[0], result.size()));
295  return result;
296}
297
298base::string16 SetupDialog::GetUser() const {
299  return GetDlgItemText(IDC_USER);
300}
301
302base::string16 SetupDialog::GetPassword() const {
303  return GetDlgItemText(IDC_PASSWORD);
304}
305
306bool SetupDialog::IsLoggingEnabled() const{
307  return IsDlgButtonChecked(IDC_LOGGING) == BST_CHECKED;
308}
309
310void SetupDialog::UpdateState() {
311  DCHECK(base::MessageLoopForIO::IsCurrent());
312  controller_.UpdateState();
313  PostUITask(base::Bind(&SetupDialog::SetState, this, controller_.state(),
314                        controller_.user(), controller_.is_logging_enabled()));
315}
316
317void SetupDialog::ShowError(const base::string16& error_message) {
318  DCHECK(base::MessageLoopForIO::IsCurrent());
319  PostUITask(base::Bind(&SetupDialog::SetState,
320                        this,
321                        ServiceController::STATE_UNKNOWN,
322                        L"",
323                        false));
324  PostUITask(base::Bind(&SetupDialog::ShowErrorMessageBox, this,
325                        error_message));
326  LOG(ERROR) << error_message;
327}
328
329void SetupDialog::ShowError(int string_id) {
330  ShowError(cloud_print::LoadLocalString(string_id));
331}
332
333void SetupDialog::ShowError(HRESULT hr) {
334  ShowError(GetErrorMessage(hr));
335}
336
337void SetupDialog::Install(const base::string16& user,
338                          const base::string16& password,
339                          bool enable_logging) {
340  // Don't forget to update state on exit.
341  base::ScopedClosureRunner scoped_update_status(
342        base::Bind(&SetupDialog::UpdateState, this));
343
344  DCHECK(base::MessageLoopForIO::IsCurrent());
345
346  SetupListener setup(GetUser());
347  HRESULT hr = controller_.InstallCheckService(user, password,
348                                               base::FilePath());
349  if (FAILED(hr))
350    return ShowError(hr);
351
352  {
353    // Always uninstall service after requirements check.
354    base::ScopedClosureRunner scoped_uninstall(
355        base::Bind(base::IgnoreResult(&ServiceController::UninstallService),
356                   base::Unretained(&controller_)));
357
358    hr = controller_.StartService();
359    if (FAILED(hr))
360      return ShowError(hr);
361
362    if (!setup.WaitResponce(base::TimeDelta::FromSeconds(30)))
363      return ShowError(IDS_ERROR_FAILED_START_SERVICE);
364  }
365
366  if (setup.user_data_dir().empty())
367    return ShowError(IDS_ERROR_NO_DATA_DIR);
368
369  if (setup.chrome_path().empty())
370    return ShowError(IDS_ERROR_NO_CHROME);
371
372  if (!setup.is_xps_available())
373    return ShowError(IDS_ERROR_NO_XPS);
374
375  base::FilePath file = setup.user_data_dir();
376  file = file.Append(chrome::kServiceStateFileName);
377
378  std::string proxy_id;
379  std::string contents;
380
381  if (base::ReadFileToString(file, &contents)) {
382    ServiceState service_state;
383    if (service_state.FromString(contents))
384      proxy_id = service_state.proxy_id();
385  }
386  PostUITask(base::Bind(&SetupDialog::AskToCloseChrome, this));
387  contents = ChromeLauncher::CreateServiceStateFile(proxy_id, setup.printers());
388
389  if (contents.empty())
390    return ShowError(IDS_ERROR_FAILED_CREATE_CONFIG);
391
392  size_t written = base::WriteFile(file, contents.c_str(),
393                                        contents.size());
394  if (written != contents.size()) {
395    DWORD last_error = GetLastError();
396    if (!last_error)
397      return ShowError(IDS_ERROR_FAILED_CREATE_CONFIG);
398    return ShowError(HRESULT_FROM_WIN32(last_error));
399  }
400
401  hr = controller_.InstallConnectorService(user, password, base::FilePath(),
402                                           enable_logging);
403  if (FAILED(hr))
404    return ShowError(hr);
405
406  hr = controller_.StartService();
407  if (FAILED(hr))
408    return ShowError(hr);
409}
410
411void SetupDialog::Start() {
412  DCHECK(base::MessageLoopForIO::IsCurrent());
413  HRESULT hr = controller_.StartService();
414  if (FAILED(hr))
415    ShowError(hr);
416  UpdateState();
417}
418
419void SetupDialog::Stop() {
420  DCHECK(base::MessageLoopForIO::IsCurrent());
421  HRESULT hr = controller_.StopService();
422  if (FAILED(hr))
423    ShowError(hr);
424  UpdateState();
425}
426
427void SetupDialog::Uninstall() {
428  DCHECK(base::MessageLoopForIO::IsCurrent());
429  HRESULT hr = controller_.UninstallService();
430  if (FAILED(hr))
431    ShowError(hr);
432  UpdateState();
433}
434
435class CloudPrintServiceConfigModule
436    : public ATL::CAtlExeModuleT<CloudPrintServiceConfigModule> {
437};
438
439CloudPrintServiceConfigModule _AtlModule;
440
441int WINAPI WinMain(__in  HINSTANCE hInstance,
442                   __in  HINSTANCE hPrevInstance,
443                   __in  LPSTR lpCmdLine,
444                   __in  int nCmdShow) {
445  base::AtExitManager at_exit;
446  CommandLine::Init(0, NULL);
447
448  base::MessageLoopForUI loop;
449  scoped_refptr<SetupDialog> dialog(new SetupDialog());
450  dialog->Create(NULL);
451  dialog->ShowWindow(SW_SHOW);
452  SetupDialog::Dispatcher dispatcher(dialog);
453  base::RunLoop run_loop(&dispatcher);
454  run_loop.Run();
455  return 0;
456}
457