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