1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2014 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
17 //
18 #if defined(__GNUG__) && !defined(__APPLE__)
19 #pragma implementation "DlgExclusiveApps.h"
20 #endif
21 
22 #include "stdwx.h"
23 #include "BOINCGUIApp.h"
24 #include "MainDocument.h"
25 #include "BOINCBaseFrame.h"
26 #include "SkinManager.h"
27 #include "Events.h"
28 #include "error_numbers.h"
29 #include "version.h"
30 #include "DlgExclusiveApps.h"
31 
32 
33 using std::string;
34 
IMPLEMENT_DYNAMIC_CLASS(CDlgExclusiveApps,wxDialog)35 IMPLEMENT_DYNAMIC_CLASS(CDlgExclusiveApps, wxDialog)
36 
37 BEGIN_EVENT_TABLE(CDlgExclusiveApps, wxDialog)
38     EVT_COMMAND(ID_LISTBOX_EXCLAPPS,wxEVT_COMMAND_LISTBOX_SELECTED,CDlgExclusiveApps::OnExclusiveAppListEvent)
39     EVT_COMMAND(ID_LISTBOX_EXCLGPUAPPS,wxEVT_COMMAND_LISTBOX_SELECTED,CDlgExclusiveApps::OnExclusiveGPUAppListEvent)
40     //buttons
41     EVT_BUTTON(ID_ADDEXCLUSIVEAPPBUTTON,CDlgExclusiveApps::OnAddExclusiveApp)
42     EVT_BUTTON(ID_REMOVEEXCLUSIVEAPPBUTTON,CDlgExclusiveApps::OnRemoveExclusiveApp)
43     EVT_BUTTON(ID_ADDEXCLUSIVEGPUAPPBUTTON,CDlgExclusiveApps::OnAddExclusiveGPUApp)
44     EVT_BUTTON(ID_REMOVEEXCLUSIVEGPUAPPBUTTON,CDlgExclusiveApps::OnRemoveExclusiveGPUApp)
45     EVT_BUTTON(wxID_OK,CDlgExclusiveApps::OnOK)
46     EVT_BUTTON(ID_HELPBOINC,CDlgExclusiveApps::OnHelp)
47 END_EVENT_TABLE()
48 
49 /* Constructor */
50 CDlgExclusiveApps::CDlgExclusiveApps(wxWindow* parent) :
51     wxDialog( parent, ID_ANYDIALOG, wxEmptyString, wxDefaultPosition,
52     wxDefaultSize, wxDEFAULT_DIALOG_STYLE
53     ) {
54     CSkinAdvanced* pSkinAdvanced = wxGetApp().GetSkinManager()->GetAdvanced();
55     wxASSERT(pSkinAdvanced);
56     wxASSERT(wxDynamicCast(pSkinAdvanced, CSkinAdvanced));
57 
58     wxString title;
59     title.Printf(
60         _("%s - Exclusive Applications"),
61         pSkinAdvanced->GetApplicationShortName().c_str()
62     );
63 
64     SetTitle(title);
65 
66     m_bInInit=false;
67     m_bExclusiveAppsDataChanged=false;
68 
69     wxBoxSizer* dialogSizer = new wxBoxSizer( wxVERTICAL );
70     dialogSizer->AddSpacer(10);
71 
72     wxStaticBox* exclusiveAppsListStaticBox = new wxStaticBox( this, -1, _("Suspend processor and network usage when these applications are running:") );
73     wxStaticBoxSizer* exclusiveAppsListBoxSizer = new wxStaticBoxSizer( exclusiveAppsListStaticBox, wxVERTICAL );
74 
75     m_exclusiveApsListBox = new wxListBox(exclusiveAppsListStaticBox, ID_LISTBOX_EXCLAPPS, wxDefaultPosition, wxSize(-1, 145), 0, NULL, wxLB_EXTENDED|wxLB_NEEDED_SB|wxLB_SORT);
76     exclusiveAppsListBoxSizer->Add(m_exclusiveApsListBox, 1, wxALL|wxEXPAND, 5);
77 
78 	wxBoxSizer* exclusiveAppsButtonSizer = new wxBoxSizer( wxHORIZONTAL );
79 
80     m_addExclusiveAppButton = new wxButton( exclusiveAppsListStaticBox, ID_ADDEXCLUSIVEAPPBUTTON, _("Add..."), wxDefaultPosition, wxDefaultSize, 0 );
81     m_addExclusiveAppButton->SetToolTip( _("Add an application to this list"));
82 	exclusiveAppsButtonSizer->Add( m_addExclusiveAppButton, 0, wxRIGHT, 5 );
83 
84     exclusiveAppsButtonSizer->AddStretchSpacer();
85 
86     m_removeExclusiveAppButton = new wxButton( exclusiveAppsListStaticBox, ID_REMOVEEXCLUSIVEAPPBUTTON, _("Remove"), wxDefaultPosition, wxDefaultSize, 0 );
87     m_removeExclusiveAppButton->SetToolTip( _("Remove an application from this list"));
88 	exclusiveAppsButtonSizer->Add( m_removeExclusiveAppButton, 0, wxLEFT, 5 );
89 
90     exclusiveAppsListBoxSizer->Add(exclusiveAppsButtonSizer, 0, wxEXPAND|wxLEFT|wxRIGHT, 25 );
91 
92     dialogSizer->Add( exclusiveAppsListBoxSizer, 0, wxALL|wxEXPAND, 1 );
93 
94     dialogSizer->AddSpacer(25);
95 
96     wxStaticBox* exclusiveGPUAppsListStaticBox = new wxStaticBox( this, -1, _("Suspend GPU usage when these applications are running:") );
97     wxStaticBoxSizer* exclusiveGPUAppsListBoxSizer = new wxStaticBoxSizer( exclusiveGPUAppsListStaticBox, wxVERTICAL );
98 
99     m_exclusiveGPUApsListBox = new wxListBox(exclusiveGPUAppsListStaticBox, ID_LISTBOX_EXCLGPUAPPS, wxDefaultPosition, wxSize(-1, 145), 0, NULL, wxLB_EXTENDED|wxLB_NEEDED_SB|wxLB_SORT);
100     exclusiveGPUAppsListBoxSizer->Add(m_exclusiveGPUApsListBox, 1, wxALL|wxEXPAND, 5);
101 
102 	wxBoxSizer* exclusiveGPUAppsButtonSizer = new wxBoxSizer( wxHORIZONTAL );
103 
104     m_addExclusiveGPUAppButton = new wxButton( exclusiveGPUAppsListStaticBox, ID_ADDEXCLUSIVEGPUAPPBUTTON, _("Add..."), wxDefaultPosition, wxDefaultSize, 0 );
105     m_addExclusiveGPUAppButton->SetToolTip( _("Add an application to this list"));
106 	exclusiveGPUAppsButtonSizer->Add( m_addExclusiveGPUAppButton, 0, wxRIGHT, 5 );
107 
108     exclusiveGPUAppsButtonSizer->AddStretchSpacer();
109 
110     m_removeExclusiveGPUAppButton = new wxButton( exclusiveGPUAppsListStaticBox, ID_REMOVEEXCLUSIVEGPUAPPBUTTON, _("Remove"), wxDefaultPosition, wxDefaultSize, 0 );
111     m_removeExclusiveGPUAppButton->SetToolTip( _("Remove an application from this list"));
112 	exclusiveGPUAppsButtonSizer->Add( m_removeExclusiveGPUAppButton, 0, wxLEFT, 5 );
113 
114     exclusiveGPUAppsListBoxSizer->Add(exclusiveGPUAppsButtonSizer, 0, wxEXPAND|wxLEFT|wxRIGHT, 25 );
115 
116     dialogSizer->Add( exclusiveGPUAppsListBoxSizer, 0, wxALL|wxEXPAND, 1 );
117 
118     wxBoxSizer* moreOptionsLinkSizer = new wxBoxSizer( wxHORIZONTAL );
119 
120     moreOptionsLinkSizer->Add(
121         new wxStaticText(
122             this, wxID_ANY, _("For advanced options, refer to "),
123             wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT
124         ),
125         0, wxLEFT, 5
126     );
127 
128     moreOptionsLinkSizer->Add(
129         new wxHyperlinkCtrl(
130             this, wxID_ANY, wxT("http://boinc.berkeley.edu/wiki/Client_configuration"),
131             wxT("http://boinc.berkeley.edu/wiki/Client_configuration"),
132             wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE
133         ),
134 #ifdef __WXMAC__
135         0, wxLEFT, 5
136 #else
137         0, wxLEFT, 3
138 #endif
139     );
140 
141     dialogSizer->Add(moreOptionsLinkSizer, 0, wxALL, 10);
142 
143     m_panelButtons = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
144     wxBoxSizer* buttonSizer = new wxBoxSizer( wxHORIZONTAL );
145 
146     m_btnOK = new wxButton( m_panelButtons, wxID_OK, _("OK"), wxDefaultPosition, wxDefaultSize, 0 );
147     m_btnOK->SetToolTip( _("save all values and close the dialog") );
148     m_btnOK->SetDefault();
149 
150     buttonSizer->Add( m_btnOK, 0, wxALL, 5 );
151 
152     m_btnCancel = new wxButton( m_panelButtons, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
153     m_btnCancel->SetToolTip( _("close the dialog without saving") );
154 
155     buttonSizer->Add( m_btnCancel, 0, wxALL, 5 );
156 
157     m_btnHelp = new wxButton( m_panelButtons, ID_HELPBOINC, _("Help"), wxDefaultPosition, wxDefaultSize, 0 );
158     m_btnHelp->SetToolTip( _("shows the preferences web page") );
159 
160     buttonSizer->Add( m_btnHelp, 0, wxALL, 5 );
161 
162     m_panelButtons->SetSizer( buttonSizer );
163     m_panelButtons->Layout();
164     buttonSizer->Fit( m_panelButtons );
165     dialogSizer->Add( m_panelButtons, 0, wxALIGN_BOTTOM|wxALIGN_CENTER_HORIZONTAL|wxALL, 1 );
166 
167     m_removeExclusiveAppButton->Disable();
168     m_removeExclusiveGPUAppButton->Disable();
169 
170     ReadPreferenceSettings();
171 
172 // CAF  SetSizerAndFit(dialogSizer);
173      Layout();
174      dialogSizer->Fit( this );
175      SetSizer( dialogSizer );
176 
177 // CAF    Fit();
178     Centre();
179 }
180 
181 /* destructor */
~CDlgExclusiveApps()182 CDlgExclusiveApps::~CDlgExclusiveApps() {
183 }
184 
185 /* read preferences from core client and initialize control values */
ReadPreferenceSettings()186 void CDlgExclusiveApps::ReadPreferenceSettings() {
187     m_bInInit=true;//prevent dialog handlers from doing anything
188     CMainDocument* pDoc = wxGetApp().GetDocument();
189     int retval;
190 
191     wxASSERT(pDoc);
192     wxASSERT(wxDynamicCast(pDoc, CMainDocument));
193 
194     // Get cc_config.xml file flags
195     log_flags.init();
196     config.defaults();
197     retval = pDoc->rpc.get_cc_config(config, log_flags);
198     if (!retval) {
199         for (unsigned int i=0; i<config.exclusive_apps.size(); ++i) {
200             wxString appName = wxString(config.exclusive_apps[i].c_str(), wxConvUTF8);
201             m_exclusiveApsListBox->Append(appName);
202         }
203 
204         for (unsigned int i=0; i<config.exclusive_gpu_apps.size(); ++i) {
205             wxString appName = wxString(config.exclusive_gpu_apps[i].c_str(), wxConvUTF8);
206             m_exclusiveGPUApsListBox->Append(appName);
207         }
208     }
209 
210     m_bInInit=false;
211 }
212 
213 
SavePreferencesSettings()214 bool CDlgExclusiveApps::SavePreferencesSettings() {
215     if (m_bExclusiveAppsDataChanged) {
216         CMainDocument*    pDoc = wxGetApp().GetDocument();
217 
218         wxASSERT(pDoc);
219         wxASSERT(wxDynamicCast(pDoc, CMainDocument));
220 
221         wxArrayString appNames = m_exclusiveApsListBox->GetStrings();
222 
223         config.exclusive_apps.clear();
224         for (unsigned int i=0; i<appNames.size(); ++i) {
225             std::string s = (const char*)appNames[i].mb_str();
226             config.exclusive_apps.push_back(s);
227         }
228 
229        wxArrayString gpuAppNames = m_exclusiveGPUApsListBox->GetStrings();
230         config.exclusive_gpu_apps.clear();
231         for (unsigned int i=0; i<gpuAppNames.size(); ++i) {
232             std::string s = (const char*)gpuAppNames[i].mb_str();
233             config.exclusive_gpu_apps.push_back(s);
234         }
235         int retval = pDoc->rpc.set_cc_config(config, log_flags);
236         if (!retval) {
237             pDoc->rpc.read_cc_config();
238         }
239         return true;
240     }
241     return false;
242 }
243 
244 // ------------ Event handlers starts here
245 // ---- Exclusive Apps list box handler
OnExclusiveAppListEvent(wxCommandEvent & ev)246 void CDlgExclusiveApps::OnExclusiveAppListEvent(wxCommandEvent& ev) {
247     wxArrayInt selections;
248     int numSelected;
249 
250     if(!m_bInInit) {
251         numSelected = m_exclusiveApsListBox->GetSelections(selections);
252         m_removeExclusiveAppButton->Enable(numSelected > 0);
253     }
254     ev.Skip();
255 }
256 
OnExclusiveGPUAppListEvent(wxCommandEvent & ev)257 void CDlgExclusiveApps::OnExclusiveGPUAppListEvent(wxCommandEvent& ev) {
258     wxArrayInt selections;
259     int numSelected;
260 
261     if(!m_bInInit) {
262         numSelected = m_exclusiveGPUApsListBox->GetSelections(selections);
263         m_removeExclusiveGPUAppButton->Enable(numSelected > 0);
264     }
265     ev.Skip();
266 }
267 
268 // ---- command buttons handlers
269 // handles Add button clicked
OnAddExclusiveApp(wxCommandEvent &)270 void CDlgExclusiveApps::OnAddExclusiveApp(wxCommandEvent&) {
271     AddToListBox(m_exclusiveApsListBox);
272 }
273 
OnAddExclusiveGPUApp(wxCommandEvent &)274 void CDlgExclusiveApps::OnAddExclusiveGPUApp(wxCommandEvent&) {
275     AddToListBox(m_exclusiveGPUApsListBox);
276 }
277 
AddToListBox(wxListBox * theListBox)278 void CDlgExclusiveApps::AddToListBox(wxListBox * theListBox) {
279     wxString strMachineName;
280     int i, j, n;
281     bool hostIsMac = false;
282     bool hostIsWin = false;
283     bool isDuplicate;
284     wxArrayString appNames;
285     wxChar *extension = wxT("");
286     wxString errmsg;
287     CMainDocument* pDoc = wxGetApp().GetDocument();
288 
289     wxASSERT(pDoc);
290     wxASSERT(wxDynamicCast(pDoc, CMainDocument));
291 
292     if (strstr(pDoc->state.host_info.os_name, "Darwin")) {
293         hostIsMac = true;
294         extension = wxT(".app");
295     } else if (strstr(pDoc->state.host_info.os_name, "Microsoft")) {
296         hostIsWin = true;
297         extension = wxT(".exe");
298     }
299 
300     pDoc->GetConnectedComputerName(strMachineName);
301     if (pDoc->IsComputerNameLocal(strMachineName)) {
302 #ifdef __WXMAC__
303         wxFileDialog picker(this, _("Applications to add"),
304             wxT("/Applications"), wxT(""), wxT("*.app"),
305             wxFD_OPEN|wxFD_FILE_MUST_EXIST|wxFD_MULTIPLE|wxFD_CHANGE_DIR
306         );
307 #elif defined(__WXMSW__)
308 //TODO: fill in the default directory for MSW
309         wxFileDialog picker(this, _("Applications to add"),
310             wxT("C:/Program Files"), wxT(""), wxT("*.exe"),
311             wxFD_OPEN|wxFD_FILE_MUST_EXIST|wxFD_MULTIPLE|wxFD_CHANGE_DIR
312         );
313 #else
314 //TODO: fill in the default directory for Linux
315         wxFileDialog picker(this, _("Applications to add"),
316             wxT("/usr/bin"), wxT(""), wxT("*"),
317             wxFD_OPEN|wxFD_FILE_MUST_EXIST|wxFD_MULTIPLE|wxFD_CHANGE_DIR
318         );
319 #endif
320         if (picker.ShowModal() != wxID_OK) return;
321         picker.GetFilenames(appNames);
322 
323         for (i=appNames.Count()-1; i>=0; --i) {
324 #ifdef __WXMSW__
325             // Under Windows, filename may include paths if a shortcut selected
326             wxString appNameOnly = appNames[i].AfterLast('\\');
327             appNames[i] = appNameOnly;
328 #endif
329             wxString directory = picker.GetDirectory();
330             wxFileName fn(directory, appNames[i]);
331             if (!fn.IsOk() || !fn.IsFileExecutable()) {
332                 errmsg.Printf(_("'%s' is not an executable application."), appNames[i].c_str());
333                 wxGetApp().SafeMessageBox(errmsg, _("Add Exclusive App"),
334                     wxOK | wxICON_EXCLAMATION, this
335                 );
336                 appNames.RemoveAt(i);
337                 continue;
338             }
339         }
340     } else {
341         // We can't use file picker if connected to a remote computer,
342         // so show a dialog with textedit field so user can type app name
343         wxChar path_separator = wxT('/');
344 
345         wxTextEntryDialog dlg(this, _("Name of application to add?"), _("Add exclusive app"));
346         if (hostIsMac) {
347             dlg.SetValue(extension);
348         } else if (hostIsWin) {
349             dlg.SetValue(extension);
350             path_separator = wxT('\\');
351         }
352         if (dlg.ShowModal() != wxID_OK) return;
353 
354         wxString theAppName = dlg.GetValue();
355         // Strip off path if present
356         appNames.Add(theAppName.AfterLast(path_separator));
357     }
358 
359     for (i=0; i<(int)appNames.Count(); ++i) {
360         // wxFileName::IsFileExecutable() doesn't seem to work on Windows,
361         // and we can only perform minimal validation on remote hosts, so
362         // check filename extension on Mac and Win
363         bool bad_name = false;
364         if (hostIsMac) {
365             bad_name = !appNames[i].EndsWith(extension);
366         } else if (hostIsWin) {
367             size_t len = appNames[i].Len();
368             size_t xl = 4;
369             if (len < xl) {
370                 bad_name = true;
371             } else {
372                 wxString x = appNames[i].Mid(len-xl);
373                 if (x.CmpNoCase(extension) != 0) {
374                     bad_name = true;
375                 }
376             }
377         }
378         if (bad_name) {
379             errmsg.Printf(_("Application names must end with '%s'"), extension);
380             wxGetApp().SafeMessageBox(errmsg, _("Add Exclusive App"),
381                 wxOK | wxICON_EXCLAMATION, this
382             );
383             return;
384         }
385 
386         if (hostIsMac) {
387             int suffix = appNames[i].Find('.', true);
388             if (suffix != wxNOT_FOUND) {
389                 appNames[i].Truncate(suffix);
390             }
391         }
392 
393         // Skip requests for duplicate entries
394         isDuplicate = false;
395         n = theListBox->GetCount();
396         for (j=0; j<n; ++j) {
397             if ((theListBox->GetString(j)).Cmp(appNames[i]) == 0) {
398                 isDuplicate = true;
399                 break;
400             }
401         }
402         if (isDuplicate) {
403             errmsg.Printf(_("'%s' is already in the list."), appNames[i].c_str());
404             wxGetApp().SafeMessageBox(errmsg, _("Add Exclusive App"),
405                 wxOK | wxICON_EXCLAMATION, this
406             );
407             continue;
408         }
409 
410         theListBox->Append(appNames[i]);
411         m_bExclusiveAppsDataChanged = true;
412     }
413 }
414 
415 typedef int (*sortcomparefunc)(int*, int*);
416 
myCompareInts(int * first,int * second)417 static int myCompareInts(int *first, int *second) {
418     return *first - *second;
419 }
420 // handles Remove button clicked
OnRemoveExclusiveApp(wxCommandEvent & ev)421 void CDlgExclusiveApps::OnRemoveExclusiveApp(wxCommandEvent& ev) {
422     wxArrayInt selections;
423     int numSelected = m_exclusiveApsListBox->GetSelections(selections);
424 
425     // The selection indices are returned in random order.
426     // We must sort them to ensure deleting the correct items.
427     selections.Sort((sortcomparefunc)&myCompareInts);
428     for (int i=numSelected-1; i>=0; --i) {
429         m_exclusiveApsListBox->Delete(selections[i]);
430         m_bExclusiveAppsDataChanged = true;
431     }
432     ev.Skip();
433 }
434 
OnRemoveExclusiveGPUApp(wxCommandEvent & ev)435 void CDlgExclusiveApps::OnRemoveExclusiveGPUApp(wxCommandEvent& ev) {
436     wxArrayInt selections;
437     int numSelected = m_exclusiveGPUApsListBox->GetSelections(selections);
438 
439     // The selection indices are returned in random order.
440     // We must sort them to ensure deleting the correct items.
441     selections.Sort((sortcomparefunc)&myCompareInts);
442     for (int i=numSelected-1; i>=0; --i) {
443         m_exclusiveGPUApsListBox->Delete(selections[i]);
444         m_bExclusiveAppsDataChanged = true;
445     }
446     ev.Skip();
447 }
448 
449 // handles OK button clicked
OnOK(wxCommandEvent & ev)450 void CDlgExclusiveApps::OnOK(wxCommandEvent& ev) {
451     SavePreferencesSettings();
452 
453     ev.Skip();
454 }
455 
456 // handles Help button clicked
OnHelp(wxCommandEvent & ev)457 void CDlgExclusiveApps::OnHelp(wxCommandEvent& ev) {
458     if (IsShown()) {
459 
460         wxString strURL = wxGetApp().GetSkinManager()->GetAdvanced()->GetOrganizationHelpUrl();
461 
462         wxString wxurl;
463         wxurl.Printf(
464             wxT("%s?target=exclusive_apps&version=%s&controlid=%d"),
465             strURL.c_str(),
466             wxString(BOINC_VERSION_STRING, wxConvUTF8).c_str(),
467             ev.GetId()
468         );
469         wxLaunchDefaultBrowser(wxurl);
470     }
471 }
472