Related
Hi
I want to make a call with my mda without using the RAS dialer. Direct access to the COM-ports does not work, as described in the document "Serial Communications". But that documents mentions that TAPI calls are possible with an xda.
It says:
"If you´ve establised a call, TAPI returns a handle for further data-i/o..."
Is there an example how to make a call using TAPI especially how to get that data-handle. And furthermore can I use that handle with WriteFile / ReadFile as I would use a handle to a COM-Port?
I did this for a terminal emulator I wrote. I pass a string to these functions which is COM1: for serial, TAPI for modem or RAS for TCPIP RAS.
I have simply copied some of my code here, work through it as if lpszPortName = "TAPI" and you should make sense of it. Once you have the port handle hPort which is returned by the Connected response of the TAPI call back function, then you can read and write as if that were a serial/file handle.
Cheers
Paul
#include <windows.h>
#include <string.h>
#include <tapi.h>
#include "toolbox.h"
//Initilise the port either the TAPI or COMM port as requested by lpszPortName
BOOL PortInitialize (LPTSTR lpszPortName)
{
DWORD dwError;
BOOL RetValue;
HKEY hKey;
DWORD RegKeyDisp, RegKeyType, RegKeySize;
DWORD dwTapiNumDevs;
LONG lTapiReturn;
char PrintString[80];
TCHAR TempString[20];
char CharString[20];
LINEDEVCAPS TapiLineDevCaps;
static DWORD dwLocalAPIVersion;
LINEEXTENSIONID LineExtensionID;
LINECALLPARAMS LineCallParams;
static DWORD ChoosenDevice;
TCHAR DialNumber[30];
#ifdef _WIN32_WCE_EMULATION
if (wcscmp(lpszPortName, TEXT("TAPI")) != 0)
return TRUE; //If running under emulator then unable to emulate
#endif //the serial port, so in this 'test' mode just echo.
if (COMMPORTSHUT == TRUE) //If this is True then we are shutting down the program
return TRUE; //So don't try to re-open port as it gets stuck in an infinate loop
if (wcscmp(lpszPortName, TEXT("TAPI")) == 0)
{
#ifdef DEBUGVERSION
VTPrint("Entering (TAPI)PortInitilize\r\n" , 0);
#endif
//Retrive the telephone number from the registry
// Fill in the Modem Telephone number
wcscpy(DialNumber, TEXT("T"));
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE,
TEXT("Software\\PVG\\Terminal\\Settings"),
0, NULL, 0,0, NULL, &hKey, &RegKeyDisp) == ERROR_SUCCESS)
{
//Retrive the telephone number
RegKeyType = REG_SZ;
RegKeySize = sizeof(DialNumber);
RegQueryValueEx(hKey, TEXT("Telephone"),
NULL, &RegKeyType,
(PBYTE)DialNumber + sizeof(TCHAR),
&RegKeySize);
RegCloseKey(hKey);
}
//Confirm there is a telephone number
if (*(DialNumber + 1) == 0)
{
VTPrint("No Telephone Number\r\n", 0);
return FALSE;
}
// Use TAPI to open a DATAMODEM communication channel
if (ghLineApp == NULL)
{
#ifdef DEBUGVERSION
VTPrint("Tapi - LineInitialise\r\n",0);
#endif
memset(&TapiLineDevCaps, 0, sizeof(TapiLineDevCaps));
lTapiReturn = lineInitialize(&ghLineApp, hInst, TapiCallBackFunction, NULL, &dwTapiNumDevs);
if (lTapiReturn)
{
wsprintf (TempString,TEXT("%d"), lTapiReturn);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 20, NULL, NULL);
VTPrint("TAPI failed to initialise\n\rError Code = ",0);
VTPrint(CharString, 0);
VTPrint("\n\r",0);
TAPIShutdown();
return FALSE;
}
//Loop through devices to find one that can do data at 9600 baud (GSM)
for (ChoosenDevice=0 ; ChoosenDevice < dwTapiNumDevs ; ChoosenDevice++)
{
TapiLineDevCaps.dwTotalSize = sizeof(TapiLineDevCaps);
//Ask for at least TAPI Version 1.4
lTapiReturn = lineNegotiateAPIVersion(ghLineApp, ChoosenDevice, 0x00010004,
0x00010004, &dwLocalAPIVersion, &LineExtensionID);
#ifdef DEBUGVERSION
if (lTapiReturn)
{
wsprintf (TempString,TEXT("%d"), lTapiReturn);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 20, NULL, NULL);
VTPrint("TAPI failed to negotiate API Version\n\rError Code = ",0);
VTPrint(CharString, 0);
VTPrint("\n\r",0);
}
#endif
if (!(lTapiReturn))
{
lTapiReturn=lineGetDevCaps(ghLineApp, ChoosenDevice, dwLocalAPIVersion, 0, &TapiLineDevCaps);
#ifdef DEBUGVERSION
if (lTapiReturn)
{
wsprintf (TempString,TEXT("%d"), lTapiReturn);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 20, NULL, NULL);
VTPrint("TAPI failed to get device capibility\n\rError Code = ",0);
VTPrint(CharString, 0);
VTPrint("\n\r",0);
}
else
#endif
#ifndef DEBUGVERSION
if (!(lTapiReturn))
#endif
{
if ((TapiLineDevCaps.dwBearerModes & LINEBEARERMODE_VOICE) &&
(TapiLineDevCaps.dwMaxRate >= 9600) &&
(TapiLineDevCaps.dwMediaModes & LINEMEDIAMODE_DATAMODEM))
break;
}
}
}
if (!((TapiLineDevCaps.dwBearerModes & LINEBEARERMODE_VOICE) ||
(TapiLineDevCaps.dwMaxRate >= 9600) ||
(TapiLineDevCaps.dwMediaModes & LINEMEDIAMODE_DATAMODEM)))
{
VTPrint("Unable to find a modem device\r\n", 0);
TAPIShutdown();
lineShutdown(ghLineApp);
ghLineApp = NULL;
return FALSE;
}
//Now we have found a device capable of a dial up modem
strcpy(PrintString, "TAPI Initilised (");
wsprintf (TempString,TEXT("%d"), ChoosenDevice);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 20, NULL, NULL);
strcat(PrintString, CharString);
strcat(PrintString, "/");
wsprintf (TempString,TEXT("%d"), dwTapiNumDevs);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 3, NULL, NULL);
strcat(PrintString, CharString);
strcat(PrintString, ")\n\r");
VTPrint(PrintString, 0);
}
//Open the TAPI line device
if (ghLine == NULL)
{
#ifdef DEBUGVERSION
VTPrint("TAPI - Obtaining line handle\r\n", 0);
#endif
lTapiReturn = lineOpen(ghLineApp, ChoosenDevice, &ghLine, dwLocalAPIVersion, 0 , 0,
LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM, 0);
if (lTapiReturn)
{
wsprintf (TempString,TEXT("%d"), lTapiReturn);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 20, NULL, NULL);
VTPrint("TAPI failed to Open Line\n\rError Code = ",0);
VTPrint(CharString, 0);
VTPrint("\n\r",0);
TAPIShutdown();
return FALSE;
}
//Request specific notification messages
lTapiReturn = lineSetStatusMessages(ghLine, LINEDEVSTATE_RINGING | LINEDEVSTATE_CONNECTED |
LINEDEVSTATE_DISCONNECTED | LINEDEVSTATE_OUTOFSERVICE |
LINEDEVSTATE_MAINTENANCE | LINEDEVSTATE_CLOSE |
LINEDEVSTATE_REINIT, LINEADDRESSSTATE_OTHER);
if (lTapiReturn)
{
wsprintf (TempString,TEXT("%d"), lTapiReturn);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 20, NULL, NULL);
VTPrint("TAPI failed to Set Status Messages\n\rError Code = ",0);
VTPrint(CharString, 0);
VTPrint("\n\r",0);
TAPIShutdown();
return FALSE;
}
//Configure line device for a data modem
memset(&LineCallParams, 0, sizeof(LineCallParams));
LineCallParams.dwTotalSize = sizeof(LineCallParams);
LineCallParams.dwBearerMode = LINEBEARERMODE_VOICE;
LineCallParams.dwMediaMode = LINEMEDIAMODE_DATAMODEM;
//Expect the line to start out idle (we don't want to break into a current call)
LineCallParams.dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;
//If multiple addresses on the line, use the first address
LineCallParams.dwAddressMode = LINEADDRESSMODE_ADDRESSID;
LineCallParams.dwAddressID = 0;
}
//Make the call
if (ghCall == NULL)
{
#ifdef DEBUGVERSION
VTPrint("TAPI Making the call\r\n", 0);
#endif
lTapiReturn = lineMakeCall(ghLine, &ghCall, DialNumber, 0, &LineCallParams);
if (lTapiReturn < 0)
{
wsprintf (TempString,TEXT("%d"), lTapiReturn);
WideCharToMultiByte(CP_ACP, 0, TempString, -1, CharString, 20, NULL, NULL);
VTPrint("TAPI failed to Make the Call\n\rError Code = ",0);
VTPrint(CharString, 0);
VTPrint("\n\r",0);
TAPIShutdown();
ghCall = NULL;
return FALSE;
}
}
VTPrint("Call Initiated\r\n",0);
return TRUE;
}
if (wcscmp(lpszPortName, TEXT("RAS")) != 0)
{
{
// Open the serial port.
hPort = CreateFile (lpszPortName, // Pointer to the name of the port
GENERIC_READ | GENERIC_WRITE,
// Access (read-write) mode
0, // Share mode
NULL, // Pointer to the security attribute
OPEN_EXISTING,// How to open the serial port
0, // Port attributes
NULL); // Handle to port with attribute
// to copy
// If it fails to open the port, return FALSE.
if ( hPort == INVALID_HANDLE_VALUE )
{
// Could not open the port.
COMMPORTSHUT=TRUE;
MessageBox (hMainWnd, TEXT("Unable to open the port"),
TEXT("Error"), MB_OK);
dwError = GetLastError ();
return FALSE;
}
}
RetValue=InitiliseCommHandle();
return RetValue;
}
return TRUE;
}
//Initilise the communication handle, this occurs once a communication channel has been
//opened, either direct to a serial port or via TAPI
BOOL InitiliseCommHandle(void)
{
DWORD dwError,
dwThreadID;
DCB PortDCB;
COMMTIMEOUTS CommTimeouts;
#ifdef DEBUGVERSION
char SendString[40];
sprintf(SendString, "Handle = %d\r\n", hPort);
VTPrint(SendString,0);
#endif
// Get the default port setting information.
PortDCB.DCBlength = sizeof (DCB);
GetCommState (hPort, &PortDCB);
// Change the DCB structure settings.
if (wcscmp(COMMPORTNAME, TEXT("TAPI")) == 0)
{
//TAPI (modem) Settings
PortDCB.BaudRate = 19200; // Current baud
PortDCB.ByteSize = 8; // Number of bits/byte, 4-8
PortDCB.Parity = NOPARITY; // Parity odd,even,mark,space
}
else
{
//Standard Serial Port Settings
PortDCB.BaudRate = 1200; // Current baud
PortDCB.ByteSize = 7; // Number of bits/byte, 4-8
PortDCB.Parity = EVENPARITY; // Parity odd,even,mark,space
}
PortDCB.fBinary = TRUE; // Binary mode; no EOF check
PortDCB.fParity = TRUE; // Enable parity checking
PortDCB.fOutxCtsFlow = FALSE; // CTS output flow control
PortDCB.fRtsControl = RTS_CONTROL_HANDSHAKE;
PortDCB.fOutxDsrFlow = FALSE; // No DSR output flow control
PortDCB.fDtrControl = DTR_CONTROL_ENABLE; //DTR output ON
PortDCB.fDsrSensitivity = FALSE; // DSR sensitivity
PortDCB.fTXContinueOnXoff = TRUE; // XOFF continues Tx
PortDCB.fOutX = FALSE; // No XON/XOFF out flow control
PortDCB.fInX = FALSE; // No XON/XOFF in flow control
PortDCB.fErrorChar = FALSE; // Disable error replacement
PortDCB.fNull = FALSE; // Disable null stripping
PortDCB.fAbortOnError = FALSE; // Do not abort reads/writes on
// error
PortDCB.StopBits = ONESTOPBIT; // 0,1,2 = 1, 1.5, 2
// Configure the port according to the specifications of the DCB
// structure.
if (!SetCommState (hPort, &PortDCB))
{
MessageBox (hMainWnd, TEXT("Unable to configure the port"),
TEXT("Error"), MB_OK);
dwError = GetLastError ();
return FALSE;
}
// Retrieve the time-out parameters for all read and write operations
// on the port.
GetCommTimeouts (hPort, &CommTimeouts);
// Change the COMMTIMEOUTS structure settings.
CommTimeouts.ReadIntervalTimeout = MAXDWORD;
CommTimeouts.ReadTotalTimeoutMultiplier = 0;
CommTimeouts.ReadTotalTimeoutConstant = 0;
CommTimeouts.WriteTotalTimeoutMultiplier = 20;
CommTimeouts.WriteTotalTimeoutConstant = 1000;
// Set the time-out parameters for all read and write operations
// on the port.
if (!SetCommTimeouts (hPort, &CommTimeouts))
{
// Could set the timeouts.
MessageBox (hMainWnd, TEXT("Unable to set the port time-out parameters"),
TEXT("Error"), MB_OK);
dwError = GetLastError ();
return FALSE;
}
if (wcscmp(COMMPORTNAME, TEXT("TAPI")) != 0)
{
// Direct the port to perform extended functions SETDTR and SETRTS
// SETDTR: Sends the DTR (data-terminal-ready) signal.
// SETRTS: Sends the RTS (request-to-send) signal.
// EscapeCommFunction (hPort, SETDTR);
// EscapeCommFunction (hPort, SETRTS);
//Use the Swap Comms routine to set the serial port to the last open state
//This is a little messy to set the port first of all and then change some
//of the settings here
SwapComms(CurrentController);
}
// Create a read thread for reading data from the communication port.
if (hReadThread = CreateThread (NULL, 0, PortReadThread, 0, 0,
&dwThreadID))
{
CloseHandle (hReadThread);
}
else
{
// Could not create the read thread.
MessageBox (hMainWnd, TEXT("Unable to create the read thread"),
TEXT("Error"), MB_OK);
dwError = GetLastError ();
return FALSE;
}
return TRUE;
}
//TAPI sends status messages to this function.
void CALLBACK TapiCallBackFunction(DWORD dwdevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
DWORD lTapiReturn, dwStructSize;
VARSTRING *pvs, *pvsOld;
#ifdef DEBUGVERSION
char SendString[40];
#endif
pvs = NULL;
dwStructSize = sizeof (VARSTRING);
switch(dwMsg)
{
case LINE_CALLSTATE:
switch(dwParam1)
{
case LINECALLSTATE_IDLE:
if (hPort != INVALID_HANDLE_VALUE)
{
VTPrint("Line Idle\n\r",0);
//Change MENU back to COMM mode
ControllerMenu(TRUE);
//Shutdown TAPI
hPort = INVALID_HANDLE_VALUE;
lineDeallocateCall(ghCall);
ghCall = NULL;
ghLine = NULL;
}
break;
case LINECALLSTATE_CONNECTED:
//If a handle is already present then don't re-open
//This needs doing as multiple connected events can occur on connection
if (hPort != INVALID_HANDLE_VALUE)
break;
//Spin round loop till the structure is big enough
do
{ pvsOld = pvs;
if (NULL == (pvs = realloc (pvs, dwStructSize)))
break;
pvs->dwTotalSize = dwStructSize;
if (lTapiReturn = lineGetID (NULL, 0, ghCall, LINECALLSELECT_CALL,
pvs, TEXT("comm/datamodem")))
break;
}
while ((dwStructSize = pvs->dwNeededSize) > pvs->dwTotalSize) ;
if (pvs == NULL)
{
if (pvsOld)
free (pvsOld);
VTPrint("Failed to allocate port handle memory\n\r", 0);
TAPIShutdown();
break;
}
if (lTapiReturn)
{
free(pvs);
VTPrint("Failed to obtain port handle\n\r", 0);
TAPIShutdown();
break;
}
hPort = * (HANDLE*) ((char*) pvs + pvs->dwStringOffset);
free(pvs);
//Change Menu to Modem Mode
ControllerMenu(FALSE);
VTPrint("Connected\n\r", 0);
InitiliseCommHandle();
break;
case LINECALLSTATE_DIALING:
//VTPrint("Dialling\n\r", 0);
break;
case LINECALLSTATE_PROCEEDING:
//VTPrint("Waiting for an answer\n\r", 0);
break;
case LINECALLSTATE_DISCONNECTED:
ControllerMenu(TRUE);
//Change Menu to COMM mode
switch (dwParam2)
{
case LINEDISCONNECTMODE_UNREACHABLE:
VTPrint("Unreachable\r\n",0);
hPort = INVALID_HANDLE_VALUE;
break;
case LINEDISCONNECTMODE_BUSY:
VTPrint("Line Busy\n\r", 0);
break;
}
TAPIShutdown();
}
break;
default:
break;
}
}
[/code]
BOOL TAPIShutdown()
//Do the TAPI Shutdown Process
{
static bShuttingDown = FALSE;
//If we are not initilised then Shutdown unnecessary
if (ghLineApp == NULL)
return TRUE;
//Prevent Shutdown re-entrancy problems
if (bShuttingDown)
return TRUE;
bShuttingDown=TRUE;
if (ghCall != NULL)
lineDrop(ghCall, NULL, 0);
bShuttingDown = FALSE;
return TRUE;
}
Thanks for code posted. It is very helpful.
But I don't quite understand the logic how the "hPort" is created when receiveing a "CONNECTED" message callback.
Can you explain a little bit more?
- David
The handle to the port (hPort) is contained at the end of a variable length string (VARSTRING) that is returned from the call to LineGetID.
The fiddly thing about it is you give LineGetID a pointer to a VARSTRING called pvs and you have to fill in one of the member variables of pvs with the size allocated ( pvs->dwTotalSize ) to the variable length string.
Since I don't know how much to allocate to the VARSTRING, initially I just allocate enough memory from a sizeof(VARSTRING) result. Maybe you could just allocate a set size and not bother with the looping back, I think it is better to ask the system how much memory it wants as LineGetID may need more memory in later versions of the operating system.
Then you pass the VARSTRING with it's size stored in dwTotalSize to the function LineGetID.
The LineGetID function then fills in dwNeededSize with the size of the VARSTRING it needed to complete. That is why I loop back and re-allocate the VARSTRING to a new (bigger) size if NeededSize is greater than TotalSize.
Once the LineGetID function succeeds with a big enough VARSTRING then the Port Handle is at the end of the VARSTRING, as it is a variable length string you have to do maths to say the Port Handle is at the Address of the string PLUS the offset to the actural data wanted i.e. pvs + pvs->dwStringOffset The maths must be done with char* byte sizes and the final result is a handle, hence the casting HANDLE* on the final result.
Once the Port Handle is stored in hPort, the memory allocated to pvs is freed.
One other thing I noticed is that you can get multiple CONNECTED events, that is why I just exit the CONNECTED event if there is already a valid handle
I hope that makes sense.
Cheers
Paul
Do you have sample for the answering part?
thanks,
- David
No, sorry I have only ever written programs to dial out.
Hi,
I tried the sample code posted in this thread. It works only if I used
LineCallParams.dwBearerMode = LINEBEARERMODE_VOICE;
LineCallParams.dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
combination. I got the RING message. However, the original:
LineCallParams.dwBearerMode = LINEBEARERMODE_VOICE;
LineCallParams.dwMediaMode = LINEMEDIAMODE_DATAMODEM;
didn't seem to work.
Do I miss something? (I think I have successfully killed the cprog.exe already)
Thanks,
- David
That means you are making a voice call.
So is the problem that you can not make a data(modem) type call ?
Check that you have data enabled on your SIM and also that under Settings -> Connections -> CSD Line Type it is set correctly. In the UK it is 9600 bps(v.32) and Non-tranparent, but I was told it is v110 is the states.
Let me know how you get on.
Is there any chance that somebody could compile this TAPI code as an object I could consume in C#? I have tried, but I am not even able to compile the example in C++... it's just not my world!
I would be eternally gratefull - and I bet a whole bunch of other guys who just want to quickly establish a data connection over GSM from C# (or VB?) would be also! It should have been in the phone.dll if you ask me!
compiled sample
Hey could somebody compile the tapi code for me in eVc++ or MFC ?? because i am really struggling. Plus what is toolbox.h i dont seem to have it ?? can somebody please help ??
thanks guys
This is an old post of mine, don't worry about Toolbox.h that was for the program I was writing and it has nothing to do with Tapi.
This sample code was not meant to compile, after you you won't have function VTPrint but I hope you can guess that just shows text. It was just posted to demonstate the order of functions needed to get a modem connection.
I built all this Tapi code into a DLL and I have it all presented with the eVC 3.0 build files in this post, this code will compile as presented and is just a zip of all the project files of a single sample application.
http://forum.xda-developers.com/viewtopic.php?t=18978
Have a look there.
Cheers
Paul
during a data call when using TAPI can u make some kind of AT command request ??? and retrieve the data from that answer ??? And do i need some TAPI app on the remote unit to send the data back ?
i am trying to access a remote unit ( GPS RX and GSM TX )
I don't really understand your question that well.
But AT commands have nothing to do with TAPI, so any question saying can TAPI send an AT command for etc -- The answer must be no.
All TAPI does is gives a Handle that can be used with ReadFile and WriteFile for receiving and sending data over the modem.
So TAPI will dial a number and establish a DataModem connection, then on Connect it can provide the handle for you to direct read/write requests to. Once the link is established you just treat the handle as if it was a file handle returned from a CreateFile command.
As said earlier all this has nothing to do with AT commands.
During a data call should ic onnect via COM 1 or COM 9 if i'm using TAPI ?? Would you have any sample code i could have a look at ? I'm a bit lost at the moment .... with LineGetID too ...
you can't use COM2 or COM9 via TAPI cause COM2 and COM9 masked by RIL
about lineGetID look http://forum.xda-developers.com/viewtopic.php?t=9761
ok thanks - so if i cant use COM1 or COM9 in this case then i'm guessing that i shall pass a string (lpszPortName) just like the example on this post.
Code:
// Open the serial port.
hPort = CreateFile (lpszPortName, // Pointer to the name of the port
GENERIC_READ | GENERIC_WRITE,
// Access (read-write) mode
0, // Share mode
NULL, // Pointer to the security attribute
OPEN_EXISTING,// How to open the serial port
0, // Port attributes
NULL); // Handle to port with attribute
// to copy
But what should the string lpszPortName be initialized to ? Or am i totally wrong ?[/quote]
L"COM9:" for COM9
but not with TAPI
with TAPI you have not open any COM-ports
you must use lineGetID
Code:
//PART 7 - TIMEOUT AND DCB SETTINGS
PortDCB.BaudRate = 115200;
PortDCB.fBinary = TRUE;
PortDCB.fParity = FALSE;
PortDCB.fOutxCtsFlow = FALSE;
PortDCB.fOutxDsrFlow = FALSE;
PortDCB.fDtrControl = DTR_CONTROL_ENABLE;
PortDCB.fDsrSensitivity = FALSE;
PortDCB.fTXContinueOnXoff = FALSE;
PortDCB.fOutX = FALSE;
PortDCB.fInX = FALSE;
PortDCB.fErrorChar = FALSE;
PortDCB.fNull = FALSE;
PortDCB.fRtsControl = RTS_CONTROL_DISABLE;
PortDCB.fAbortOnError = FALSE;
PortDCB.ByteSize = 8;
PortDCB.Parity = NOPARITY;
PortDCB.StopBits = ONESTOPBIT;
if (!SetCommState (hPort, &PortDCB))
{
MessageBox (_T("unable to configure com port "));
return FALSE;
}
GetCommTimeouts (hPort, &CommTimeouts);
CommTimeouts.ReadIntervalTimeout = MAXDWORD;
CommTimeouts.ReadTotalTimeoutMultiplier = 0;
CommTimeouts.ReadTotalTimeoutConstant = 0;
CommTimeouts.WriteTotalTimeoutMultiplier = 20;
CommTimeouts.WriteTotalTimeoutConstant = 1000;
if (!SetCommTimeouts (hPort, &CommTimeouts))
{
MessageBox (_T("unable to set comport parameters"));
return FALSE;
}
Are the COM and Timeout settings necessary when using TAPI ? because i am getting an error message at the !setCommState function.
I use lineGetID to retrieve the Handle to the Comm then i try to use readFile but the operation is unsuccesful.
Anyhelp from out there ? thanks.
Are you sure that you call lineGetID only after connection?
Maybe you are right. I just do lineMakeCall then do lineGetId without waiting for a LINECALLSTATE_CONNECTED message.
I think its because i have troubles implementing the lineCallBackFunc() i dont understand the parameters that need to be passed to it.
Code:
void CALLBACK lineCallbackFunc(
DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
Anyhelp for this ?? Cheers.
I'm try write a application to addjust LCD backlight.But it failed at the
first step Create file.GetLastError return 161.
Somesone can tell me what's wrong with it?
Sorry for my poor english.
thx
Below is my code
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
TCHAR *pszDevice=_T("\\\\.\\LCD");
HANDLE hLCD=NULL;
hLCD=CreateFileW(pszDevice,
GENERIC_READ,
FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
0,
NULL);
DWORD dwError=GetLastError();
if(INVALID_HANDLE_VALUE!=hLCD)
{
MessageBox(NULL,_T("Got Lcd"),_T("MyMessage"),MB_OK);
}
CloseHandle(hLCD);
return 0;
}
Hi ft1860!
I don't think you can do it that way, although I never tried it. You should check out power management API:
http://msdn.microsoft.com/library/d...pplicationsToTurnSmartphoneBacklightOffOn.asp
This example is for smartphones but should work on a PPC just as well.
P.S.:
Error 161 is "The specified path is invalid" so you should check the device name (no idea what that might be).
levenum said:
Hi ft1860!
I don't think you can do it that way, although I never tried it. You should check out power management API:
http://msdn.microsoft.com/library/d...pplicationsToTurnSmartphoneBacklightOffOn.asp
This example is for smartphones but should work on a PPC just as well.
P.S.:
Error 161 is "The specified path is invalid" so you should check the device name (no idea what that might be).
Click to expand...
Click to collapse
hi levenum
Thanks your comment,but according to this document
http://windowssdk.msdn.microsoft.co...r/base/ioctl_video_set_display_brightness.asp
It say we can set the current AC and DC backlight levels by invoke DeviceIoControl with IOCTL_VIDEO_SET_DISPLAY_BRIGHTNESS parameter.
So first I must create a handle of LCD,but I cann't open it successfully,
why?
oh,I make a mistake
IOCTL_VIDEO_SET_DISPLAY_BRIGHTNESS is Win32 function.
On smart device,I should use SetPowerRequirement to set the backlight.
But when I invoke SetPowerRequirement on my Universal with D0 or D4,it say success ,but nothing happened.
maybe you have to access backlight by BKL1: device.
CreateFile(L"BKL1:", ....
or write the brightness value into registry.. that works for sure.
buzz
buzz_lightyear said:
maybe you have to access backlight by BKL1: device.
CreateFile(L"BKL1:", ....
or write the brightness value into registry.. that works for sure.
buzz
Click to expand...
Click to collapse
Thanks
I'll try it.
It seem that SetPowerRequirement D0-D4 just Open or close backlight,
but what I want is adjust the backlight's brightness.
ft1860 said:
It seem that SetPowerRequirement D0-D4 just Open or close backlight,
but what I want is adjust the backlight's brightness.
Click to expand...
Click to collapse
then just do it through registry.
buzz
buzz_lightyear said:
ft1860 said:
It seem that SetPowerRequirement D0-D4 just Open or close backlight,
but what I want is adjust the backlight's brightness.
Click to expand...
Click to collapse
then just do it through registry.
buzz
Click to expand...
Click to collapse
oh,thanks
It almost work.
It works fine on my Universal,but it doesn't work on my x50v with WM5.
I should not debug it on my x50v,there is so many bugs in Dell's WM5 update.
Another question is how to get the max brightness value.On my Universal it is 10,but on my x50v it is 8;
below is my new code
#include <WinReg.h>
#include <pm.h>
TCHAR tszAppName[] = TEXT("PMSAMPLE");
TCHAR tszTitle[] = TEXT("Power Manager Sample");
HINSTANCE g_hInst = NULL; // Local copy of hInstance
HANDLE g_hEventShutDown = NULL;
HANDLE g_hPowerNotificationThread = NULL;
HWND g_hSystemState = NULL;
//***************************************************************************
// Function Name: SetBacklightRequirement
//
// Purpose: Sets or releases the device power requirement to keep the
// backlight at D0
//
// Arguments:
// IN BOOL fBacklightOn - TRUE to leave the backlight on
//
void SetBacklightRequirement(BOOL fBacklightOn)
{
// the name of the backlight device
TCHAR tszBacklightName[] = TEXT("BKL1:");
static HANDLE s_hBacklightReq = NULL;
if (fBacklightOn)
{
if (NULL == s_hBacklightReq)
{
// Set the requirement that the backlight device must remain
// in device state D0 (full power)
s_hBacklightReq = SetPowerRequirement(tszBacklightName, D4,
POWER_NAME, NULL, 0);
if (!s_hBacklightReq)
{
RETAILMSG(1, (L"SetPowerRequirement failed: %X\n",
GetLastError()));
}
}
}
else
{
if (s_hBacklightReq)
{
if (ERROR_SUCCESS != ReleasePowerRequirement(s_hBacklightReq))
{
RETAILMSG(1, (L"ReleasePowerRequirement failed: %X\n",
GetLastError()));
}
s_hBacklightReq = NULL;
}
}
}
#define REG_BACKLIGHT L"ControlPanel\\Backlight"
//#define REG_VAL_BATT_TO L"BatteryTimeout"
//#define REG_VAL_AC_TO L"ACTimeout"
#define REG_VAL_BN L"BrightNess"
#define REG_VAL_ACBN L"ACBrightness"
unsigned int OldBattBN=0;
unsigned int OldACBN=0;
void RegOptionBL( DWORD dw1,DWORD dw2)
{
HKEY hKey = 0;
DWORD dwSize;
HANDLE hBL;
static bool bOpened=false;
if ( ERROR_SUCCESS == RegOpenKeyEx( HKEY_CURRENT_USER,REG_BACKLIGHT, 0, 0, &hKey ) )
{
if( !bOpened )
{
bOpened=true;
dwSize = 4;
RegQueryValueEx( hKey, REG_VAL_BN,NULL,NULL,(unsigned char*)&OldBattBN,&dwSize );
dwSize = 4;
RegQueryValueEx( hKey, REG_VAL_ACBN,NULL,NULL,(unsigned char*) &OldACBN,&dwSize );
// dwSize = 4;
// dwValue = 0xefff ;
// RegSetValueEx( hKey,REG_VAL_BATT_TO,NULL,REG_DWORD,(unsigned char *)&dwValue,dwSize );
// dwSize = 4;
// dwValue = 0xefff ;
// RegSetValueEx( hKey,REG_VAL_AC_TO,NULL,REG_DWORD,(unsigned char *)&dwValue,dwSize );
dwSize = 4;
RegSetValueEx( hKey,REG_VAL_BN,NULL,REG_DWORD,(unsigned char *)&dw1,dwSize );
dwSize = 4;
RegSetValueEx( hKey,REG_VAL_ACBN,NULL,REG_DWORD,(unsigned char *)&dw2,dwSize );
}
else
{
if (OldBattBN)
{
dwSize = 4;
RegSetValueEx( hKey,REG_VAL_BN,NULL,REG_DWORD,(unsigned char *)&OldBattBN,dwSize );
}
if (OldACBN)
{
OldACBN = 4;
RegSetValueEx( hKey,REG_VAL_ACBN,NULL,REG_DWORD,(unsigned char *)&OldACBN,dwSize );
}
}
RegCloseKey( hKey );
hBL = CreateEvent( NULL, FALSE, FALSE,L"BackLightChangeEvent" );
if( hBL )
{
SetEvent(hBL);
CloseHandle( hBL );
}
}
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
int nResult = 0;
// Create mutex to track whether or not an application is already running
HANDLE hMutex = CreateMutex(0, 0, _T("_PMSAMPLE_EXE_MUTEX_"));
// check the result code
if (NULL != hMutex)
{
if (ERROR_ALREADY_EXISTS != GetLastError())
{
SetBacklightRequirement(TRUE);
RegOptionBL(8,8);
Sleep(5000);
RegOptionBL(-1,-1);
SetBacklightRequirement(FALSE);
}
else
{
}
CloseHandle(hMutex);
}
return ( nResult >= 0 );
}
I am desperately trying to work around some unknown problem caused by our NDIS IM driver on WM6 and HTC TyTn ( TyTn at least, but most likely other devices as well ). I started some other threads regarding different facets of this problem, and no help is coming. Now I am just trying to work around it.
in short the problem is when power events happen the WLAN will never connect.
The work around is programmatically open the control panel with STControlPanel class and press the down button 1 time then the f1 key ( right dash ). This "presses connect" on the last configured access point. This works around my problem, however, I need to detect when the access point has connected. There is a "connecting" and "connected" message for the access point in this control panel dialog ( control panel # 17 ).
My Question: Is there some way to get this text "connecting" or "connected" from the control panel #17?
I have tried wzc, ndisuio, winsock, and other "non control panel" ways to detect connected state, but the IM problem fools all these methods. The only thing I have ever seen that successfully shows when the problem has occurred is the "connecting"/"connected" status in the control panel 17.
Please Help Someone.
p.s. If I can get this worked around, we sell a commercial grade ndis intermediate driver toolkit letting you easily write plugins without any of the hard driver details. We have redirectors, transparent proxy, tunnelers, virtual adapters, lots of good stuff. Also the same plugin will work on all windows desktop platforms vista - 98, WM5/6 and CE and also linux and solaris...
Hello skk
What about notification API?
Here is the solution (simple concept) for Your problem. Interesting parts are in bold.
SNapiTest.cpp
Code:
#include "stdafx.h"
#include "SNapiTest.h"
#include <windows.h>
#include <commctrl.h>
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE g_hInst; // current instance
HWND g_hWndMenuBar; // menu bar handle
[b]
const DWORD WM_WIFISTATUS = WM_USER + 1;
bool g_connecting;
bool g_connected;
HREGNOTIFY g_hNotify;
HREGNOTIFY g_hNotify2;
[/b]
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE, LPTSTR);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
// Perform application initialization:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable;
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SNAPITEST));
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
// COMMENTS:
//
ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SNAPITEST));
wc.hCursor = 0;
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = szWindowClass;
return RegisterClass(&wc);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
TCHAR szTitle[MAX_LOADSTRING]; // title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // main window class name
g_hInst = hInstance; // Store instance handle in our global variable
// SHInitExtraControls should be called once during your application's initialization to initialize any
// of the device specific controls such as CAPEDIT and SIPPREF.
SHInitExtraControls();
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_SNAPITEST, szWindowClass, MAX_LOADSTRING);
//If it is already running, then focus on the window, and exit
hWnd = FindWindow(szWindowClass, szTitle);
if (hWnd)
{
// set focus to foremost child window
// The "| 0x00000001" is used to bring any owned windows to the foreground and
// activate them.
SetForegroundWindow((HWND)((ULONG) hWnd | 0x00000001));
return 0;
}
if (!MyRegisterClass(hInstance, szWindowClass))
{
return FALSE;
}
hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
// When the main window is created using CW_USEDEFAULT the height of the menubar (if one
// is created is not taken into account). So we resize the window after creating it
// if a menubar is present
if (g_hWndMenuBar)
{
RECT rc;
RECT rcMenuBar;
GetWindowRect(hWnd, &rc);
GetWindowRect(g_hWndMenuBar, &rcMenuBar);
rc.bottom -= (rcMenuBar.bottom - rcMenuBar.top);
MoveWindow(hWnd, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, FALSE);
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
static SHACTIVATEINFO s_sai;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_HELP_ABOUT:
DialogBox(g_hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, About);
break;
case IDM_OK:
SendMessage (hWnd, WM_CLOSE, 0, 0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_CREATE:
SHMENUBARINFO mbi;
memset(&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hWnd;
mbi.nToolBarId = IDR_MENU;
mbi.hInstRes = g_hInst;
if (!SHCreateMenuBar(&mbi))
{
g_hWndMenuBar = NULL;
}
else
{
g_hWndMenuBar = mbi.hwndMB;
}
// Initialize the shell activate info structure
memset(&s_sai, 0, sizeof (s_sai));
s_sai.cbSize = sizeof (s_sai);
[b]
g_connecting = false;
g_connected = false;
{HRESULT hr = RegistryNotifyWindow(SN_WIFISTATECONNECTING_ROOT,
SN_WIFISTATECONNECTING_PATH, SN_WIFISTATECONNECTING_VALUE,
hWnd, WM_WIFISTATUS, 0, NULL, &g_hNotify);}
{HRESULT hr2 = RegistryNotifyWindow(SN_WIFISTATECONNECTING_ROOT,
SN_WIFISTATECONNECTED_PATH, SN_WIFISTATECONNECTED_VALUE,
hWnd, WM_WIFISTATUS, 0, NULL, &g_hNotify2);}
[/b]
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
[b]
case WM_WIFISTATUS:
{
DWORD newValue = (DWORD) wParam;
WCHAR caption[] = L"Wifi Status";
if ((newValue & SN_WIFISTATECONNECTED_BITMASK) == SN_WIFISTATECONNECTED_BITMASK)
{
if (!g_connected)
{
g_connected = true;
g_connecting = false;
MessageBox(hWnd, L"Connected!!", caption, MB_OK);
}
break;
}
if ((newValue & SN_WIFISTATECONNECTING_BITMASK) == SN_WIFISTATECONNECTING_BITMASK)
{
if (!g_connecting)
{
g_connecting = true;
g_connected =false;
MessageBox(hWnd, L"Connecting...", caption, MB_OK);
}
}
break;
}
[/b]
case WM_DESTROY:
CommandBar_Destroy(g_hWndMenuBar);
[B]
RegistryCloseNotification(g_hNotify);
RegistryCloseNotification(g_hNotify2);
[/B]
PostQuitMessage(0);
break;
case WM_ACTIVATE:
// Notify shell of our activate message
SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);
break;
case WM_SETTINGCHANGE:
SHHandleWMSettingChange(hWnd, wParam, lParam, &s_sai);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
// Create a Done button and size it.
SHINITDLGINFO shidi;
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
shidi.hDlg = hDlg;
SHInitDialog(&shidi);
}
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
case WM_CLOSE:
EndDialog(hDlg, message);
return TRUE;
}
return (INT_PTR)FALSE;
}
Needed includes:
// TODO: reference additional headers your program requires here
#include <snapi.h>
#include <regext.h>
snapi.h
Thank you so much for your post. I just found snapi.h, I assume this is snapitest.h?
IT WORKS!
It works! You are my hero RStein. You are a god among men.
Great. ) Thanks for sharing the results.
Possible to make the Xbox360 controller connect to your Android phone ?
there's alot of software for both Mac and Windows letting you connect it to your computer, so without too much research i wondered if there is a possibility to port one of those apps over to android and use the controller for emulators and games.
there must be some open-source apps for win/osx out there i think so if any dev would have a look at it, for me to read those codes would be the same as to try to teach me greek..
just an idea, but if there's anyone that could make something out of this i think there will be alot of people wanting to use it
kinda like the SNES controller the streak's got, only wireless instead.. haha
Xbox 360 controllers aren't bluetooth
Sent from my HTC Droid Incredible. This Droid Does More.
its not just software to allow an Xbox controller to be connected to a PC, unless you're talking about the wired version, you need the reciever.
Like generamerica says, Xbox controllers aren't BT. PS3 ones are, but the bluetooth it uses is slightly different than normal and if i recall there have been issues in the past trying to get it to connect, no idea where that project has gone since then.
The only controller that can work with android from the big 3 atm is the wiimote, and even then you have to be running the stack from AOSP instead of the sense stack.
You got a lot of Bluetooth receivers !
It's possible !
GéVé said:
You got a lot of Bluetooth receivers !
It's possible !
Click to expand...
Click to collapse
!!!!!!! lo voglioooo contropper per android...cosi gioco a game psx gba
why not ps3 controller?
Yeah 360 would be impracticle if not impossible as you would have to either use a wired controller or have a wireless adapter for it to work. All of which means more stuff to lug around with an already large controller.
It would be cool to get the ps3 controller working on android but the controllerr is fairly big to carry with your phone.
Btw, the ps3 controller works great on windows. I use it with my netbook to play games in between classes. It connects to my normal Bluetooth receiver without any issues using a custom driver and software to pair the devices.
The only problem I can see coming from getting a ps3 controller working on android would be that once you pair the controller to the receiver you can no longer use it with any other devices unless you unpaid and uninstall driver to original. This is how it is with the controller on windows and most people use 2 Bluetooth receivers to deal with the problem.
Regardless of how practicle it would be to carry a ps3 or Xbox controller around with your phone it would be sweet to have working especially to get some of the other bluetooth controllers that are out there working. I have seen Asus controllers that are Bluetooth and have all the same buttons as ps3 controllers but they fold up and are a lot smaller and easy to carry around.
And a Bluetooth controller would be great for us who do not have enough hardware keys to play games with - no hardware keyboard.
Sent from my HTC Hero CDMA using XDA App
Its hardly impractical, if you was chilling at work on a break and you and a friend wanted a game of street fighter on Tiger mame or something then i think linking two xbox pads to your phone and having a few round would be superb, I would avoid even bothering with wireless with the xbox pad because it's not bluetooth and the ps3 is problematic for most devices using sense (which means half of them) so it's just the wired xbox pad, a port or a fresh app which can link the onscreen buttons to games or emulators to the digital buttons would be fantastic.
Dev's would pick up on this and would probably develop around them if it became successful enough, I think it's a good idea, the reason i say that is because the onscreen keyboards for the devices out now have some achillies heel's on them, first one is the fact that it's touchscreen and it's just not the same as a pad, the second is there is only ever going to be one player per device the likes of street fighter 2 turbo and king of fighters were ment to be played with two players, not to mention the amount of other game types which would be played better on a pad other than touchscreen.
In an ideal world it would be a cable to your tv and a cable to your pad, run a game on your phone and play it on your tv with your xbox pad wired.
I know it can be done, might look into doing it myself, usb can be split too, analogue could come later, hmmmmm.
There is an actual implementation of Xbox360 controler for the Elocity A7 tablet,
[ROM] Emulator / XBox360 Controller Mod 1-21-2011
But idk if it could be adapted to other android devices since it seems to need an usb port support.
Sine. said:
There is an actual implementation of Xbox360 controler for the Elocity A7 tablet,
[ROM] Emulator / XBox360 Controller Mod 1-21-2011
But idk if it could be adapted to other android devices since it seems to need an usb port support.
Click to expand...
Click to collapse
Yeah, in order to use the wired controller, you would need USB Host mode drivers, which at the moment not a lot of Android phones have (yet). At least, not to my knowledge anyway. Then you would need some sort of adapter to go from Mini/Micro USB (depending on which phone you have) to regular USB.
Sine. said:
There is an actual implementation of Xbox360 controler for the Elocity A7 tablet,
[ROM] Emulator / XBox360 Controller Mod 1-21-2011
But idk if it could be adapted to other android devices since it seems to need an usb port support.
Click to expand...
Click to collapse
abrigham said:
Yeah, in order to use the wired controller, you would need USB Host mode drivers, which at the moment not a lot of Android phones have (yet). At least, not to my knowledge anyway. Then you would need some sort of adapter to go from Mini/Micro USB (depending on which phone you have) to regular USB.
Click to expand...
Click to collapse
well I've seen some articles where people have some sort of adapter that turnes mini/micro usb into a dual input for (mouse / keyboard) so I guess two controllers shouldn't be that much further off... it's a matter of who has the knowledge and can implement it... unfortunately, it isn't me =( heh
But for the devices with usb ports how would one go about getting the required support in their rom?
When you plug the 360 controller in nothing happens.. I would like to know what files I may need to push to my android device that HAS full usb ports.
sent from gv1.5 on g2
Get a micro usb to female usb cable, plug in the wireless adapter. Your device has to be running Android 4.0 to work, but it does work flawlessly. You then have to go to market and download a free app that allows you to configure the buttons on the controller....then you're all set. There are also a few games that are already set in options to run third party controllers as well. I'm planning on doing this as soon as we get some type on ICS love from any direction for the SGS2 SGH-T989, so. I hooked this setting up on a customers Galaxy Tab, so. Awesome...
This doesn't have to do with the xbox controller, but On my old itouch, I could use a wii controller to play games on a nes emulator. If it's possible with ios, then it has to be with android.
Sent from my GT-I9100 using Tapatalk
Well, maybe it's possible to make Android using pads already connected to PC? I have Xbox 360 pad with PC receiver and I am wondering if it's actually possible to connect that controller to Android device (mine is HTC Desire Z).
I'm afraid that there can be some input lag but maybe that can be done?
Plausible and an achievable concept... is it worth it? Lol?
This got me thinking.
I dont know why anyone would want to do this now a days but it is very possible.
Back in 2007 remotejoy came out on the psp-1001 Phat.
Basically the concept is simple you would put a couple of .prx files on the memory stick pro duo in the root directory in a folder named plugins. And set up a windows drivers and a small application I think. I'm not 100% about that but seems logical.
Then interface the psp with the pc via USB but before interfacing, you would have to go into the vsh menu which you would hit a button combo and a text overlay would appear over the xmb in the top left hand corner. You could then select what plugin you wanted to load. Then you would load up the prx's and plug in your controller and USB type B from the psp to the pc. It would detect it though libusb and then you simply execute remotejoy.exe "I think" don't quote me on any of this. It was a long time ago....
Then you would literally be able to play your game on your monitor or on a flat screen like me. Via dvi output from an ant 1600xt graphics card in crossfire. And my 10000 pound Sony professional 42 inch $4000 plasma..... and then thru remotejoy gui you could go to controllers and basically use any analog controller that there were drivers for...... Man things have really changed in 13 years!!!!.... Jeez.
These plugins could add all kinds of extra features. Fps counter, background music player, an ir yeah infrared.... lol irmanager a badass file explorer with every feature you could ever imagine, well there was an irshell .prx, and a psx one called pops before Sony ever even released it.....
Sound familiar? Should, first there was xposed framework, now we have magisk plugins with support for xposed modules....
With that being said this is very possible and a feasible feat. (Not that anyone should take it on).....
It was coded under unix. So same kernel we use on androids..... It could be forked/updated/shimed/referenced/converted/reverse engineer to remotejoy magisk plugin but it's old code.
I think the last time it was updated was in 09 when I graduated high school.... so someone way more talented then me could possibly modify and retrofit or port to android using adb and USB debugging on a rooted android.
But this is not my area of expertise ports aren't my bag, man.
It would be a huge undertaking.
For controller support it would use socks. TCP specifically.
I didn't dig into it too much because well everyone now a days has smart share or screen mirroring and bluetooth xbox one s controllers and 4k TV's......
Just wanted to say it it a very plausible and achievable project.
Sorry to dig up an old thread. I have the original source code somewhere.
Here's an excerpt for controller support and mapping via universal serial ports it's in raw.
/*
* PSPLINK
* -----------------------------------------------------------------------
* Licensed under the BSD license, see LICENSE in PSPLINK root for details.
*
* remotejoy.c - PSPLINK PC remote joystick handler (SDL Version)
*
* Copyright (c) 2006 James F <[email protected]>
*
* $HeadURL: svn://svn.pspdev.org/psp/branches/psplinkusb/tools/remotejoy/pcsdl/remotejoy.c $
* $Id: remotejoy.c 2187 2007-02-20 19:28:00Z tyranid $
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <limits.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <SDL.h>
#include <SDL_thread.h>
#include "../remotejoy.h"
#define DEFAULT_PORT 10004
#define DEFAULT_IP "localhost"
#define MAX_AXES_NUM 32767
#define DIGITAL_TOL 10000
#define PSP_SCREEN_W 480
#define PSP_SCREEN_H 272
#define EVENT_ENABLE_SCREEN 1
#define EVENT_RENDER_FRAME_1 2
#define EVENT_RENDER_FRAME_2 3
#define EVENT_DISABLE_SCREEN 4
#ifndef SOL_TCP
#define SOL_TCP IPPROTO_TCP
#endif
#if defined BUILD_BIGENDIAN || defined _BIG_ENDIAN
uint16_t swap16(uint16_t i)
{
uint8_t *p = (uint8_t *) &i;
uint16_t ret;
ret = (p[1] << 8) | p[0];
return ret;
}
uint32_t swap32(uint32_t i)
{
uint8_t *p = (uint8_t *) &i;
uint32_t ret;
ret = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
return ret;
}
uint64_t swap64(uint64_t i)
{
uint8_t *p = (uint8_t *) &i;
uint64_t ret;
ret = (uint64_t) p[0] | ((uint64_t) p[1] << 8) | ((uint64_t) p[2] << 16) | ((uint64_t) p[3] << 24)
| ((uint64_t) p[4] << 32) | ((uint64_t) p[5] << 40) | ((uint64_t) p[6] << 48) | ((uint64_t) p[7] << 56);
return ret;
}
#define LE16(x) swap16(x)
#define LE32(x) swap32(x)
#define LE64(x) swap64(x)
#else
#define LE16(x) (x)
#define LE32(x) (x)
#define LE64(x) (x)
#endif
enum PspCtrlButtons
{
/** Select button. */
PSP_CTRL_SELECT = 0x000001,
/** Start button. */
PSP_CTRL_START = 0x000008,
/** Up D-Pad button. */
PSP_CTRL_UP = 0x000010,
/** Right D-Pad button. */
PSP_CTRL_RIGHT = 0x000020,
/** Down D-Pad button. */
PSP_CTRL_DOWN = 0x000040,
/** Left D-Pad button. */
PSP_CTRL_LEFT = 0x000080,
/** Left trigger. */
PSP_CTRL_LTRIGGER = 0x000100,
/** Right trigger. */
PSP_CTRL_RTRIGGER = 0x000200,
/** Triangle button. */
PSP_CTRL_TRIANGLE = 0x001000,
/** Circle button. */
PSP_CTRL_CIRCLE = 0x002000,
/** Cross button. */
PSP_CTRL_CROSS = 0x004000,
/** Square button. */
PSP_CTRL_SQUARE = 0x008000,
/** Home button. */
PSP_CTRL_HOME = 0x010000,
/** Music Note button. */
PSP_CTRL_NOTE = 0x800000,
/** Screen button. */
PSP_CTRL_SCREEN = 0x400000,
/** Volume up button. */
PSP_CTRL_VOLUP = 0x100000,
/** Volume down button. */
PSP_CTRL_VOLDOWN = 0x200000,
};
enum PspButtons
{
PSP_BUTTON_CROSS = 0,
PSP_BUTTON_CIRCLE = 1,
PSP_BUTTON_TRIANGLE = 2,
PSP_BUTTON_SQUARE = 3,
PSP_BUTTON_LTRIGGER = 4,
PSP_BUTTON_RTRIGGER = 5,
PSP_BUTTON_START = 6,
PSP_BUTTON_SELECT = 7,
PSP_BUTTON_UP = 8,
PSP_BUTTON_DOWN = 9,
PSP_BUTTON_LEFT = 10,
PSP_BUTTON_RIGHT = 11,
PSP_BUTTON_HOME = 12,
PSP_BUTTON_NOTE = 13,
PSP_BUTTON_SCREEN = 14,
PSP_BUTTON_VOLUP = 15,
PSP_BUTTON_VOLDOWN = 16,
PSP_BUTTON_MAX = 17
};
unsigned int g_bitmap[PSP_BUTTON_MAX] = {
PSP_CTRL_CROSS, PSP_CTRL_CIRCLE, PSP_CTRL_TRIANGLE, PSP_CTRL_SQUARE,
PSP_CTRL_LTRIGGER, PSP_CTRL_RTRIGGER, PSP_CTRL_START, PSP_CTRL_SELECT,
PSP_CTRL_UP, PSP_CTRL_DOWN, PSP_CTRL_LEFT, PSP_CTRL_RIGHT, PSP_CTRL_HOME,
PSP_CTRL_NOTE, PSP_CTRL_SCREEN, PSP_CTRL_VOLUP, PSP_CTRL_VOLDOWN
};
const char *map_names[PSP_BUTTON_MAX] = {
"cross", "circle", "triangle", "square",
"ltrig", "rtrig", "start", "select",
"up", "down", "left", "right", "home",
"note", "screen", "volup", "voldown"
};
/* Maps the buttons on the joystick to the buttons on the PSP controller */
unsigned int *g_buttmap = NULL;
struct Args
{
const char *ip;
unsigned short port;
const char *dev;
const char *mapfile;
int verbose;
int video;
int fullscreen;
int droprate;
int fullcolour;
int halfsize;
int showfps;
};
struct GlobalContext
{
struct Args args;
struct sockaddr_in serv;
char name[128];
unsigned int version;
unsigned char axes;
unsigned char buttons;
int exit;
int digital;
int analog;
int tol;
int scron;
};
struct GlobalContext g_context;
struct ScreenBuffer
{
unsigned char buf[PSP_SCREEN_W * PSP_SCREEN_H * 4];
struct JoyScrHeader head;
/* Mutex? */
};
static struct ScreenBuffer g_buffers[2];
void init_font(void);
void print_text(SDL_Surface *screen, int x, int y, const char *fmt, ...);
/* Should have a mutex on each screen */
#define VERBOSE (g_context.args.verbose)
int fixed_write(int s, const void *buf, int len)
{
int written = 0;
while(written < len)
{
int ret;
ret = write(s, buf+written, len-written);
if(ret < 0)
{
if(errno != EINTR)
{
perror("write");
written = -1;
break;
}
}
else
{
written += ret;
}
}
return written;
}
int parse_args(int argc, char **argv, struct Args *args)
{
memset(args, 0, sizeof(*args));
args->ip = DEFAULT_IP;
args->port = DEFAULT_PORT;
while(1)
{
int ch;
int error = 0;
ch = getopt(argc, argv, "vsfchldp:i:m:r:");
if(ch < 0)
{
break;
}
switch(ch)
{
case 'p': args->port = atoi(optarg);
break;
case 'i': args->ip = optarg;
break;
case 'm': args->mapfile = optarg;
break;
case 'v': args->verbose = 1;
break;
case 'd': args->video = 1;
break;
case 'f': args->fullscreen = 1;
break;
case 'c': args->fullcolour = 1;
break;
case 'l': args->halfsize = 1;
break;
case 's': args->showfps = 1;
break;
case 'r': args->droprate = atoi(optarg);
if((args->droprate < 0) || (args->droprate > 59))
{
fprintf(stderr, "Invalid drop rate (0 <= r < 60)\n");
error = 1;
}
break;
case 'h':
default : error = 1;
break;
};
if(error)
{
return 0;
}
}
argc -= optind;
argv += optind;
return 1;
}
void print_help(void)
{
fprintf(stderr, "Remotejoy Help\n");
fprintf(stderr, "Usage: remotejoy [options]\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, "-p port : Specify the port number\n");
fprintf(stderr, "-i ip : Specify the ip address (default %s)\n", DEFAULT_IP);
fprintf(stderr, "-m mapfile : Specify a file to map joystick buttons to the PSP\n");
fprintf(stderr, "-d : Auto enable display support\n");
fprintf(stderr, "-f : Full screen mode\n");
fprintf(stderr, "-r drop : Frame Skip, 0 (auto), 1 (1/2), 2 (1/3), 3(1/4) etc.\n");
fprintf(stderr, "-c : Full colour mode\n");
fprintf(stderr, "-l : Half size mode (both X and Y)\n");
fprintf(stderr, "-s : Show fps\n");
fprintf(stderr, "-v : Verbose mode\n");
}
int init_sockaddr(struct sockaddr_in *name, const char *ipaddr, unsigned short port)
{
struct hostent *hostinfo;
name->sin_family = AF_INET;
name->sin_port = htons(port);
hostinfo = gethostbyname(ipaddr);
if(hostinfo == NULL)
{
fprintf(stderr, "Unknown host %s\n", ipaddr);
return 0;
}
name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
return 1;
}
int connect_to(const char *ipaddr, unsigned short port)
{
struct sockaddr_in name;
int sock = -1;
int flag = 1;
sock = socket(PF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return -1;
}
if(!init_sockaddr(&name, ipaddr, port))
{
printf("Could not initialise socket address\n");
close(sock);
return -1;
}
if(connect(sock, (struct sockaddr *) &name, sizeof(name)) < 0)
{
perror("connect");
close(sock);
return -1;
}
/* Disable NAGLE's algorithm to prevent the packets being joined */
setsockopt(sock, SOL_TCP, TCP_NODELAY, &flag, sizeof(int));
return sock;
}
int get_joyinfo(SDL_Joystick *stick)
{
const char *name;
name = SDL_JoystickName(0);
if(!name)
{
return 0;
}
strcpy(g_context.name, name);
g_context.axes = SDL_JoystickNumAxes(stick);
g_context.buttons = SDL_JoystickNumButtons(stick);
return 1;
}
void remove_wsp(char *buf)
{
int len = strlen(buf);
int i = 0;
while(isspace(buf))
{
i++;
}
if(i > 0)
{
len -= i;
memmove(buf, &buf, len + 1);
}
if(len <= 0)
{
return;
}
i = len-1;
while(isspace(buf))
{
buf[i--] = 0;
}
}
int build_map(const char *mapfile, int buttons)
{
int i;
FILE *fp;
g_context.analog = -1;
g_context.digital = -1;
g_context.tol = DIGITAL_TOL;
g_buttmap = (unsigned int *) malloc(buttons * sizeof(unsigned int));
if(g_buttmap == NULL)
{
return 0;
}
for(i = 0; i < buttons; i++)
{
/* Fill with mappings, repeat if more than 8 buttons */
g_buttmap = i % 8;
}
if(mapfile)
{
char buffer[512];
int line = 0;
fp = fopen(mapfile, "r");
if(fp == NULL)
{
fprintf(stderr, "Could not open mapfile %s\n", mapfile);
return 0;
}
while(fgets(buffer, sizeof(buffer), fp))
{
char *tok, *val;
int butt;
line++;
remove_wsp(buffer);
if((buffer[0] == '#') || (buffer[0] == 0)) /* Comment or empty line */
{
continue;
}
tok = strtok(buffer, ":");
val = strtok(NULL, "");
if((tok == NULL) || (val == NULL))
{
printf("Invalid mapping on line %d\n", line);
continue;
}
butt = atoi(val);
for(i = 0; i < PSP_BUTTON_MAX; i++)
{
if(strcasecmp(map_names, tok) == 0)
{
g_buttmap[butt] = i;
break;
}
}
if(i == PSP_BUTTON_MAX)
{
if(strcasecmp("analog", tok) == 0)
{
g_context.analog = butt;
}
else if(strcasecmp("digital", tok) == 0)
{
g_context.digital = butt;
}
else if(strcasecmp("tol", tok) == 0)
{
g_context.tol = atoi(val);
}
else
{
fprintf(stderr, "Unknown map type %s\n", tok);
}
}
}
fclose(fp);
}
return 1;
}
int send_event(int sock, int type, unsigned int value)
{
struct JoyEvent event;
if(sock < 0)
{
return 0;
}
/* Note, should swap endian */
event.magic = LE32(JOY_MAGIC);
event.type = LE32(type);
event.value = LE32(value);
if(fixed_write(sock, &event, sizeof(event)) != sizeof(event))
{
fprintf(stderr, "Could not write out data to socket\n");
return 0;
}
return 1;
}
void post_event(int no)
{
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = no;
event.user.data1 = NULL;
event.user.data2 = NULL;
SDL_PushEvent(&event);
}
int flush_socket(int sock)
{
/* If we encounter some horrible error which means we are desynced
* then send a video off packet to remotejoy, wait around for a second sucking up
* any more data from the socket and then reenable */
return 0;
}
void update_fps(SDL_Surface *screen)
{
#define FRAME_VALUES 32
static Uint32 times[FRAME_VALUES];
static Uint32 lastticks = 0;
static int index = 0;
Uint32 ticks;
int i;
double fps;
ticks = SDL_GetTicks();
times[index] = ticks - lastticks;
index = (index + 1) % FRAME_VALUES;
lastticks = ticks;
fps = 0.0;
for(i = 0; i < FRAME_VALUES; i++)
{
fps += (double) times;
}
fps /= (double) FRAME_VALUES;
/* Fps is now average frame time */
fps = 1000.0 / fps;
/* Now frame frequency in Hz */
print_text(screen, 0, 0, "Fps: %.2f", fps);
}
int read_thread(void *p)
{
int err = 0;
int frame = 0;
fd_set saveset, readset;
int count;
int sock = *(int *) p;
struct JoyScrHeader head;
FD_ZERO(&saveset);
FD_SET(sock, &saveset);
while(!err)
{
readset = saveset;
count = select(FD_SETSIZE, &readset, NULL, NULL, NULL);
if(count > 0)
{
int ret;
int mode;
int size;
if(FD_ISSET(sock, &readset))
{
ret = read(sock, &head, sizeof(head));
if((ret != sizeof(head)) || (LE32(head.magic) != JOY_MAGIC))
{
fprintf(stderr, "Error in socket %d, magic %08X\n", ret, head.magic);
flush_socket(sock);
break;
}
mode = LE32(head.mode);
size = LE32(head.size);
g_buffers[frame].head.mode = mode;
g_buffers[frame].head.size = size;
if(mode < 0)
{
if(g_context.args.video)
{
post_event(EVENT_ENABLE_SCREEN);
}
else
{
g_context.scron = 0;
}
}
else if(mode > 3)
{
/* Flush socket */
flush_socket(sock);
}
else
{
/* Try and read in screen */
/* If we do not get a full frame read and we timeout in quater second or so then
* reset sync as it probably means the rest isn't coming */
int loc = 0;
//fprintf(stderr, "Size %d\n", size);
while(1)
{
readset = saveset;
/* Should have a time out */
count = select(FD_SETSIZE, &readset, NULL, NULL, NULL);
if(count > 0)
{
ret = read(sock, &(g_buffers[frame].buf[loc]), size-loc);
if(ret < 0)
{
if(errno != EINTR)
{
perror("read:");
err = 1;
break;
}
}
else if(ret == 0)
{
fprintf(stderr, "EOF\n");
break;
}
//fprintf(stderr, "Read %d\n", loc);
loc += ret;
if(loc == size)
{
break;
}
}
else if(count < 0)
{
if(errno != EINTR)
{
perror("select:");
err = 1;
break;
}
}
}
if(!err)
{
if(frame)
{
post_event(EVENT_RENDER_FRAME_2);
}
else
{
post_event(EVENT_RENDER_FRAME_1);
}
frame ^= 1;
}
}
}
}
else if(count < 0)
{
if(errno != EINTR)
{
perror("select:");
err = 1;
}
}
}
return 0;
}
SDL_Surface *create_surface(void *buf, int mode)
{
unsigned int rmask, bmask, gmask, amask;
int currw, currh;
int bpp;
currw = PSP_SCREEN_W;
currh = PSP_SCREEN_H;
if(g_context.args.halfsize)
{
currw >>= 1;
currh >>= 1;
}
if(VERBOSE)
{
printf("Mode %d\n", mode);
}
switch(mode)
{
case 3:
rmask = LE32(0x000000FF);
gmask = LE32(0x0000FF00);
bmask = LE32(0x00FF0000);
amask = 0;
bpp = 32;
break;
case 2:
rmask = LE16(0x000F);
gmask = LE16(0x00F0);
bmask = LE16(0x0F00);
amask = 0;
bpp = 16;
break;
case 1:
rmask = LE16(0x1F);
gmask = LE16(0x1F << 5);
bmask = LE16(0x1F << 10);
amask = 0;
bpp = 16;
break;
case 0:
rmask = LE16(0x1F);
gmask = LE16(0x3F << 5);
bmask = LE16(0x1F << 11);
amask = 0;
bpp = 16;
break;
default: return NULL;
};
return SDL_CreateRGBSurfaceFrom(buf, currw, currh, bpp, currw*(bpp/8),
rmask, gmask, bmask, amask);
}
void save_screenshot(SDL_Surface *surface)
{
int i;
char path[PATH_MAX];
struct stat s;
/* If we cant find one in the next 1000 then dont bother */
for(i = 0; i < 1000; i++)
{
snprintf(path, PATH_MAX, "scrshot%03d.bmp", i);
if(stat(path, &s) < 0)
{
break;
}
}
if(i == 1000)
{
return;
}
if(SDL_SaveBMP(surface, path) == 0)
{
printf("Saved screenshot to %s\n", path);
}
else
{
printf("Error saving screenshot\n");
}
}
void mainloop(void)
{
SDL_Joystick *stick = NULL;
SDL_Surface *screen = NULL;
SDL_Surface *buf1 = NULL;
SDL_Surface *buf2 = NULL;
SDL_Thread *thread = NULL;
int currw, currh;
int sdl_init = 0;
int sock = -1;
unsigned int button_state = 0;
int currmode[2] = { 3, 3 };
int flags = SDL_HWSURFACE;
int pspflags = 0;
int showfps = 0;
Hit my limit deleted non essentials to controller mapping and left some display transmission info in.
But no one will probably even read this... I LOVE XDADEVELOPERS!
I have an android application that is receiving a string from an arduino via Bluetooth, names the string "data" and displays it by setting a TextView to the string "data". I want a chronometer to start when the incoming string matches a predefined string.
For example:
Code:
if data.equals(startChrono)){
chronometerLeft.setBase(SystemClock.elapsedRealtime());
chronometerLeft.start();
I actually have the arduino sending a "g" and am setting my string goL to be "g" but cannot get the chronometer to start when the g is received. My TextView shows the g. Code is below. I've tried several things and at a loss. Using same code for chronometer.start() with onClickListener with a button works great. I just need it to start the chronometer when i receive a specific string from the arduino.
Code:
beginListenForData();
// text.setText("Bluetooth Opened");
}
void beginListenForData() {
final Handler handler = new Handler();
final byte delimiter = 10; // This is the ASCII code for a newline
// character
stopWorker = false;
readBufferPosition = 0;
readBuffer = new byte[1024];
workerThread = new Thread(new Runnable() {
public void run() {
while (!Thread.currentThread().isInterrupted() && !stopWorker) {
try {
int bytesAvailable = mmInputStream.available();
if (bytesAvailable > 0) {
byte[] packetBytes = new byte[bytesAvailable];
mmInputStream.read(packetBytes);
for (int i = 0; i < bytesAvailable; i++) {
byte b = packetBytes[i];
if (b == delimiter) {
byte[] encodedBytes = new byte[readBufferPosition];
System.arraycopy(readBuffer, 0,
encodedBytes, 0,
encodedBytes.length);
final String data = new String(
encodedBytes, "US-ASCII");
readBufferPosition = 0;
handler.post(new Runnable() {
public void run() {
text.setText(data);
String goL = "g";
String goR = "f";
chronometerLeft = (Chronometer)findViewById(R.id.chronometerLeft);
chronometerRight = (Chronometer)findViewById(R.id.chronometerRight);
if(data.equals(goL)){
chronometerLeft.setBase(SystemClock.elapsedRealtime());
chronometerLeft.start();
if(data.equals(goR))
chronometerRight.setBase(SystemClock.elapsedRealtime());
chronometerRight.start();
}
}
});
} else {
readBuffer[readBufferPosition++] = b;
}
}
}
} catch (IOException ex) {
stopWorker = true;
}
}
}
});
workerThread.start();
}
Sorry to bother, but in your while loop condition, what does the '!' before Thread do?