// Icecast2winDlg.cpp : implementation file // #include "stdafx.h" #include "Icecast2win.h" #include "Icecast2winDlg.h" #include #include "ResizableDialog.h" #include #include #include #include extern "C" { #include "thread.h" #include "avl.h" #include "log.h" #include "global.h" #include "httpp.h" #include "sock.h" #include "connection.h" #include "refbuf.h" #include "client.h" #include "stats.h" } #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define ICECAST_VERSION "2.x" CEdit *g_accessControl; CEdit *g_errorControl; CIcecast2winDlg *g_mainDialog; bool g_tailAccess = false; bool g_tailError = false; CString gConfigurationSave; char gTitleSource[1024] = ""; char gTitleName[1024] = ""; #define MAXSTATSPERSOURCE 30 #define MAXSOURCES 1024 typedef struct tagElement { CString name; CString value; int titleFlag; } Element; typedef struct tagElementAdditional { CString source; CString name; CString value; int titleFlag; } ElementAdditional; typedef struct tagMainElement { CString source; long numStats; Element stats[MAXSTATSPERSOURCE]; int populated; } MainElement; typedef struct tagMainElementAdditional { long numStats; ElementAdditional stats[MAXSTATSPERSOURCE]; } MainElementAdditional; MainElement gStats[MAXSOURCES]; MainElement gGlobalStats; MainElementAdditional gAdditionalGlobalStats; long numMainStats; extern "C" { int main(int argc, char **argv); } void AddToAdditionalGlobalStats(CString source, CString name) { int foundit = 0; for (int i=0;iUpdateStatsLists(); } void ClearTitleAdditionalGlobalStats(CString source, CString name) { int foundit = 0; int i,j; for (i=0;iUpdateStatsLists(); } void AddToTitleAdditionalGlobalStats(CString source, CString name) { int foundit = 0; int i,j; for (i=0;iUpdateStatsLists(); } void RemoveFromAdditionalGlobalStats(CString source, CString name) { int foundit = 0; for (int i=0;iUpdateStatsLists(); } ///////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About class CAboutDlg : public CResizableDialog { public: CAboutDlg(); // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: //{{AFX_MSG(CAboutDlg) //}}AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CResizableDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CResizableDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CResizableDialog) //{{AFX_MSG_MAP(CAboutDlg) // No message handlers //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CIcecast2winDlg dialog CIcecast2winDlg::CIcecast2winDlg(CWnd* pParent /*=NULL*/) : CResizableDialog(CIcecast2winDlg::IDD, pParent) { //{{AFX_DATA_INIT(CIcecast2winDlg) m_AccessEdit = _T(""); m_ErrorEdit = _T(""); m_ConfigEdit = _T(""); m_ServerStatus = _T(""); m_SourcesConnected = _T(""); m_NumClients = _T(""); m_StatsEdit = _T(""); m_Autostart = FALSE; //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_pTray = NULL; } void CIcecast2winDlg::DoDataExchange(CDataExchange* pDX) { CResizableDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CIcecast2winDlg) DDX_Control(pDX, IDC_STATIC_SS, m_SS); DDX_Control(pDX, IDC_SERVERSTATUS, m_ServerStatusBitmap); DDX_Control(pDX, IDC_START, m_StartButton); DDX_Control(pDX, IDC_MAINTAB, m_MainTab); DDX_Check(pDX, IDC_AUTOSTART, m_Autostart); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CIcecast2winDlg, CResizableDialog) //{{AFX_MSG_MAP(CIcecast2winDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_NOTIFY(TCN_SELCHANGE, IDC_MAINTAB, OnSelchangeMaintab) ON_COMMAND(ID_FILE_EXIT, OnFileExit) ON_WM_TIMER() ON_COMMAND(ID_FILE_STARTSERVER, OnFileStartserver) ON_COMMAND(ID_FILE_STOPSERVER, OnFileStopserver) ON_BN_CLICKED(IDC_START, OnStart) ON_WM_CLOSE() ON_WM_SIZE() ON_BN_CLICKED(IDC_HIDESYSTRAY, OnHidesystray) ON_COMMAND(ID_BLANK_RESTORE, OnBlankRestore) ON_MESSAGE(WM_TRAY_NOTIFY, OnTrayNotify) ON_WM_DESTROY() ON_COMMAND(ID_FILE_EDITCONFIGURATION, OnFileEditconfiguration) ON_COMMAND(ID_ABOUT_HELP, OnAboutHelp) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CIcecast2winDlg message handlers #include "colors.h" BOOL CIcecast2winDlg::OnInitDialog() { CResizableDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } g_mainDialog = this; // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here config_read(); statsTab.m_colSource0Width = m_colSource0Width; statsTab.m_colStats0Width = m_colStats0Width; statsTab.m_colStats1Width = m_colStats1Width; statusTab.m_colStats0Width = m_colGStats0Width; statusTab.m_colStats1Width = m_colGStats1Width; statusTab.m_colStats2Width = m_colGStats2Width; statsTab.Create(IDD_STATSDIALOG, this); statusTab.Create(IDD_SSTATUS, this); int nPageID = 0; m_MainTab.AddSSLPage (_T("Server Status"), nPageID, (CTabPageSSL *)&statusTab); nPageID++; m_MainTab.AddSSLPage (_T("Source Level Stats"), nPageID, (CTabPageSSL *)&statsTab); nPageID++; labelFont.CreateFont(24,0, 0, 0, FW_BOLD, 0, 0, 0, 0, OUT_TT_PRECIS, 0, PROOF_QUALITY, 0, "Arial"); runningBitmap.LoadBitmap(IDB_BITMAP6); stoppedBitmap.LoadBitmap(IDB_BITMAP5); UpdateData(FALSE); LoadConfig(); AddAnchor(IDC_MAINTAB, TOP_LEFT, BOTTOM_RIGHT); AddAnchor(IDC_STATICBLACK, TOP_LEFT, TOP_RIGHT); EnableSaveRestore("icecast2win", "positions"); m_pTray = NULL; char version[255] = ""; sprintf(version, "Icecast2 Version %s", ICECAST_VERSION); SetWindowText(version); if (m_Autostart) { OnStart(); } return TRUE; // return TRUE unless you set the focus to a control } void CIcecast2winDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CResizableDialog::OnSysCommand(nID, lParam); } } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CIcecast2winDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CResizableDialog::OnPaint(); } } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CIcecast2winDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } void CIcecast2winDlg::OnSelchangeMaintab(NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Add your control notification handler code here *pResult = 0; } void CIcecast2winDlg::LoadConfig() { FILE *filep; char buffer[2046] = ""; CIcecast2winApp *myApp = (CIcecast2winApp *)AfxGetApp(); configTab.m_Config = ""; filep = fopen(myApp->m_configFile, "r"); if (filep) { fclose(filep); } else { MessageBox("Unable to load config file (" + CString(myApp->m_configFile) + ") unable to start", NULL, MB_OK); } } void CIcecast2winDlg::OnFileExit() { // TODO: Add your command handler code here DestroyWindow(); } void CIcecast2winDlg::getTag(char *pbuf, char *ptag, char *dest) { char openTag[256] = ""; char closeTag[256] = ""; sprintf(openTag, "<%s>", ptag); sprintf(closeTag, "", ptag); char *p1; p1 = strstr(pbuf, openTag); if (p1) { p1 = p1 + strlen(openTag); char *p2; p2 = strstr(p1, closeTag); if (p2) { strncpy(dest, p1, p2-p1); } } } void CIcecast2winDlg::EnableControl(UINT control) { CWnd *pWnd; pWnd = GetDlgItem(control); ::ShowWindow(pWnd->GetSafeHwnd(), SW_SHOW); } void CIcecast2winDlg::DisableControl(UINT control) { CWnd *pWnd; pWnd = GetDlgItem(control); ::ShowWindow(pWnd->GetSafeHwnd(), SW_HIDE); } void AddUpdateStatistic(int sourceIndex, char *name, char *value) { for (int j=0;jxmlChildrenNode; while (children != NULL) { if (!strcmp((char *)children->name, "listeners")) { listenerInd = 1; } ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, children->xmlChildrenNode, 1); AddUpdateStatistic(sourceIndex, (char *)children->name, ls_xmlContentPtr); xmlFree(ls_xmlContentPtr); children = children->next; } if (!listenerInd) { /* If no listeners, then the source has been disconnected */ gStats[sourceIndex].populated = 0; gStats[sourceIndex].numStats = 0; g_mainDialog->statsTab.m_SourceListCtrl.DeleteAllItems(); g_mainDialog->statsTab.m_StatsListCtrl.DeleteAllItems(); } } bool g_collectingStats = false; void StartStats(void *dummy) { while (global.running != ICE_RUNNING) { Sleep(500); } while (global.running == ICE_RUNNING) { if (global.running == ICE_RUNNING) { for (int j=0;jxmlChildrenNode; char* ls_xmlContentPtr2 = NULL; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"source"))) { ls_xmlContentPtr2 = (char *)xmlGetProp(cur, (unsigned char *)"mount"); UpdateSourceData(doc, cur, ls_xmlContentPtr2); } else { /* A Global stat */ ls_xmlContentPtr2 = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); AddUpdateStatistic(0, (char *)cur->name, ls_xmlContentPtr2); } if (ls_xmlContentPtr2) { xmlFree(ls_xmlContentPtr2); } cur = cur->next; } xmlFreeDoc(doc); xmlCleanupParser(); g_mainDialog->UpdateStatsLists(); Sleep(5000); } if (global.running != ICE_RUNNING) { _endthread(); } } _endthread(); } void CIcecast2winDlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default if (nIDEvent == 0) { if (global.running == ICE_RUNNING) { char buffer[255] = ""; CString tmp; // Get info from stats... m_ServerStatusBitmap.SetBitmap(HBITMAP(runningBitmap)); sprintf(buffer, "%d", global.sources); tmp = buffer; if (tmp != statusTab.m_Sources) { statusTab.m_Sources = tmp; statusTab.UpdateData(FALSE); } sprintf(buffer, "%d", global.clients); tmp = buffer; if (tmp != statusTab.m_Clients) { statusTab.m_Clients = tmp; statusTab.UpdateData(FALSE); } m_StartButton.GetWindowText(tmp); if (tmp == "Start Server") { m_StartButton.SetWindowText("Stop Server"); m_StartButton.SetState(0); } time_t currentTime; time(¤tTime); time_t runningTime = currentTime - serverStart; CTimeSpan runningFor(runningTime); char timespan[1024] = ""; sprintf(timespan, "%d Days, %d Hours, %d Minutes, %d Seconds", runningFor.GetDays(), runningFor.GetHours(), runningFor.GetMinutes(), runningFor.GetSeconds()); statusTab.m_RunningFor = timespan; statusTab.UpdateData(FALSE); SetTimer(0, 500, NULL); } else { statusTab.m_Sources = "0"; statusTab.m_Clients = "0"; m_ServerStatusBitmap.SetBitmap(HBITMAP(stoppedBitmap)); m_StartButton.SetWindowText("Start Server"); m_StartButton.SetState(0); UpdateData(FALSE); statusTab.m_RunningFor = "Not running"; statusTab.UpdateData(FALSE); } } CResizableDialog::OnTimer(nIDEvent); } char g_configFile[1024] = ""; char g_progName[255] = "icecast2"; void StartServer(void *configfile) { int argc = 3; char* argv[3]; strcpy(g_configFile, (char *)configfile); argv[0] = g_progName; argv[1] = "-c"; argv[2] = g_configFile; time(&(g_mainDialog->serverStart)); int ret = main(argc, (char **)argv); if (ret) { MessageBox(NULL, "Unable to start server", NULL, MB_OK); } global.running = ICE_HALTING; _endthread(); } void CIcecast2winDlg::OnFileStartserver() { // TODO: Add your command handler code here CIcecast2winApp *myApp = (CIcecast2winApp *)AfxGetApp(); if (gConfigurationSave == "") { gConfigurationSave = m_ConfigEdit; } if (global.running == ICE_RUNNING) { MessageBox("Server already running", "Error", MB_OK); } else { m_ConfigEditCtrl.SetReadOnly(TRUE); LoadConfig(); SetTimer(0, 500, NULL); _beginthread(StartServer, 0, (void *)(LPCSTR)myApp->m_configFile); _beginthread(StartStats, 0, (void *)NULL); } } void CIcecast2winDlg::OnFileStopserver() { // TODO: Add your command handler code here ; } bool infocus = false; void CIcecast2winDlg::StopServer() { KillTimer(0); global.running = ICE_HALTING; m_StartButton.SetWindowText("Start Server"); m_StartButton.SetState(0); m_ServerStatusBitmap.SetBitmap(HBITMAP(stoppedBitmap)); statusTab.m_RunningFor = "Not running"; statusTab.UpdateData(FALSE); } void CIcecast2winDlg::OnStart() { CIcecast2winApp *myApp = (CIcecast2winApp *)AfxGetApp(); // TODO: Add your control notification handler code here if (global.running == ICE_RUNNING) { StopServer(); } else { SetTimer(0, 500, NULL); _beginthread(StartServer, 0, (void *)(LPCSTR)myApp->m_configFile); _beginthread(StartStats, 0, (void *)NULL); } } void CIcecast2winDlg::UpdateStatsLists() { char item[1024] = ""; int l = 0; // Global Stats are index of 0 for (int k=0;k < gStats[0].numStats;k++) { int inthere = 0; for (l=0;l < statusTab.m_GlobalStatList.GetItemCount();l++) { statusTab.m_GlobalStatList.GetItemText(l, 1, item, sizeof(item)); if (!strcmp(gStats[0].stats[k].name, item)) { inthere = 1; break; } } if (!inthere) { LVITEM lvi; lvi.mask = LVIF_IMAGE | LVIF_TEXT; lvi.iItem = statsTab.m_SourceListCtrl.GetItemCount(); lvi.iSubItem = 0; //lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].source; lvi.pszText = "Global Stat"; statusTab.m_GlobalStatList.InsertItem(&lvi); lvi.iSubItem = 1; lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].stats[k].name; statusTab.m_GlobalStatList.SetItem(&lvi); lvi.iSubItem = 2; lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].stats[k].value; statusTab.m_GlobalStatList.SetItem(&lvi); if ((!strcmp(gTitleSource, gStats[0].source)) && (!strcmp(gTitleName, gStats[0].stats[k].name))) { gStats[0].stats[k].titleFlag = 1; } } else { LVITEM lvi; lvi.mask = LVIF_IMAGE | LVIF_TEXT; lvi.iItem = l; lvi.iSubItem = 2; lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].stats[k].value; statusTab.m_GlobalStatList.SetItem(&lvi); } if (gStats[0].stats[k].titleFlag) { CString windowTitle = CString("Global Stat") + " - " + gStats[0].stats[k].name + " - " + gStats[0].stats[k].value; SetWindowText(windowTitle); if (m_pTray) { m_pTray->SetTIP((LPSTR)(LPCSTR)windowTitle); } } } for (int i=1;iSetTIP((LPSTR)(LPCSTR)windowTitle); } } } } else { } } } } char gAppName[255] = "icecast2"; char gConfigFile[255] = "icecast2.ini"; void CIcecast2winDlg::config_write() { char buf[255] = ""; char buf2[1024] = ""; UpdateData(TRUE); m_colSource0Width = statsTab.m_SourceListCtrl.GetColumnWidth(0); m_colStats0Width = statsTab.m_StatsListCtrl.GetColumnWidth(0); m_colStats1Width = statsTab.m_StatsListCtrl.GetColumnWidth(1); m_colGStats0Width = statusTab.m_GlobalStatList.GetColumnWidth(0); m_colGStats1Width = statusTab.m_GlobalStatList.GetColumnWidth(1); m_colGStats2Width = statusTab.m_GlobalStatList.GetColumnWidth(2); sprintf(buf, "%d", m_colSource0Width); WritePrivateProfileString(gAppName, "col0SourceWidth", buf, gConfigFile); sprintf(buf, "%d", m_colStats0Width); WritePrivateProfileString(gAppName, "col0StatsWidth", buf, gConfigFile); sprintf(buf, "%d", m_colStats1Width); WritePrivateProfileString(gAppName, "col1StatsWidth", buf, gConfigFile); sprintf(buf, "%d", m_colGStats0Width); WritePrivateProfileString(gAppName, "col0GStatsWidth", buf, gConfigFile); sprintf(buf, "%d", m_colGStats1Width); WritePrivateProfileString(gAppName, "col1GStatsWidth", buf, gConfigFile); sprintf(buf, "%d", m_colGStats2Width); WritePrivateProfileString(gAppName, "col2GStatsWidth", buf, gConfigFile); if (m_Autostart) { WritePrivateProfileString(gAppName, "AutoStart", "1", gConfigFile); } else { WritePrivateProfileString(gAppName, "AutoStart", "0", gConfigFile); } sprintf(buf, "%d", gAdditionalGlobalStats.numStats); WritePrivateProfileString(gAppName, "numAdditionalStats", buf, gConfigFile); for (int i=0;i 0) { char *p1 = strchr(buf, '|'); if (p1) { char tmpSource[1024] = ""; char tmpName[1024] = ""; memset(tmpSource, '\000', sizeof(tmpSource)); memset(tmpName, '\000', sizeof(tmpName)); strncpy(tmpSource, buf, p1-buf); p1++; strcpy(tmpName, p1); strcpy(gTitleSource, tmpSource); strcpy(gTitleName, tmpName); } } } void CIcecast2winDlg::OnClose() { // TODO: Add your message handler code here and/or call default config_write(); CResizableDialog::OnClose(); } void CIcecast2winDlg::OnSize(UINT nType, int cx, int cy) { CResizableDialog::OnSize(nType, cx, cy); int border1 = 0; int border2 = 78; // TODO: Add your message handler code here if (m_MainTab.m_hWnd) { CRect rect; GetClientRect (&rect); m_MainTab.ResizeDialog(0, rect.Width()-border1, rect.Height()-border2); m_MainTab.ResizeDialog(1, rect.Width()-border1, rect.Height()-border2); } } LONG CIcecast2winDlg::OnTrayNotify ( WPARAM wParam, LPARAM lParam ) { switch (lParam) { case WM_RBUTTONDOWN: { CMenu menu ; // Load and Verify Menu VERIFY(menu.LoadMenu(IDR_TRAY)); CMenu* pPopup = menu.GetSubMenu (0) ; ASSERT(pPopup != NULL); // Get the cursor position POINT pt ; GetCursorPos (&pt) ; // Fix Microsofts' BUG!!!! SetForegroundWindow(); /////////////////////////////////// // Display The Menu pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,pt.x, pt.y, AfxGetMainWnd()); break ; } case WM_LBUTTONDBLCLK: ////////////////////////////////// // Unhide our Window if (m_bHidden) { ShowWindow (SW_RESTORE); } //OnUnHide() ; break ; } return (0) ; } void CIcecast2winDlg::OnHidesystray() { // TODO: Add your control notification handler code here OnHide(); theApp.HideApplication(); } void CIcecast2winDlg::OnHide() { // TODO: Add your control notification handler code here if (m_pTray == NULL) { m_pTray = new CTrayNot (this,WM_TRAY_NOTIFY, NULL,theApp.m_pIconList); } m_pTray->SetState(0); m_bHidden = TRUE; } void CIcecast2winDlg::OnBlankRestore() { // TODO: Add your command handler code here if (m_bHidden) { ShowWindow (SW_RESTORE); } } void CIcecast2winDlg::OnDestroy() { CResizableDialog::OnDestroy(); if (m_pTray) { delete m_pTray ; m_pTray = NULL ; } // TODO: Add your message handler code here } void CIcecast2winDlg::OnFileEditconfiguration() { // TODO: Add your command handler code here STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); int ok = 1; if (global.running == ICE_RUNNING) { MessageBox("I'm sorry, but you cannot edit the configuration file while the server is running", NULL, MB_OK); } else { // Start the child process. if( !CreateProcess( NULL, // No module name (use command line). "notepad icecast.xml", // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) // Pointer to PROCESS_INFORMATION structure. ) { ok = 0; } // Wait until child process exits. WaitForSingleObject( pi.hProcess, INFINITE ); // Close process and thread handles. CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); } } void CIcecast2winDlg::OnAboutHelp() { // TODO: Add your command handler code here ShellExecute(NULL, "open", "doc\\icecast2.chm", NULL, NULL, SW_SHOWNORMAL); }