1 // $Id: watchUsbIpApp.cpp 605 2011-02-22 04:00:58Z felfert $
2 //
3 // Copyright (C) 2006 The OpenNX Team
4 // Author: Fritz Elfert
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU Library General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU Library General Public
17 // License along with this program; if not, write to the
18 // Free Software Foundation, Inc.,
19 // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 //
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 // For compilers that support precompilation, includes "wx/wx.h".
27 #include "wx/wxprec.h"
28
29 #ifdef __BORLANDC__
30 #pragma hdrstop
31 #endif
32
33 #ifndef WX_PRECOMP
34 #include "wx/wx.h"
35 #endif
36
37 #include <wx/cmdline.h>
38 #include <wx/xrc/xmlres.h>
39 #include <wx/msgdlg.h>
40 #include <wx/config.h>
41 #include <wx/fs_zip.h>
42 #include <wx/fs_mem.h>
43 #include <wx/wfstream.h>
44 #include <wx/mimetype.h>
45 #include <wx/sysopt.h>
46 #include <wx/socket.h>
47 #include <wx/tokenzr.h>
48 #include <wx/process.h>
49
50 #include "watchUsbIpApp.h"
51 #include "Icon.h"
52 #include "xh_richtext.h"
53 #include "MyXmlConfig.h"
54 #include "UsbFilterDetailsDialog.h"
55 #include "UsbIp.h"
56 #include "LibUSB.h"
57 #include "osdep.h"
58
59 #include "memres.h"
60
61 #include "trace.h"
62 ENABLE_TRACE;
63 DECLARE_TRACETAGS;
64
65 DECLARE_LOCAL_EVENT_TYPE(wxEVT_PROCESS_DIED, -1);
66 DEFINE_LOCAL_EVENT_TYPE(wxEVT_PROCESS_DIED);
67
68 IMPLEMENT_APP( watchUsbIpApp )
IMPLEMENT_CLASS(watchUsbIpApp,wxApp)69 IMPLEMENT_CLASS( watchUsbIpApp, wxApp )
70
71 BEGIN_EVENT_TABLE(watchUsbIpApp, wxApp)
72 EVT_HOTPLUG(watchUsbIpApp::OnHotplug)
73 EVT_COMMAND(wxID_ANY, wxEVT_PROCESS_DIED, watchUsbIpApp::OnSshDied)
74 END_EVENT_TABLE()
75
76 #ifdef __UNIX__
77 # include <signal.h>
78 static void terminate(int sig __attribute((unused)))
79 {
80 ::wxGetApp().Terminate();
81 signal(SIGTERM, terminate);
82 signal(SIGINT, terminate);
83 }
84 #endif
85
86 class ProcessWatcher : public wxThreadHelper
87 {
88 public:
ProcessWatcher(wxEvtHandler * handler,long pid)89 ProcessWatcher(wxEvtHandler *handler, long pid)
90 :wxThreadHelper()
91 ,m_pEvtHandler(handler)
92 ,m_bOk(false)
93 ,m_bTerminate(false)
94 ,m_lPid(pid)
95 {
96 if (Create(
97 #ifdef __OPENBSD__
98 32768
99 #endif
100 ) == wxTHREAD_NO_ERROR) {
101 GetThread()->Run();
102 while ((!m_bOk) && GetThread()->IsRunning())
103 wxThread::Sleep(100);
104 if (!m_bOk)
105 ::myLogTrace(MYTRACETAG, wxT("ssh watch thread terminated unexpectedly"));
106 } else
107 ::myLogTrace(MYTRACETAG, wxT("could not create ssh watch thread"));
108 }
109
~ProcessWatcher()110 virtual ~ProcessWatcher()
111 {
112 m_pEvtHandler = NULL;
113 if (m_bOk) {
114 m_bTerminate = true;
115 GetThread()->Delete();
116 while (m_bOk)
117 wxThread::Sleep(100);
118 }
119 }
120
Entry()121 virtual wxThread::ExitCode Entry()
122 {
123 m_bOk = true;
124 while (!m_thread->TestDestroy()) {
125 if (m_bTerminate)
126 break;
127 if (!wxProcess::Exists(m_lPid)) {
128 wxCommandEvent ev(wxEVT_PROCESS_DIED, wxID_ANY);
129 ev.SetInt(m_lPid);
130 if (m_pEvtHandler) {
131 m_bTerminate = true;
132 m_pEvtHandler->AddPendingEvent(ev);
133 }
134 } else
135 wxThread::Sleep(1000);
136 }
137 m_bOk = false;
138 return 0;
139 }
140
IsOk()141 bool IsOk() { return m_bOk; }
SetHandler(wxEvtHandler * handler)142 void SetHandler(wxEvtHandler *handler) { m_pEvtHandler = handler; }
143
144 private:
145 wxEvtHandler *m_pEvtHandler;
146 bool m_bOk;
147 bool m_bTerminate;
148 long m_lPid;
149 };
150
watchUsbIpApp()151 watchUsbIpApp::watchUsbIpApp()
152 : m_pSessionCfg(NULL)
153 , m_pUsbIp(NULL)
154 , m_pDialog(NULL)
155 {
156 SetAppName(wxT("OpenNX"));
157 wxConfig *cfg;
158 #ifdef __WXMSW__
159 cfg = new wxConfig(wxT("OpenNX"), wxT("InnoviData"));
160 #else
161 # ifdef __WXMAC__
162 cfg = new wxConfig(wxT("OpenNX"), wxT("InnoviData"), wxT("OpenNX Preferences"), wxT("OpenNX Preferences"));
163 # else
164 cfg = new wxConfig(wxT("OpenNX"), wxT("InnoviData"), wxT(".opennx"), wxT("opennx.conf"));
165 # endif
166 #endif
167 wxConfigBase::Set(cfg);
168
169 // Language overrides from KDE - only applied if running inside a KDE session.
170 if (inKdeSession != 0) {
171 wxLogNull dummy;
172
173 // If KDE_LANG is set, then it has precedence over kdeglobals.
174 wxString lang;
175 if (::wxGetEnv(wxT("KDE_LANG"), &lang)) {
176 myLogDebug(wxT("Overriding LANG from KDE_LANG environment to: '%s'"), lang.c_str());
177 ::wxSetEnv(wxT("LANG"), lang);
178 } else {
179 // Try to get KDE language settings and override locale accordingly
180 wxFileInputStream fis(::wxGetHomeDir() +
181 wxFileName::GetPathSeparator() + wxT(".kde") +
182 wxFileName::GetPathSeparator() + wxT("share") +
183 wxFileName::GetPathSeparator() + wxT("config") +
184 wxFileName::GetPathSeparator() + wxT("kdeglobals"));
185 if (fis.IsOk()) {
186 wxFileConfig cfg(fis);
187 wxString country = cfg.Read(wxT("Locale/Country"), wxEmptyString);
188 wxString lang = cfg.Read(wxT("Locale/Language"), wxEmptyString);
189 if ((!lang.IsEmpty()) && (!country.IsEmpty())) {
190 if (lang.Contains(wxT(":")))
191 lang = lang.BeforeFirst(wxT(':'));
192 if (lang.Length() < 3)
193 lang << wxT("_") << country.Upper();
194 lang << wxT(".UTF-8");
195 myLogDebug(wxT("Overriding LANG from kdeglobals to: '%s'"), lang.c_str());
196 ::wxSetEnv(wxT("LANG"), lang);
197 }
198 }
199 }
200 }
201 }
202
OnInitCmdLine(wxCmdLineParser & parser)203 void watchUsbIpApp::OnInitCmdLine(wxCmdLineParser& parser)
204 {
205 // Init standard options (--help, --verbose);
206 wxApp::OnInitCmdLine(parser);
207
208 // tags will be appended to the last switch/option
209 wxString tags;
210 allTraceTags.Sort();
211 for (size_t i = 0; i < allTraceTags.GetCount(); i++) {
212 if (!tags.IsEmpty())
213 tags += wxT(" ");
214 tags += allTraceTags.Item(i);
215 }
216 tags.Prepend(_("\n\nSupported trace tags: "));
217
218 parser.AddOption(wxT("s"), wxT("sessionid"), _("NX SessionID to watch for."),
219 wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY);
220 parser.AddOption(wxT("c"), wxT("config"), _("Path of session config file."),
221 wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY);
222 parser.AddOption(wxT("p"), wxT("pid"), _("Process ID of the nxssh process."),
223 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_OPTION_MANDATORY);
224 parser.AddOption(wxEmptyString, wxT("trace"),
225 _("Specify wxWidgets trace mask.") + tags);
226 }
227
OnCmdLineParsed(wxCmdLineParser & parser)228 bool watchUsbIpApp::OnCmdLineParsed(wxCmdLineParser& parser)
229 {
230 if (!wxApp::OnCmdLineParsed(parser))
231 return false;
232 parser.Found(wxT("s"), &m_sSessionID);
233 if (parser.Found(wxT("c"), &m_sSessionConfig)) {
234 if (!m_sSessionConfig.IsEmpty())
235 m_pSessionCfg = new MyXmlConfig(m_sSessionConfig);
236 }
237 parser.Found(wxT("p"), &m_lSshPid);
238
239 wxString traceTags;
240 if (parser.Found(wxT("trace"), &traceTags)) {
241 wxStringTokenizer t(traceTags, wxT(","));
242 while (t.HasMoreTokens()) {
243 wxString tag = t.GetNextToken();
244 if (allTraceTags.Index(tag) == wxNOT_FOUND) {
245 OnCmdLineError(parser);
246 return false;
247 }
248 ::myLogDebug(wxT("Trace for '%s' enabled"), tag.c_str());
249 wxLog::AddTraceMask(tag);
250 }
251 }
252 return true;
253 }
254
OnInit()255 bool watchUsbIpApp::OnInit()
256 {
257 wxString tmp;
258
259 initWxTraceTags();
260 if (::wxGetEnv(wxT("WXTRACE"), &tmp)) {
261 wxStringTokenizer t(tmp, wxT(",:"));
262 while (t.HasMoreTokens()) {
263 wxString tag = t.GetNextToken();
264 if (allTraceTags.Index(tag) != wxNOT_FOUND) {
265 ::myLogDebug(wxT("Trace for '%s' enabled"), tag.c_str());
266 wxLog::AddTraceMask(tag);
267 }
268 }
269 }
270
271 wxConfigBase::Get()->Read(wxT("Config/SystemNxDir"), &tmp);
272 m_cLocale.AddCatalogLookupPathPrefix(tmp + wxFileName::GetPathSeparator()
273 + wxT("share") + wxFileName::GetPathSeparator() + wxT("locale"));
274 m_cLocale.AddCatalogLookupPathPrefix(wxT("locale"));
275 m_cLocale.Init();
276 m_cLocale.AddCatalog(wxT("opennx"));
277
278 // Win: Don't remap bitmaps to system colors
279 wxSystemOptions::SetOption(wxT("msw.remap"), 0);
280 // WinXP: Don't draw themed gradients on notebook pages
281 wxSystemOptions::SetOption(wxT("msw.notebook.themed-background"), 0);
282
283 wxFileSystem::AddHandler(new wxZipFSHandler);
284 wxFileSystem::AddHandler(new wxMemoryFSHandler);
285 wxInitAllImageHandlers();
286 wxBitmap::InitStandardHandlers();
287 wxXmlResource::Get()->InitAllHandlers();
288 wxXmlResource::Get()->AddHandler(new wxRichTextCtrlXmlHandler());
289
290 // This enable socket-I/O from other threads.
291 wxSocketBase::Initialize();
292
293 bool resok = false;
294 wxString optionalRsc = tmp + wxFileName::GetPathSeparator() + wxT("share")
295 + wxFileName::GetPathSeparator() + wxT("opennx.rsc");
296 if (wxFileName::FileExists(optionalRsc)) {
297 wxFile rf(optionalRsc);
298 if (rf.IsOpened()) {
299 unsigned char *resptr = (unsigned char *)malloc(rf.Length());
300 if (resptr) {
301 if (rf.Read(resptr, rf.Length()) == rf.Length()) {
302 wxMemoryFSHandler::AddFileWithMimeType(wxT("memrsc"), resptr, rf.Length(), wxT("application/zip"));
303 {
304 // The following code eliminates a stupid error dialog which shows up
305 // if some .desktop entires (in KDE or GNOME applink dirs) are dangling symlinks.
306 wxLogNull lognull;
307 wxTheMimeTypesManager->GetFileTypeFromExtension(wxT("zip"));
308 }
309 resok = true;
310 }
311 free(resptr);
312 }
313 }
314 }
315 if (!resok) {
316 const unsigned char *resptr = get_mem_res();
317 if (resptr) {
318 wxMemoryFSHandler::AddFileWithMimeType(wxT("memrsc"), resptr, cnt_mem_res, wxT("application/zip"));
319 {
320 // The following code eliminates a stupid error dialog which shows up
321 // if some .desktop entires (in KDE or GNOME applink dirs) are dangling symlinks.
322 wxLogNull lognull;
323 wxTheMimeTypesManager->GetFileTypeFromExtension(wxT("zip"));
324 }
325 free_mem_res(resptr);
326 }
327 resok = true;
328 }
329 if (!resok) {
330 ::wxLogFatalError(wxT("Could not load application resource."));
331 return false;
332 }
333
334 m_sResourcePrefix = wxT("memory:memrsc#zip:");
335 if (!wxXmlResource::Get()->Load(m_sResourcePrefix + wxT("res/opennx.xrc")))
336 return false;
337
338 if (!wxApp::OnInit())
339 return false;
340
341 if (m_sSessionID.IsEmpty()) {
342 ::wxLogError(_("An empty session ID is not allowed"));
343 return false;
344 }
345
346 if ((!m_pSessionCfg) || (!m_pSessionCfg->IsValid())) {
347 ::wxLogError(_("Could not load session config file"));
348 return false;
349 }
350 wxFileName tmpfn(m_pSessionCfg->sGetFileName());
351 if (tmpfn.GetName().IsSameAs(m_sSessionID)) {
352 // If the basename of the session config is the sessionID, then
353 // this file is temporary and can be deleted now.
354 ::wxRemoveFile(tmpfn.GetFullPath());
355 }
356
357 if (!m_pSessionCfg->bGetEnableUSBIP()) {
358 // No need to watch for hotplug events, silently exit
359 return false;
360 }
361
362 UsbIp *usbip = new UsbIp();
363 wxString usock = wxConfigBase::Get()->Read(wxT("Config/UsbipdSocket"),
364 wxT("/var/run/usbipd2.socket"));
365 if (usbip->Connect(usock)) {
366 usbip->SetSession(m_sSessionID);
367 if (usbip->IsConnected()) {
368 m_pDialog = new UsbFilterDetailsDialog(NULL);
369 m_pDialog->SetDialogMode(UsbFilterDetailsDialog::MODE_HOTPLUG);
370 // SetTopWindow(m_pDialog);
371 usbip->SetEventHandler(m_pDialog);
372 if (!usbip->RegisterHotplug()) {
373 ::wxLogError(_("Could not register at usbipd2! No hotplugging functionality."));
374 m_pDialog->Destroy();
375 return false;
376 }
377 m_pUsbIp = usbip;
378 }
379 } else
380 ::wxLogError(_("Could not connect to usbipd2! No hotplugging functionality."));
381 #ifdef __UNIX__
382 signal(SIGTERM, terminate);
383 signal(SIGINT, terminate);
384 #endif
385 m_pProcessWatcher = new ProcessWatcher(this, m_lSshPid);
386 return true;
387 }
388
OnSshDied(wxCommandEvent & event)389 void watchUsbIpApp::OnSshDied(wxCommandEvent &event)
390 {
391 wxUnusedVar(event);
392 ::myLogTrace(MYTRACETAG, wxT("nxssh has terminated"));
393 m_pDialog->Destroy();
394 }
395
OnHotplug(HotplugEvent & event)396 void watchUsbIpApp::OnHotplug(HotplugEvent &event)
397 {
398 ArrayOfUsbForwards af = m_pSessionCfg->aGetUsbForwards();
399 SharedUsbDevice *sdev = NULL;
400 int retry = 0;
401 size_t i, j;
402
403
404 bool found = false;
405 bool doexport = false;
406 while ((NULL == sdev) && (retry++ < 10)) {
407 USB u;
408 ArrayOfUSBDevices au = u.GetDevices();
409 for (i = 0; i < au.GetCount(); i++) {
410 if ((au[i].GetBusNum() == event.GetBusNum()) && (au[i].GetDevNum() == event.GetDevNum())) {
411 for (j = 0; j < af.GetCount(); j++) {
412 if (af[j].MatchHotplug(au[i])) {
413 found = true;
414 doexport = (af[j].m_eMode == SharedUsbDevice::MODE_REMOTE);
415 break;
416 }
417 }
418 sdev = new SharedUsbDevice;
419 sdev->m_iVendorID = au[i].GetVendorID();
420 sdev->m_iProductID = au[i].GetProductID();
421 sdev->m_iClass = au[i].GetDeviceClass();
422 sdev->m_sVendor = au[i].GetVendor();
423 sdev->m_sProduct = au[i].GetProduct();
424 sdev->m_sSerial = au[i].GetSerial();
425 break;
426 }
427 }
428 if (NULL == sdev)
429 wxThread::Sleep(500);
430 }
431 if (NULL == sdev) {
432 m_pUsbIp->SendHotplugResponse(event.GetCookie());
433 ::wxLogError(_("Got hotplug event, but device is not available in libusb"));
434 return;
435 }
436 if (found) {
437 // Found device in session config. Silently act on configuration
438 ::myLogTrace(MYTRACETAG, wxT("Found device in session config action=%s"),
439 doexport ? wxT("export") : wxT("local"));
440 if (!m_pUsbIp->SendHotplugResponse(event.GetCookie()))
441 ::wxLogError(_("Could not send hotplug response"));
442 } else {
443 // Device not in session config. Ask user
444 m_pDialog->SetVendorID(wxString::Format(wxT("%04X"), sdev->m_iVendorID));
445 m_pDialog->SetProductID(wxString::Format(wxT("%04X"), sdev->m_iProductID));
446 m_pDialog->SetDeviceClass(wxString::Format(wxT("%02X"), sdev->m_iClass));
447 m_pDialog->SetVendor(sdev->m_sVendor);
448 m_pDialog->SetProduct(sdev->m_sProduct);
449 m_pDialog->SetSerial(sdev->m_sSerial);
450 int result = m_pDialog->ShowModal();
451
452 m_pUsbIp->SendHotplugResponse(event.GetCookie());
453 // Do NOT report an error here, because user might inot have responded in time.
454
455 if (wxID_OK == result) {
456 doexport = m_pDialog->GetForwarding();
457 ::myLogTrace(MYTRACETAG, wxT("Dialog OK, store=%d action=%s"),
458 m_pDialog->GetStoreFilter(), doexport ? wxT("export") : wxT("local"));
459 if (m_pDialog->GetStoreFilter()) {
460 ArrayOfUsbForwards a = m_pSessionCfg->aGetUsbForwards();
461 SharedUsbDevice dev;
462 long tmp;
463 if (m_pDialog->GetVendorID().IsEmpty())
464 dev.m_iVendorID = -1;
465 else {
466 m_pDialog->GetVendorID().ToLong(&tmp, 16);
467 dev.m_iVendorID = tmp;
468 }
469 if (m_pDialog->GetProductID().IsEmpty())
470 dev.m_iProductID = -1;
471 else {
472 m_pDialog->GetProductID().ToLong(&tmp, 16);
473 dev.m_iProductID = tmp;
474 }
475 if (m_pDialog->GetDeviceClass().IsEmpty())
476 dev.m_iClass = -1;
477 else {
478 m_pDialog->GetDeviceClass().ToLong(&tmp, 16);
479 dev.m_iClass = tmp;
480 }
481 dev.m_sVendor = m_pDialog->GetVendor();
482 dev.m_sProduct = m_pDialog->GetProduct();
483 dev.m_sSerial = m_pDialog->GetSerial();
484 dev.m_eMode = doexport ? SharedUsbDevice::MODE_REMOTE : SharedUsbDevice::MODE_LOCAL;
485 found = false;
486 for (size_t i = 0; i < a.GetCount(); i++) {
487 if (dev.cmpNoMode(a[i])) {
488 found = true;
489 break;
490 }
491 }
492 if (!found) {
493 a.Add(dev);
494 m_pSessionCfg->aSetUsbForwards(a);
495 ::myLogTrace(MYTRACETAG, wxT("saving to %s"), m_pSessionCfg->sGetFileName().c_str());
496 if (!m_pSessionCfg->SaveToFile())
497 ::wxLogError(_("Could not save session config"));
498 }
499 }
500 ::myLogTrace(MYTRACETAG, wxT("action=%s"), doexport ? wxT("export") : wxT("local"));
501 }
502 }
503 delete sdev;
504 if (doexport) {
505 if (!m_pUsbIp->ExportDevice(event.GetBusID()))
506 ::wxLogError(_("Could not export USB device"));
507 }
508 }
509
Terminate()510 void watchUsbIpApp::Terminate()
511 {
512 ::wxMutexGuiEnter();
513 ::myLogTrace(MYTRACETAG, wxT("Terminate()"));
514 wxCommandEvent ev(wxEVT_PROCESS_DIED, wxID_ANY);
515 ev.SetInt(0);
516 AddPendingEvent(ev);
517 ::wxMutexGuiLeave();
518 }
519
OnExit()520 int watchUsbIpApp::OnExit()
521 {
522 if (m_pUsbIp)
523 delete m_pUsbIp;
524 m_pUsbIp = NULL;
525 return wxApp::OnExit();
526 }
527
528 void
EnableContextHelp(wxWindow * w)529 watchUsbIpApp::EnableContextHelp(wxWindow *w)
530 {
531 if (NULL == w)
532 return;
533 wxAcceleratorEntry entries[1];
534 entries[0].Set(wxACCEL_SHIFT, WXK_F1, wxID_CONTEXT_HELP);
535 wxAcceleratorTable accel(1, entries);
536 w->SetAcceleratorTable(accel);
537 w->SetFocus();
538 }
539
540