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