_NETWORKING WITH WINDOWS 3_ by Mike Klein [LISTING ONE] /* MEGAPHON.H */ #define MAX_CONNECTIONS 100 #define MAX_MESSAGE_LEN 56 #define IDT_MESSAGETIMER 100 #define IDC_USERLISTBOXTITLE 100 #define IDC_USERLISTBOX 101 #define IDC_SERVERCOMBOBOXTITLE 102 #define IDC_SERVERCOMBOBOX 103 #define IDC_MESSAGEEDITBOX 104 #define IDC_SENDBUTTON 105 #define IDC_SETTINGSBUTTON 106 #define IDC_EXITBUTTON 107 #define IDC_ACCEPTMESSAGES 100 #define IDC_ICONIZEMESSAGES 101 #define IDC_ONLYATTACHEDUSERS 102 #define IDC_ALLUSERSINBINDERY 103 #define IDC_USERNAME 100 #define IDC_STATION 101 #define IDC_NODE 102 #define IDC_FULLNAME 103 #define IDC_LOGINTIME 104 #define IDC_NETWORK 105 #define IDC_REPLYEDITBOX 100 #define IDC_REPLYBUTTON 101 #define IDC_SAVEBUTTON 102 #define IDM_EXIT 200 #define IDM_ABOUT 201 #define IDM_REFRESH 220 int PASCAL WinMain(HANDLE, HANDLE, LPSTR, int); LONG FAR PASCAL MainWndProc(HWND, unsigned, WORD, LONG); LONG FAR PASCAL UserInfo(HWND, unsigned, WORD, LONG); LONG FAR PASCAL MessageHandler(HWND, unsigned, WORD, LONG); BOOL FAR PASCAL About(HWND, unsigned, WORD, LONG); BOOL FAR PASCAL Settings(HWND, unsigned, WORD, LONG); BOOL PASCAL InitNetStuff(VOID); VOID PASCAL EnableOrDisableSendButton(VOID); VOID PASCAL SendNetWareMessageToUsers(VOID); VOID PASCAL ShowUserInformation(VOID); [LISTING TWO] /***************************************************************************** PROGRAM: Megaphone AUTHOR : Mike Klein VERSION: 1.0 FILE : megaphon.exe CREATED: 10-25-90 REQUIREMENTS: Windows 3.x and a Novell NetWare 2.1x or compatible network PURPOSE : Messaging and simple information system for Novell NetWare. Allows quick replies to messages from coworkers and miscellaneous login information about them. *****************************************************************************/ #define NOCOMM #include #include #include #include #include #include #include #include #include #include #include "megaphon.h" HANDLE hInstMegaphone; /* Original instance of Megaphone */ /* These are handles to commonly accessed controls in different dialogs */ HWND hWndCurrent; /* handle to currently active window on screen */ HWND hDlgMegaphone; /* handle to Megaphone dialog window */ HWND hWndUserListBox; HWND hWndServerComboBox; HWND hWndMessageEditBox; HWND hWndSendButton; /* Set up some additional global variables to commonly used NetWare stuff */ WORD DefaultConnectionID; BYTE ServerName[48]; WORD UserConnectionNum; BYTE UserName[48]; BYTE UserLoginTime[7]; WORD SelUserConnectionNum; BYTE SelUserNetworkAddr[4]; BYTE SelUserNodeAddr[6]; BYTE SelUserName[48]; BYTE SelUserFullName[48]; BYTE SelUserLoginTime[7]; BOOL AcceptMessages = TRUE; BOOL IconizeMessages = FALSE; BOOL AllUsers = FALSE; BYTE Text[100]; /***************************************************************************** FUNCTION: WinMain PURPOSE : Calls initialization function, processes message loop *****************************************************************************/ int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASS wc; MSG msg; if(!hPrevInstance) /* Other instances of app running? */ { hInstMegaphone = hInstance; /* Remember original instance */ /* Fill in window class structure with parameters that describe the */ /* main window. */ wc.style = CS_DBLCLKS; /* Process double click msgs */ wc.lpfnWndProc = MainWndProc; /* Function to retrieve msgs for */ /* windows of this class. */ wc.cbClsExtra = 0; /* No per-class extra data. */ wc.cbWndExtra = DLGWINDOWEXTRA; /* Set becuase we used the CLASS */ /* statement in dialog box */ wc.hInstance = hInstance; /* Application that owns the class.*/ wc.hIcon = LoadIcon(hInstance, "Megaphone"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "Megaphone"; wc.lpszClassName = "Megaphone"; if(!RegisterClass(&wc)) return(FALSE); /* Fill in window class structure with parameters that describe the */ /* message window. */ wc.style = NULL; /* Process double click msgs */ wc.lpfnWndProc = MessageHandler; wc.cbClsExtra = 0; /* No per-class extra data. */ wc.cbWndExtra = DLGWINDOWEXTRA; /* Set becuase we used the CLASS */ /* statement in dialog box */ wc.hInstance = hInstance; /* Application that owns the class.*/ wc.hIcon = LoadIcon(hInstance, "Message"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "Message"; if(!RegisterClass(&wc)) return(FALSE); /* Fill in window class structure with parameters that describe the */ /* userinfo window. */ wc.style = NULL; /* Process double click msgs */ wc.lpfnWndProc = UserInfo; wc.cbClsExtra = 0; /* No per-class extra data. */ wc.cbWndExtra = DLGWINDOWEXTRA; /* Set becuase we used the CLASS */ /* statement in dialog box */ wc.hInstance = hInstance; /* Application that owns the class.*/ wc.hIcon = LoadIcon(hInstance, "User"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "User"; if(!RegisterClass(&wc)) return(FALSE); /* Create the main dialog window Megaphone, get window handles to */ /* several of the controls, send the message edit box a message to */ /* limit itself to MAX_MESSAGE_LEN characters, and set the system */ /* font (mono-spaced) for server combo box and user list box. */ hDlgMegaphone = CreateDialog(hInstance, "Megaphone", NULL, 0L); hWndUserListBox = GetDlgItem(hDlgMegaphone, IDC_USERLISTBOX); hWndServerComboBox = GetDlgItem(hDlgMegaphone, IDC_SERVERCOMBOBOX); hWndMessageEditBox = GetDlgItem(hDlgMegaphone, IDC_MESSAGEEDITBOX); hWndSendButton = GetDlgItem(hDlgMegaphone, IDC_SENDBUTTON); SendMessage(hWndMessageEditBox, EM_LIMITTEXT, MAX_MESSAGE_LEN - 1, 0L); SendMessage(hWndUserListBox, WM_SETFONT, GetStockObject(SYSTEM_FIXED_FONT), FALSE); SendMessage(hWndServerComboBox, WM_SETFONT, GetStockObject(SYSTEM_FIXED_FONT), FALSE); /* Finally, show the Megaphone dialog box */ ShowWindow(hDlgMegaphone, nCmdShow); UpdateWindow(hDlgMegaphone); /* Initialize the network stuff, and fill in list boxes */ InitNetStuff(); } else { /* If there was another instance of Megaphone running, then switch to */ /* it by finding any window of class = "Megaphone". Then, if it's an */ /* icon, open the window, otherwise just make it active. */ hDlgMegaphone = FindWindow("Megaphone", NULL); if(IsIconic(hDlgMegaphone)) ShowWindow(hDlgMegaphone, SW_SHOWNORMAL); SetActiveWindow(hDlgMegaphone); return(FALSE); } /* Acquire and dispatch messages until a WM_QUIT message is received. */ /* The window handle hWndCurrent points to the currently active window, */ /* and is used to identify and process keystrokes going to any modeless */ /* dialog box. */ while(GetMessage(&msg, NULL, NULL, NULL)) if(hWndCurrent == NULL || !IsDialogMessage(hWndCurrent, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } /***************************************************************************** FUNCTION: MainWndProc PURPOSE : Processes messages for Megaphone dialog box *****************************************************************************/ long FAR PASCAL MainWndProc(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam) { FARPROC lpProc; /* Far procedure ptr to be used for About box */ HWND hWndTemp; HWND hDlgMessage; BYTE *ptr; int Index = 100; BYTE MessageCaption[100]; BYTE MessageDate[9]; BYTE MessageTime[9]; switch(wMsg) { case WM_COMMAND : switch(wParam) { /* When wParam == 1, return was pressed in either the list box, */ /* combo box, check boxes, or edit control. */ case 1 : /* Find out the current control (window) and process ENTER */ /* key accordingly. */ switch(GetDlgCtrlID(GetFocus())) { case IDC_USERLISTBOX : ShowUserInformation(); break; case IDC_SERVERCOMBOBOX : SendMessage(hWndServerComboBox, WM_KILLFOCUS, NULL, 0L); SendMessage(hWndServerComboBox, WM_SETFOCUS, NULL, 0L); break; case IDC_MESSAGEEDITBOX : if(IsWindowEnabled(hWndSendButton)) { SendMessage(hWndSendButton, WM_LBUTTONDOWN, 0, 0L); SendMessage(hWndSendButton, WM_LBUTTONUP, 0, 0L); SendMessage(hDlgMegaphone, WM_NEXTDLGCTL, hWndMessageEditBox, TRUE); } else { MessageBeep(0); MessageBox(hDlgMegaphone, "You need a message and a user(s)", "ERROR", MB_ICONEXCLAMATION | MB_OK); } break; default : break; } break; case IDC_USERLISTBOX : if(HIWORD(lParam) == LBN_DBLCLK) { /* Restore the list box item's selection state. If it */ /* isn't flagged, then flag it, and vice versa. */ Index = (int) SendMessage(hWndUserListBox, LB_GETCURSEL, 0, 0L); if(SendMessage(hWndUserListBox, LB_GETSEL, Index, 0L)) SendMessage(hWndUserListBox, LB_SETSEL, FALSE, Index); else SendMessage(hWndUserListBox, LB_SETSEL, TRUE, Index); ShowUserInformation(); } else EnableOrDisableSendButton(); break; case IDC_SERVERCOMBOBOX : if(HIWORD(lParam) == CBN_SELCHANGE) { if((Index = (int) SendMessage(hWndServerComboBox, CB_GETCURSEL, 0, 0L)) == CB_ERR) break; SendMessage(hWndServerComboBox, CB_GETLBTEXT, Index, (LONG) (LPSTR) ServerName); if(!GetConnectionID(ServerName, &DefaultConnectionID)) { SetPreferredConnectionID(DefaultConnectionID); InitNetStuff(); } } break; case IDC_MESSAGEEDITBOX : EnableOrDisableSendButton(); break; case IDC_SENDBUTTON : if(HIWORD(lParam) == BN_CLICKED) SendNetWareMessageToUsers(); break; case IDM_EXIT : case IDC_EXITBUTTON : SendMessage(hDlgMegaphone, WM_CLOSE, 0, 0L); break; case IDM_ABOUT : lpProc = MakeProcInstance(About, hInstMegaphone); DialogBox(hInstMegaphone, "About", hWnd, lpProc); FreeProcInstance(lpProc); break; case IDM_REFRESH : InitNetStuff(); break; case IDC_SETTINGSBUTTON : lpProc = MakeProcInstance(Settings, hInstMegaphone); DialogBox(hInstMegaphone, "Settings", hWnd, lpProc); FreeProcInstance(lpProc); break; default : break; } break; case WM_TIMER : /* This is the Windows timer for retrieving messages that goes off */ /* every five seconds. */ GetBroadcastMessage(Text); if(*Text) { /* Create the message reply dialog box and limit the edit box */ /* to NetWare's limit of 56 or so characters. */ hDlgMessage = CreateDialog(hInstMegaphone, "Message", hDlgMegaphone, 0L); SendDlgItemMessage(hDlgMessage, IDC_REPLYEDITBOX, EM_LIMITTEXT, MAX_MESSAGE_LEN - 1, 0L); /* Parse the incoming string of 'username[station#]message' */ if((ptr = strchr(Text, '[')) == NULL) { /* If the incoming message isn't formatted by NetWare's */ /* SEND command, SESSION program, or Megaphone, then we */ /* can't use the REPLY button, so disable it. */ SelUserName[0] = '\0'; SelUserConnectionNum = 0; EnableWindow(GetDlgItem(hDlgMessage, IDC_REPLYBUTTON), FALSE); } else { /* Pull up the user name and connection#, and message, which */ /* is right after the ']'. */ strncpy(SelUserName, Text, ptr - Text); SelUserName[ptr - Text] = '\0'; SelUserConnectionNum = atoi(ptr + 1); if((ptr = strchr(Text, ']')) != NULL) lstrcpy((LPSTR) Text, (LPSTR) (ptr + 1)); /* Check again to see if we pulled up a valid Conn#. If we */ /* didn't, then disable the REPLY button. */ if(SelUserConnectionNum < 1 || SelUserConnectionNum > 255) EnableWindow(GetDlgItem(hDlgMessage, IDC_REPLYBUTTON), FALSE); } /* Put the retrieved message in the dialog's edit box */ SetDlgItemText(hDlgMessage, IDC_REPLYEDITBOX, Text); /* Record the date and time that the message came in at and */ /* make it reflected in the message caption. */ _strdate(MessageDate); _strtime(MessageTime); wsprintf(MessageCaption, "%s %s %s", (LPSTR) SelUserName, (LPSTR) MessageDate, (LPSTR) MessageTime); SetWindowText(hDlgMessage, MessageCaption); /* Finally, show (or minimize) the completed message dialog box */ if(IconizeMessages) ShowWindow(hDlgMessage, SW_SHOWMINNOACTIVE); else ShowWindow(hDlgMessage, SW_SHOWNORMAL); MessageBeep(0); } return(0L); case WM_CLOSE : /* Check before closing the main window if there are any */ /* outstanding messages that haven't been closed. */ if(hWndTemp = FindWindow((LPSTR) "Message", NULL)) { if(MessageBox(hDlgMegaphone, "Quit without disposing of/reading messages?", "Messages Outstanding", MB_YESNO | MB_APPLMODAL | MB_ICONEXCLAMATION | MB_DEFBUTTON2) == IDYES) { DestroyWindow(hDlgMegaphone); } else { ShowWindow(hWndTemp, SW_SHOWNORMAL); SetActiveWindow(hWndTemp); } } else DestroyWindow(hDlgMegaphone); return(0L); case WM_SETFOCUS : if(IsWindowEnabled(hWndMessageEditBox)) SendMessage(hDlgMegaphone, WM_NEXTDLGCTL, hWndMessageEditBox, TRUE); else SendMessage(hDlgMegaphone, WM_NEXTDLGCTL, GetDlgItem(hDlgMegaphone, IDC_EXITBUTTON), TRUE); return(0L); case WM_ACTIVATE : hWndCurrent = (wParam == NULL) ? NULL : hWnd; break; case WM_DESTROY : PostQuitMessage(0); return(0L); default : break; } return(DefDlgProc(hWnd, wMsg, wParam, lParam)); } /***************************************************************************** FUNCTION: SendNetWareMessageToUsers PURPOSE : Do I really need to explain this one? *****************************************************************************/ VOID PASCAL SendNetWareMessageToUsers(VOID) { BYTE Message[MAX_MESSAGE_LEN]; WORD ConnectionsToSend[MAX_CONNECTIONS]; BYTE ResultList[MAX_CONNECTIONS]; int NumUsers; int i, j; /* Get text inside message edit box and format message so it includes */ /* the username, connection#, and message. The first two fields are */ /* needed for replying back since there's nothing in NetWare's messaging */ /* facility to tell you who sent the message. */ GetDlgItemText(hDlgMegaphone, IDC_MESSAGEEDITBOX, (LPSTR) Text, MAX_MESSAGE_LEN); wsprintf(Message, "%s[%d]%s", (LPSTR) UserName, UserConnectionNum, (LPSTR) Text); /* Get total number of users in list box and check to see if they've */ /* been selected or not. If they have, get their Connection# and put it */ /* in the ConnectionsToSend array. */ NumUsers = (int) SendMessage(hWndUserListBox, LB_GETCOUNT, 0, 0L); for(i = j = 0; i < NumUsers; i++) if(SendMessage(hWndUserListBox, LB_GETSEL, i, 0L)) { SendMessage(hWndUserListBox, LB_GETTEXT, i, (LONG) (LPSTR) Text); ConnectionsToSend[j++] = atoi(&Text[18]); } /* Send the message to users in the array. */ SendBroadcastMessage(Message, ConnectionsToSend, ResultList, j); /* Scan through the ResultList array checking for messages that had */ /* problems. Selecting OK will continue to check the status of the other */ /* messages, where selecting CANCEL from the message box will abort the */ /* send status checking altogether. */ for(i = 0; i < j; i++) switch(ResultList[i]) { case 0xfc : wsprintf(Text, "Message to Connection %d", ConnectionsToSend[i]); if(MessageBox(hDlgMegaphone, "Message not sent - User already has message pending", Text, MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL) { i = j; } break; case 0xfd : wsprintf(Text, "Message to Connection %d", ConnectionsToSend[i]); if(MessageBox(hDlgMegaphone, "Message not sent - Invalid connection number", Text, MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL) { i = j; } break; case 0xff : wsprintf(Text, "Message to Connection %d", ConnectionsToSend[i]); if(MessageBox(hDlgMegaphone, "Message not sent - User has blocking turned on", Text, MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL) { i = j; } break; default : break; } } /***************************************************************************** FUNCTION: EnableOrDisableSendButton PURPOSE : Based on a message being in the edit box and at least one selected user, the send button is enabled or disabled *****************************************************************************/ VOID PASCAL EnableOrDisableSendButton(VOID) { /* Check to see if at least one user is selected and at least a one */ /* character message in the edit box. If there is, then enable the SEND */ /* button and thicken it to make it the default response when ENTER is */ /* pressed. */ if(SendMessage(hWndUserListBox, LB_GETSELCOUNT, 0, 0L) && SendMessage(hWndMessageEditBox, EM_LINELENGTH, -1, 0L)) { EnableWindow(hWndSendButton, TRUE); } else { EnableWindow(hWndSendButton, FALSE); } } /***************************************************************************** FUNCTION: InitNetStuff PURPOSE : Initialize network connections and fill in combo and list boxes *****************************************************************************/ BOOL PASCAL InitNetStuff(VOID) { HCURSOR hOldCursor; WORD NumberOfConnections; WORD NumberOfServers; int Index; BYTE DirHandle; BYTE TempServerName[48]; WORD ObjectType; WORD ConnID; WORD ConnectionList[MAX_CONNECTIONS]; BYTE SearchObjectName[48] = "*"; BYTE ObjectName[48]; long ObjectID; BYTE ObjectHasProperties; BYTE ObjectFlag; BYTE ObjectSecurity; /* Check to see if a connection has been made to any server */ if(UserConnectionNum = GetConnectionNumber()) if(!GetConnectionInformation(UserConnectionNum, UserName, &ObjectType, &ObjectID, UserLoginTime)) if(*UserName) { /* If we have a preferred connection ID, then were supposed to */ /* use it for all of our requests. If we don't, then check to */ /* see if we're sitting on a local drive (bit 0 or 1 not set). */ /* If we are, then set the default connection ID to that of the */ /* primary server. If we're sitting on a network drive, then */ /* requests go to the associated server. */ if(GetPreferredConnectionID()) DefaultConnectionID = GetPreferredConnectionID(); else { if(!(GetDriveInformation((BYTE) (_getdrive() - 1), &ConnID, &DirHandle) & 3)) { DefaultConnectionID = GetPrimaryConnectionID(); } SetPreferredConnectionID(DefaultConnectionID); } /* Set NetWare's message mode so that Megaphone can poll for */ /* messages instead of automatically having them sent to the */ /* station. */ EnableBroadcasts(); SetBroadcastMode(3); /* Set up a Windows timer so that every 5 seconds, the server is */ /* polled for waiting messages. */ SetTimer(hDlgMegaphone, IDT_MESSAGETIMER, 5000, NULL); EnableWindow(GetDlgItem(hDlgMegaphone, IDC_SETTINGSBUTTON), TRUE); } if(!UserConnectionNum) { EnableWindow(GetDlgItem(hDlgMegaphone, IDC_SETTINGSBUTTON), FALSE); MessageBox(hDlgMegaphone, "Must be logged into a NetWare server", "ERROR - NO USERS", MB_ICONSTOP | MB_OK); return(FALSE); } /* Now that we've established a network connection, let's fill in the */ /* drop-down combo box with file servers and the list box with users of */ /* whatever the node's preferred server is. */ /* Turn off re-drawing of the list box so it doesn't flicker, reset the */ /* contents of both boxes, capture and intercept all mouse activity, and */ /* turn the cursor into an hourglass. */ SendMessage(hWndUserListBox, WM_SETREDRAW, FALSE, 0L); SendMessage(hWndUserListBox, LB_RESETCONTENT, 0, 0L); SendMessage(hWndServerComboBox, CB_RESETCONTENT, 0, 0L); SetCapture(hDlgMegaphone); hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); /* Scan through the possible ConnectionID#'s (1-8) and see what file */ /* servers are attached, if any are, and put them in the combo box. */ for(ConnID = 1; ConnID < 9; ++ConnID) { GetFileServerName(ConnID, TempServerName); if(*TempServerName) SendMessage(hWndServerComboBox, CB_ADDSTRING, NULL, (LONG) (LPSTR) TempServerName); } /* Get default server */ GetFileServerName(DefaultConnectionID, ServerName); /* Search the NetWare bindery for active user connections, putting */ /* them into the list box */ ObjectID = -1; while(!ScanBinderyObject(SearchObjectName, OT_USER, &ObjectID, ObjectName, &ObjectType, &ObjectHasProperties, &ObjectFlag, &ObjectSecurity)) { GetObjectConnectionNumbers(ObjectName, OT_USER, &NumberOfConnections, ConnectionList, MAX_CONNECTIONS); /* If there are multiple connections for a single user then we */ /* have to make sure and get all of them. */ if(!NumberOfConnections) { if(AllUsers) { wsprintf(Text, "[%s]", (LPSTR) ObjectName); SendMessage(hWndUserListBox, LB_ADDSTRING, NULL, (LONG) (LPSTR) Text); } } else for(Index = 0; Index < (int) NumberOfConnections; ++Index) { if(UserConnectionNum == ConnectionList[Index]) wsprintf(Text, "%-16.16s *%3d", (LPSTR) ObjectName, ConnectionList[Index]); else wsprintf(Text, "%-17.17s %3d", (LPSTR) ObjectName, ConnectionList[Index]); SendMessage(hWndUserListBox, LB_ADDSTRING, NULL, (LONG) (LPSTR) Text); } } /* Turn re-drawing for the list box back on and make the first item in */ /* the server combo box and user list box the default. */ InvalidateRect(hWndUserListBox, NULL, TRUE); SendMessage(hWndUserListBox, LB_SETSEL, 0, 0L); SendMessage(hWndUserListBox, WM_SETREDRAW, TRUE, 0L); /* Select the default server in the server combo box */ SendMessage(hWndServerComboBox, CB_SELECTSTRING, -1, (LONG) (LPSTR) ServerName); /* Add the # of servers and users to the caption on the server and list */ /* boxes. */ NumberOfConnections = (int) SendMessage(hWndUserListBox, LB_GETCOUNT, 0, 0L); wsprintf(Text, "%d &Users on %s", NumberOfConnections, (LPSTR) ServerName); SetDlgItemText(hDlgMegaphone, IDC_USERLISTBOXTITLE, (LPSTR) Text); NumberOfServers = (int) SendMessage(hWndServerComboBox, CB_GETCOUNT, 0, 0L); wsprintf(Text, "%d Ser&vers", NumberOfServers); SetDlgItemText(hDlgMegaphone, IDC_SERVERCOMBOBOXTITLE, (LPSTR) Text); /* Restore mouse activity, set the cursor back to normal, and initially */ /* disable the Send button. */ ReleaseCapture(); SetCursor(hOldCursor); EnableOrDisableSendButton(); return(TRUE); } /***************************************************************************** FUNCTION: About PURPOSE : Processes messages for About box *****************************************************************************/ BOOL FAR PASCAL About(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam) { switch(wMsg) { case WM_INITDIALOG : return(TRUE); case WM_COMMAND : if(wParam == IDOK || wParam == IDCANCEL) { EndDialog(hWnd, TRUE); return(TRUE); } break; default : break; } return(FALSE); } /***************************************************************************** FUNCTION: Settings PURPOSE : Processes messages for Settings window *****************************************************************************/ BOOL FAR PASCAL Settings(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam) { switch(wMsg) { case WM_INITDIALOG : CheckDlgButton(hWnd, IDC_ACCEPTMESSAGES, AcceptMessages); CheckDlgButton(hWnd, IDC_ICONIZEMESSAGES, IconizeMessages); if(AllUsers) CheckRadioButton(hWnd, IDC_ALLUSERSINBINDERY, IDC_ALLUSERSINBINDERY, IDC_ALLUSERSINBINDERY); else CheckRadioButton(hWnd, IDC_ONLYATTACHEDUSERS, IDC_ONLYATTACHEDUSERS, IDC_ONLYATTACHEDUSERS); break; case WM_COMMAND : switch(wParam) { case IDC_ACCEPTMESSAGES : if(IsDlgButtonChecked(hWnd, IDC_ACCEPTMESSAGES)) { EnableBroadcasts(); SetTimer(hDlgMegaphone, IDT_MESSAGETIMER, 5000, NULL); AcceptMessages = TRUE; } else { DisableBroadcasts(); KillTimer(hDlgMegaphone, IDT_MESSAGETIMER); AcceptMessages = FALSE; } break; case IDC_ICONIZEMESSAGES : if(IsDlgButtonChecked(hWnd, IDC_ICONIZEMESSAGES)) IconizeMessages = TRUE; else IconizeMessages = FALSE; break; case IDC_ONLYATTACHEDUSERS : AllUsers = FALSE; InitNetStuff(); break; case IDC_ALLUSERSINBINDERY : AllUsers = TRUE; InitNetStuff(); break; case IDOK : case IDCANCEL : EndDialog(hWnd, TRUE); return(TRUE); default : break; } } return(FALSE); } /***************************************************************************** FUNCTION: MessageHandler PURPOSE : Processes messages for user message dialog box/window *****************************************************************************/ long FAR PASCAL MessageHandler(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam) { BYTE Message[MAX_MESSAGE_LEN]; WORD ConnectionsToSend[1]; BYTE ResultList[1]; switch(wMsg) { case WM_COMMAND : switch(wParam) { case 1 : /* A '1' is generated when ENTER is pressed while in a */ /* control in a dialog box and there's no default button */ /* defined. This is to trap the ENTER key when in the reply */ /* edit box. We'll simulate the pressing of the REPLY button */ if(IsWindowEnabled(GetDlgItem(hWnd, IDC_REPLYBUTTON))) { SendMessage(GetDlgItem(hWnd, IDC_REPLYBUTTON), WM_LBUTTONDOWN, 0, 0L); SendMessage(GetDlgItem(hWnd, IDC_REPLYBUTTON), WM_LBUTTONUP, 0, 0L); } else { MessageBeep(0); MessageBox(hWnd, "You cannot reply to this message", "ERROR", MB_ICONEXCLAMATION | MB_OK); } break; case IDC_SAVEBUTTON : /* "Save" the message by iconizing it */ CloseWindow(hWnd); break; case IDC_REPLYBUTTON : /* Get text from edit box located in message dialog box */ GetDlgItemText(hWnd, IDC_REPLYEDITBOX, (LPSTR) Text, MAX_MESSAGE_LEN); /* Set up my connection# to send to, format the message, and */ /* send it to the user. */ ConnectionsToSend[0] = SelUserConnectionNum; wsprintf(Message, "%s[%d]%s", (LPSTR) SelUserName, SelUserConnectionNum, (LPSTR) Text); SendBroadcastMessage(Message, ConnectionsToSend, ResultList, 1); /* Possible results of sending a message */ switch(ResultList[0]) { case 0xfc : wsprintf(Text, "Message to Connection %d", ConnectionsToSend[0]); MessageBox(hDlgMegaphone, "Message not sent - User already has message pending", Text, MB_OK | MB_ICONEXCLAMATION); break; case 0xfd : wsprintf(Text, "Message to Connection %d", ConnectionsToSend[0]); MessageBox(hDlgMegaphone, "Message not sent - Invalid connection number", Text, MB_OK | MB_ICONEXCLAMATION); break; case 0xff : wsprintf(Text, "Message to Connection %d", ConnectionsToSend[0]); MessageBox(hDlgMegaphone, "Message not sent - User has blocking turned on", Text, MB_OK | MB_ICONEXCLAMATION); break; default : break; } /* Get rid of the message reply dialog box/window */ DestroyWindow(hWnd); return(0L); case IDCANCEL : DestroyWindow(hWnd); return(0L); default : break; } break; case WM_CLOSE : DestroyWindow(hWnd); return(0L); case WM_SETFOCUS : /* Set the focus to the edit control. */ SendMessage(hWnd, WM_NEXTDLGCTL, GetDlgItem(hWnd, IDC_REPLYEDITBOX), TRUE); return(0L); case WM_ACTIVATE : hWndCurrent = (wParam == NULL) ? NULL : hWnd; break; default : break; } return(DefDlgProc(hWnd, wMsg, wParam, lParam)); } /***************************************************************************** FUNCTION: ShowUserInformation PURPOSE : Shows user information on current or double-clicked entry *****************************************************************************/ VOID PASCAL ShowUserInformation(VOID) { int Index; BYTE *ptr; HWND hDlgUserInfo; WORD ObjectType; long ObjectID; WORD SocketNum; BYTE PropertyValue[128]; BYTE MoreSegments; BYTE PropertyFlags; /* Get an index to the user name underneath the cursor, */ /* and then get the user's connection number so we can */ /* retrieve NetWare information on him/her. */ Index = (int) SendMessage(hWndUserListBox, LB_GETCURSEL, 0, 0L); SendMessage(hWndUserListBox, LB_GETTEXT, Index, (LONG) (LPSTR) Text); /* If entry in list box doesn't have a connection #, then we need to get */ /* the login name by parsing the list box string, we can't use a call to */ /* GetConnectionInformation(). */ memset(SelUserLoginTime, '\0', 7); memset(SelUserNetworkAddr, '\0', 4); memset(SelUserNodeAddr, '\0', 6); SelUserFullName[0] = '\0'; SelUserConnectionNum = 0; if(Text[0] == '[') { ptr = strchr(Text, ']'); strncpy(SelUserName, Text + 1, ptr - Text - 1); SelUserName[ptr - Text - 1] = '\0'; } else { ptr = strchr(Text, ' '); strncpy(SelUserName, Text, ptr - Text); SelUserConnectionNum = atoi(&Text[18]); SelUserName[ptr - Text] = '\0'; /* We can get connection info only for users that are logged in to a */ /* server. So, get the user name and login time, network and node */ /* address, and full name for specified connection#. */ GetConnectionInformation(SelUserConnectionNum, SelUserName, &ObjectType, &ObjectID, SelUserLoginTime); GetInternetAddress(SelUserConnectionNum, SelUserNetworkAddr, SelUserNodeAddr, &SocketNum); } if(!ReadPropertyValue(SelUserName, OT_USER, "IDENTIFICATION", 1, PropertyValue, &MoreSegments, &PropertyFlags)) { wsprintf(SelUserFullName, "%s", (LPSTR) PropertyValue); } /* Create userinfo dialog box and change caption to the */ /* user's login name. */ hDlgUserInfo = CreateDialog(hInstMegaphone, "UserInfo", hDlgMegaphone, 0L); SetWindowText(hDlgUserInfo, SelUserName); /* Initialize the user info dialog box by changing */ /* the values of different static text fields to */ /* reflect acquired user information */ wsprintf(Text, ": %s", (LPSTR) SelUserName); SetDlgItemText(hDlgUserInfo, IDC_USERNAME, (LPSTR) Text); wsprintf(Text, ": %d", SelUserConnectionNum); SetDlgItemText(hDlgUserInfo, IDC_STATION, (LPSTR) Text); wsprintf(Text, ": %s", (LPSTR) SelUserFullName); SetDlgItemText(hDlgUserInfo, IDC_FULLNAME, (LPSTR) Text); wsprintf(Text, ": %02d/%02d/%02d %02d:%02d:%02d", SelUserLoginTime[1], SelUserLoginTime[2], SelUserLoginTime[0], SelUserLoginTime[3], SelUserLoginTime[4], SelUserLoginTime[5]); SetDlgItemText(hDlgUserInfo, IDC_LOGINTIME, (LPSTR) Text); wsprintf(Text, ": %02X%02X%02X%02X", SelUserNetworkAddr[0], SelUserNetworkAddr[1], SelUserNetworkAddr[2], SelUserNetworkAddr[3]); SetDlgItemText(hDlgUserInfo, IDC_NETWORK, (LPSTR) Text); wsprintf(Text, ": %02X%02X%02X%02X%02X%02X", SelUserNodeAddr[0], SelUserNodeAddr[1], SelUserNodeAddr[2], SelUserNodeAddr[3], SelUserNodeAddr[4], SelUserNodeAddr[5]); SetDlgItemText(hDlgUserInfo, IDC_NODE, (LPSTR) Text); ShowWindow(hDlgUserInfo, SW_SHOWNORMAL); } /***************************************************************************** FUNCTION: UserInfo PURPOSE : Processes messages for user info box *****************************************************************************/ long FAR PASCAL UserInfo(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam) { switch(wMsg) { case WM_COMMAND : if(wParam == IDOK || wParam == IDCANCEL) { DestroyWindow(hWnd); return(0L); } break; case WM_CLOSE : DestroyWindow(hWnd); return(0L); case WM_ACTIVATE : hWndCurrent = (wParam == NULL) ? NULL : hWnd; break; default : break; } return(DefDlgProc(hWnd, wMsg, wParam, lParam)); } [LISTING THREE] #include "windows.h" #include "megaphon.h" Megaphone ICON MEGAPHON.ICO Message ICON MESSAGE.ICO User ICON USER.ICO Megaphone MENU BEGIN POPUP "&File" BEGIN MENUITEM "&Exit", IDM_EXIT MENUITEM SEPARATOR MENUITEM "&About ...", IDM_ABOUT END MENUITEM "&Refresh!", IDM_REFRESH END Megaphone DIALOG 65, 75, 165, 139 CLASS "Megaphone" CAPTION "Megaphone - NetWare Intercom" STYLE WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX BEGIN CONTROL "Megaphone", -1, "static", SS_ICON | WS_CHILD, 24, 6, 0, 0 CONTROL "&Users", IDC_USERLISTBOXTITLE, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 65, 2, 95, 9 CONTROL "", IDC_USERLISTBOX, "listbox", LBS_STANDARD | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP | WS_CHILD, 65, 12, 95, 71 CONTROL "Ser&vers", IDC_SERVERCOMBOBOXTITLE, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 5, 23, 55, 9 CONTROL "", IDC_SERVERCOMBOBOX, "combobox", CBS_HASSTRINGS | CBS_SORT | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP | WS_CHILD, 5, 33, 55, 63 CONTROL "&Message", -1, "static", SS_LEFT | WS_CHILD, 5, 80, 55, 9 CONTROL "", IDC_MESSAGEEDITBOX, "edit", ES_LEFT | ES_AUTOHSCROLL | ES_UPPERCASE | WS_BORDER | WS_TABSTOP | WS_CHILD, 5, 90, 155, 12 CONTROL "&Send", IDC_SENDBUTTON, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 5, 110, 45, 15 CONTROL "Se&ttings", IDC_SETTINGSBUTTON, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 60, 110, 45, 15 CONTROL "&Exit", IDC_EXITBUTTON, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 115, 110, 45, 15 END Message DIALOG 100, 100, 170, 55 CAPTION "Message" CLASS "Message" STYLE WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX BEGIN CONTROL "", IDC_REPLYEDITBOX, "edit", ES_LEFT | ES_AUTOHSCROLL | ES_UPPERCASE | WS_BORDER | WS_TABSTOP | WS_CHILD, 5, 10, 155, 12 CONTROL "&Reply", IDC_REPLYBUTTON, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 5, 30, 45, 15 CONTROL "&Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 60, 30, 45, 15 CONTROL "&Save", IDC_SAVEBUTTON, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 115, 30, 45, 15 END UserInfo DIALOG 68, 54, 145, 79 CAPTION "User Information" CLASS "User" STYLE WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX BEGIN CONTROL "User", -1, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 42, 3, 18, 9 CONTROL ":", IDC_USERNAME, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 60, 3, 82, 9 CONTROL "Stn", -1, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 42, 12, 18, 9 CONTROL ":", IDC_STATION, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 60, 12, 77, 9 CONTROL "Node", -1, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 42, 21, 18, 9 CONTROL ":", IDC_NODE, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 60, 21, 77, 9 CONTROL "Full Name", -1, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 3, 30, 39, 9 CONTROL ":", IDC_FULLNAME, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 42, 30, 95, 9 CONTROL "Login Time", -1, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 3, 39, 39, 9 CONTROL ":", IDC_LOGINTIME, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 42, 39, 95, 9 CONTROL "Network", -1, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 3, 48, 39, 9 CONTROL ":", IDC_NETWORK, "static", SS_LEFTNOWORDWRAP | WS_CHILD, 42, 48, 95, 9 CONTROL "User", -1, "static", SS_ICON | WS_CHILD, 11, 6, 15, 15 DEFPUSHBUTTON "OK", IDOK, 39, 60, 45, 15 END Settings DIALOG 11, 21, 175, 65 CAPTION "Settings" STYLE WS_POPUPWINDOW | WS_CAPTION BEGIN CONTROL "&Incoming Messages", -1, "button", BS_GROUPBOX | WS_CHILD, 5, 5, 75, 35 CONTROL "Accept", IDC_ACCEPTMESSAGES, "button", BS_AUTOCHECKBOX | WS_TABSTOP | WS_CHILD, 10, 15, 60, 10 CONTROL "Iconize", IDC_ICONIZEMESSAGES, "button", BS_AUTOCHECKBOX | WS_TABSTOP | WS_CHILD, 10, 25, 60, 10 CONTROL "&Users to scan", -1, "button", BS_GROUPBOX | WS_CHILD, 85, 5, 85, 35 CONTROL "Attached users only", IDC_ONLYATTACHEDUSERS, "button", BS_AUTORADIOBUTTON | WS_CHILD, 90, 15, 75, 10 CONTROL "Include unattached", IDC_ALLUSERSINBINDERY, "button", BS_AUTORADIOBUTTON | WS_CHILD, 90, 25, 75, 10 CONTROL "&OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_TABSTOP | WS_CHILD, 55, 45, 55, 15 END About DIALOG 22, 17, 110, 80 CAPTION "About" STYLE WS_POPUPWINDOW | WS_CAPTION BEGIN CTEXT "Megaphone - NetWare Intercom" -1, 0, 5, 110, 8 CTEXT "Version 1.0" -1, 0, 14, 110, 8 CTEXT "by Mike Klein" -1, 0, 22, 110, 8 ICON "User" -1, 13, 35, 0, 0 ICON "Megaphone" -1, 48, 35, 0, 0 ICON "Message" -1, 82, 35, 0, 0 DEFPUSHBUTTON "OK" IDOK, 33, 60, 45, 15 END [LISTING FOUR] ; module-definition file for Megaphone -- used by LINK.EXE NAME Megaphone ; application's module name DESCRIPTION 'Megaphone - NetWare Intercom' EXETYPE WINDOWS ; required for all Windows applications STUB 'WINSTUB.EXE' ; Generates error message if application ; is run without Windows ;CODE can be moved in memory and discarded/reloaded CODE PRELOAD MOVEABLE DISCARDABLE ;DATA must be MULTIPLE if program can be invoked more than once DATA PRELOAD MOVEABLE MULTIPLE HEAPSIZE 1024 STACKSIZE 5120 ; recommended minimum for Windows applications ; All functions that will be called by any Windows routine ; MUST be exported. EXPORTS MainWndProc @1 About @2 UserInfo @3 MessageHandler @4 Settings @5 [LISTING FIVE] # Standard Windows make file. The utility MAKE.EXE compares the # creation date of the file to the left of the colon with the file(s) # to the right of the colon. If the file(s) on the right are newer # then the file on the left, Make will execute all of the command lines # following this line that are indented by at least one tab or space. # Any valid MS-DOS command line may be used. # This line allows NMAKE to work as well all: megaphon.exe # Update the resource if necessary megaphon.res: megaphon.rc megaphon.h megaphon.ico rc -r megaphon.rc # Update the object file if necessary megaphon.obj: megaphon.c megaphon.h cl -W4 -c -AS -Gsw -Oad -Zp megaphon.c # Update the executable file if necessary, and if so, add the resource back in. megaphon.exe: megaphon.obj megaphon.def link /NOD megaphon,,, libw slibcew snit, megaphon.def rc megaphon.res # If the .res file is new and the .exe file is not, update the resource. # Note that the .rc file can be updated without having to either # compile or link the file. megaphon.exe: megaphon.res rc megaphon.res