// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
//
// This file is part of the VNC system.
//
// The VNC system is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// If the source code for the VNC system is not available from the place
// whence you received this file, check http://www.uk.research.att.com/vnc or contact
// the authors on vnc@uk.research.att.com for information on obtaining it.
// Daemon.cpp: implementation of the Daemon class.
#include "stdhdrs.h"
#include "vncviewer.h"
#include "Daemon.h"
#include "Exception.h"
#include "ClientConnection.h"
#include "AboutBox.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
#define DAEMON_CLASS_NAME "VNCviewer Daemon"
Daemon::Daemon(int port)
{
// Create a dummy window
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wndclass.lpfnWndProc = Daemon::WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = pApp->m_instance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = (const char *) NULL;
wndclass.lpszClassName = DAEMON_CLASS_NAME;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
m_hwnd = CreateWindow(DAEMON_CLASS_NAME,
DAEMON_CLASS_NAME,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
200, 200,
NULL,
NULL,
pApp->m_instance,
NULL);
// record which client created this window
SetWindowLong(m_hwnd, GWL_USERDATA, (LONG) this);
// Load a popup menu
m_hmenu = LoadMenu(pApp->m_instance, MAKEINTRESOURCE(IDR_TRAYMENU));
// Create a listening socket
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
m_sock = socket(AF_INET, SOCK_STREAM, 0);
if (!m_sock) throw WarningException("Error creating Daemon socket");
try {
int one = 1, res = 0;
//res = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &one, sizeof(one));
//if (res == SOCKET_ERROR)
// throw WarningException("Error setting Daemon socket options");
res = bind(m_sock, (struct sockaddr *)&addr, sizeof(addr));
if (res == SOCKET_ERROR)
throw WarningException("Error binding Daemon socket");
res = listen(m_sock, 5);
if (res == SOCKET_ERROR)
throw WarningException("Error when Daemon listens");
} catch (...) {
closesocket(m_sock);
m_sock = 0;
throw;
}
// Send a message to specified window on an incoming connection
WSAAsyncSelect (m_sock, m_hwnd, WM_SOCKEVENT, FD_ACCEPT);
// Create the tray icon
AddTrayIcon();
// A timer checks that the tray icon is intact
m_timer = SetTimer( m_hwnd, IDT_TRAYTIMER, 15000, NULL);
}
void Daemon::AddTrayIcon() {
log.Print(4, _T("Adding tray icon\n"));
SendTrayMsg(NIM_ADD);
}
void Daemon::CheckTrayIcon() {
log.Print(8, _T("Checking tray icon\n"));
if (!SendTrayMsg(NIM_MODIFY)) {
log.Print(4, _T("Tray icon not there - reinstalling\n"));
AddTrayIcon();
};
}
void Daemon::RemoveTrayIcon() {
log.Print(4, _T("Deleting tray icon\n"));
SendTrayMsg(NIM_DELETE);
}
bool Daemon::SendTrayMsg(DWORD msg)
{
m_nid.hWnd = m_hwnd;
m_nid.cbSize = sizeof(m_nid);
m_nid.uID = IDR_TRAY; // never changes after construction
m_nid.hIcon = LoadIcon(pApp->m_instance, MAKEINTRESOURCE(IDR_TRAY));
m_nid.uFlags = NIF_ICON | NIF_MESSAGE;
m_nid.uCallbackMessage = WM_TRAYNOTIFY;
m_nid.szTip[0] = '\0';
// Use resource string as tip if there is one
if (LoadString(pApp->m_instance, IDR_TRAY, m_nid.szTip, sizeof(m_nid.szTip))) {
m_nid.uFlags |= NIF_TIP;
}
return (bool) (Shell_NotifyIcon(msg, &m_nid) != 0);
}
// Process window messages
LRESULT CALLBACK Daemon::WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
// This is a static method, so we don't know which instantiation we're
// dealing with. We have stored a pseudo-this in the window user data,
// though.
Daemon *_this = (Daemon *) GetWindowLong(hwnd, GWL_USERDATA);
switch (iMsg) {
case WM_CREATE:
{
return 0;
}
case WM_SOCKEVENT:
{
assert(HIWORD(lParam) == 0);
// A new socket created by accept might send messages to
// this procedure. We can ignore them.
if(wParam != _this->m_sock) {
return 0;
}
switch(lParam) {
case FD_ACCEPT:
{
SOCKET hNewSock;
hNewSock = accept(_this->m_sock, NULL, NULL);
WSAAsyncSelect(hNewSock, hwnd, 0, 0);
unsigned long nbarg = 0;
ioctlsocket(hNewSock, FIONBIO, &nbarg);
pApp->NewConnection(hNewSock);
break;
}
case FD_READ:
{
unsigned long numbytes;
ioctlsocket(_this->m_sock, FIONREAD, &numbytes);
recv(_this->m_sock, _this->netbuf, numbytes, 0);
break;
}
case FD_CLOSE:
log.Print(5, _T("Daemon connection closed\n"));
DestroyWindow(hwnd);
break;
}
return 0;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_NEWCONN:
pApp->NewConnection();
break;
case IDC_OPTIONBUTTON:
pApp->m_options.DoDialog();
break;
case ID_CLOSEDAEMON:
PostQuitMessage(0);
break;
case IDD_APP_ABOUT:
ShowAboutBox();
break;
}
return 0;
case WM_TRAYNOTIFY:
{
HMENU hSubMenu = GetSubMenu(_this->m_hmenu, 0);
if (lParam==WM_LBUTTONDBLCLK) {
// double click: execute first menu item
::SendMessage(_this->m_nid.hWnd, WM_COMMAND,
GetMenuItemID(hSubMenu, 0), 0);
} else if (lParam==WM_RBUTTONUP || lParam==WM_LBUTTONUP) {
if (hSubMenu == NULL) {
log.Print(2, _T("No systray submenu\n"));
return 0;
}
// Make first menu item the default (bold font)
::SetMenuDefaultItem(hSubMenu, 0, TRUE);
// Display the menu at the current mouse location. There's a "bug"
// (Microsoft calls it a feature) in Windows 95 that requires calling
// SetForegroundWindow. To find out more, search for Q135788 in MSDN.
//
POINT mouse;
GetCursorPos(&mouse);
::SetForegroundWindow(_this->m_nid.hWnd);
::TrackPopupMenu(hSubMenu, 0, mouse.x, mouse.y, 0,
_this->m_nid.hWnd, NULL);
}
return 0;
}
case WM_TIMER:
_this->CheckTrayIcon();
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Daemon::~Daemon()
{
KillTimer(m_hwnd, m_timer);
RemoveTrayIcon();
DestroyMenu(m_hmenu);
shutdown(m_sock, SD_BOTH);
closesocket(m_sock);
}