#include #include #include #include #include #include #include #include #ifdef WASABI_COMPILE_WND Display *Linux::display = NULL; int linux_atoms_loaded = 0; Atom winamp_msg; Atom dnd_enter, dnd_position, dnd_status, dnd_leave, dnd_drop, dnd_finished; Atom dnd_selection, dnd_wa3drop, dnd_private, dnd_typelist; Atom dnd_urilist, dnd_textplain, dnd_mozurl; #endif #ifndef _NOSTUDIO #ifdef WASABI_COMPILE_WND void LoadAtoms() { if ( !linux_atoms_loaded ) { linux_atoms_loaded = 1; winamp_msg = XInternAtom( Linux::getDisplay(), "Winamp3", False ); dnd_wa3drop = XInternAtom( Linux::getDisplay(), "Winamp3_drop", False ); dnd_enter = XInternAtom( Linux::getDisplay(), "XdndEnter", True ); dnd_position = XInternAtom( Linux::getDisplay(), "XdndPosition", True ); dnd_status = XInternAtom( Linux::getDisplay(), "XdndStatus", True ); dnd_leave = XInternAtom( Linux::getDisplay(), "XdndLeave", True ); dnd_drop = XInternAtom( Linux::getDisplay(), "XdndDrop", True ); dnd_finished = XInternAtom( Linux::getDisplay(), "XdndFinished", True ); dnd_selection = XInternAtom( Linux::getDisplay(), "XdndSelection", True ); dnd_private = XInternAtom( Linux::getDisplay(), "XdndActionPrivate", True ); dnd_typelist = XInternAtom( Linux::getDisplay(), "XdndTypeList", True ); dnd_urilist = XInternAtom( Linux::getDisplay(), "text/uri-list", True ); dnd_textplain = XInternAtom( Linux::getDisplay(), "text/plain", True ); dnd_mozurl = XInternAtom( Linux::getDisplay(), "text/x-moz-url", True ); } } #endif #endif void OutputDebugString( const char *s ) { #ifdef _DEBUG fprintf( stderr, "%s", s ); #endif char *file = getenv( "WASABI_LOG_FILE" ); if ( file ) { if ( !STRCMP( file, "-" ) ) { fprintf( stdout, "%s", s ); } else { FILE *f = fopen( file, "a" ); if ( f ) { fprintf( f, "%s", s ); fclose( f ); } } } } DWORD GetTickCount() { static int starttime = -1; if ( starttime == -1 ) starttime = time( NULL ); struct timeb tb; ftime( &tb ); tb.time -= starttime; return tb.time * 1000 + tb.millitm; } void Sleep( int ms ) { if ( ms != 0 ) { struct timespec ts = { 0, 0 }; ts.tv_sec = ms / 1000; ts.tv_nsec = (ms % 1000) * 1000000; nanosleep( &ts, NULL); // usleep(ms * 1000); } } #ifndef _NOSTUDIO #ifdef WASABI_COMPILE_WND Display *Linux::getDisplay() { if ( ! display ) display = WASABI_API_LINUX->linux_getDisplay(); return display; } XContext Linux::getContext() { static XContext context = 0; if ( context == 0 ) context = WASABI_API_LINUX->linux_getContext(); return context; } int Linux::getScreenNum() { return DefaultScreen( getDisplay() ); } Window Linux::RootWin() { return RootWindow( getDisplay(), getScreenNum() ); } Visual *Linux::DefaultVis() { return DefaultVisual( getDisplay(), getScreenNum() ); } void Linux::setCursor( HWND h, int cursor ) { Cursor c = XCreateFontCursor( Linux::getDisplay(), cursor ); if ( cursor == None ) XUndefineCursor( Linux::getDisplay(), h ); else XDefineCursor( Linux::getDisplay(), h, c ); XFreeCursor( Linux::getDisplay(), c ); } int Linux::convertEvent( MSG *m, XEvent *e ) { m->hwnd = e->xany.window; if ( m->hwnd ) { api_window *rw =(api_window *)GetWindowLong( m->hwnd, GWL_USERDATA ); if ( !rw ) { // This is to fix messages for dead windows... return 0; } } switch ( e->type ) { case ButtonPress: switch( e->xbutton.button ) { case 1: m->message = WM_LBUTTONDOWN; m->lParam = (e->xbutton.x & 0xffff) | (e->xbutton.y << 16); break; case 2: case 3: m->message = WM_RBUTTONDOWN; m->lParam = (e->xbutton.x & 0xffff) | (e->xbutton.y << 16); break; case 4: m->message = WM_MOUSEWHEEL; m->wParam = 120 << 16 | 0; // 1 tick, no modifiers m->lParam = (e->xbutton.x & 0xffff) | (e->xbutton.y << 16); break; case 5: m->message = WM_MOUSEWHEEL; m->wParam = (-120) << 16 | 0; // 1 tick, no modifiers m->lParam = (e->xbutton.x & 0xffff) | (e->xbutton.y << 16); break; } break; case ButtonRelease: switch( e->xbutton.button ) { case 1: m->message = WM_LBUTTONUP; m->lParam = (e->xbutton.x & 0xffff) | (e->xbutton.y << 16); break; case 2: case 3: m->message = WM_RBUTTONUP; m->lParam = (e->xbutton.x & 0xffff) | (e->xbutton.y << 16); break; } break; case MotionNotify: { m->message = WM_MOUSEMOVE; do { // Spin... } while( XCheckTypedWindowEvent( Linux::getDisplay(), m->hwnd, MotionNotify, e ) ); RECT r; POINT offset = {0, 0}; HWND hwnd = m->hwnd; GetWindowRect( hwnd, &r ); m->lParam = ((e->xmotion.x_root - r.left) & 0xffff) | ((e->xmotion.y_root - r.top) << 16); if ( ! (e->xmotion.state & ( Button1Mask | Button2Mask | Button3Mask )) ) PostMessage( m->hwnd, WM_SETCURSOR, m->hwnd, 0 ); } break; case KeyPress: m->message = WM_KEYDOWN; m->wParam = e->xkey.keycode; break; case KeyRelease: m->message = WM_KEYUP; m->wParam = e->xkey.keycode; break; case Expose: { RECT r; m->message = WM_PAINT; do { r.left = e->xexpose.x; r.top = e->xexpose.y; r.right = r.left + e->xexpose.width; r.bottom = r.top + e->xexpose.height; InvalidateRect( m->hwnd, &r, FALSE ); } while( XCheckTypedWindowEvent( Linux::getDisplay(), m->hwnd, Expose, e ) ); } break; case ClientMessage: { static int coord = -1; static Atom supported = None; XClientMessageEvent cme; LoadAtoms(); int message = e->xclient.message_type; if ( message == dnd_enter ) { if ( e->xclient.data.l[1] & 1 ) { Atom actual; int format; long unsigned int nitems, bytes; unsigned char *data = NULL; XGetWindowProperty( Linux::getDisplay(), e->xclient.data.l[0], dnd_typelist, 0, 65536, True, XA_ATOM, &actual, &format, &nitems, &bytes, &data ); Atom *atomdata = (Atom *)data; supported = None; for( int i = 0; i < nitems; i++ ) { if ( atomdata[i] == dnd_urilist ) { supported = dnd_urilist; } } if ( supported == None ) { for( int i = 0; i < nitems; i++ ) { if ( atomdata[i] == dnd_textplain ) { OutputDebugString( "text/plain found\n" ); supported = dnd_textplain; } } } if ( supported == None ) { for( int i = 0; i < nitems; i++ ) { if ( atomdata[i] == dnd_mozurl ) { supported = dnd_mozurl; } } } XFree( data ); } else { if ( e->xclient.data.l[2] == dnd_urilist || e->xclient.data.l[3] == dnd_urilist || e->xclient.data.l[4] == dnd_urilist ) { supported = dnd_urilist; } else if ( e->xclient.data.l[2] == dnd_mozurl || e->xclient.data.l[3] == dnd_mozurl || e->xclient.data.l[4] == dnd_mozurl ) { supported = dnd_mozurl; } } // DnD Enter return 0; } else if ( message == dnd_position ) { // DnD Position Notify cme.type = ClientMessage; cme.message_type = dnd_status; cme.format = 32; cme.window = e->xclient.data.l[0]; cme.data.l[0] = e->xclient.window; cme.data.l[1] = 1; // Can Accept cme.data.l[2] = cme.data.l[3] = 0; // Empty rectangle - give us moves cme.data.l[4] = dnd_private; // We're doing our own thing if ( coord == -1 && supported != None ) { XConvertSelection( Linux::getDisplay(), dnd_selection, supported, dnd_wa3drop, cme.window, CurrentTime ); } coord = e->xclient.data.l[2]; XSendEvent( Linux::getDisplay(), e->xclient.data.l[0], False, NoEventMask, (XEvent *)&cme ); return 0; } else if ( message == dnd_leave ) { // DnD Leave coord = -1; supported = None; return 0; } else if ( message == dnd_drop ) { // DnD Drop Window win = e->xclient.data.l[0]; cme.type = ClientMessage; cme.message_type = dnd_finished; cme.format = 32; cme.window = e->xclient.data.l[0]; cme.data.l[0] = e->xclient.window; cme.data.l[1] = cme.data.l[2] = cme.data.l[3] = cme.data.l[4] = 0; XSendEvent( Linux::getDisplay(), e->xclient.data.l[0], False, NoEventMask, (XEvent *)&cme ); if ( supported != None ) { Atom actual; int format; long unsigned int nitems, bytes; unsigned char *data = NULL; XGetWindowProperty( Linux::getDisplay(), cme.window, dnd_wa3drop, 0, 65536, True, supported, &actual, &format, &nitems, &bytes, &data ); OutputDebugString( StringPrintf( "Drop data (%d):\n%s\n", nitems, data ) ); m->message = WM_DROPFILES; m->wParam = coord; m->lParam = (LPARAM)data; coord = -1; supported = None; } else { coord = -1; supported = None; return 0; } break; } else if ( message == winamp_msg ) { // Internal Message ... m->message = e->xclient.data.l[0]; m->wParam = e->xclient.data.l[1]; m->lParam = e->xclient.data.l[2]; break; } else { return 0; } break; } case LeaveNotify: case EnterNotify: m->message = WM_MOUSEMOVE; m->lParam = (e->xcrossing.x & 0xffff) | (e->xcrossing.y << 16); if ( ! (e->xcrossing.state & ( Button1Mask | Button2Mask | Button3Mask )) ) PostMessage( m->hwnd, WM_SETCURSOR, m->hwnd, 0 ); break; case FocusIn: m->message = WM_SETFOCUS; break; case FocusOut: m->message = WM_KILLFOCUS; break; default: return 0; } return 1; } static HWND activeWindow; HWND GetActiveWindow() { return activeWindow; } int IntersectRect( RECT *out, const RECT *i1, const RECT *i2 ) { return Std::rectIntersect(i1, i2, out); } void TranslateMessage( MSG *m ) { if ( m->message != WM_CHAR && m->message != WM_KEYDOWN && m->message != WM_KEYUP ) return; int index = !!( Std::keyDown( VK_SHIFT )); m->wParam = XKeycodeToKeysym( Linux::getDisplay(), m->wParam, index ); } void PostMessage( HWND win, UINT msg, WPARAM wParam, LPARAM lParam ) { XEvent e; LoadAtoms(); e.type = ClientMessage; e.xclient.window = win; e.xclient.message_type = winamp_msg; e.xclient.format = 32; e.xclient.data.l[0] = msg; e.xclient.data.l[1] = wParam; e.xclient.data.l[2] = lParam; XSendEvent( Linux::getDisplay(), win, FALSE, NoEventMask, &e ); } void PostQuitMessage( int i ) { PostMessage( None, WM_QUIT, i, 0 ); } #endif // wnd #if defined(WASABI_API_TIMER) | defined(WASABI_API_WND) struct TimerElem { HWND win; int id; int nexttime; int delta; TIMERPROC tproc; TimerElem( HWND _win, int _id, int ms, TIMERPROC _tproc ) { win = _win; id = _id; delta = ms; tproc = _tproc; nexttime = Std::getTickCount() + delta; } }; int timer_id = 0; CriticalSection timer_cs; PtrList timer_elems; int SetTimer( HWND win, int id, int ms, TIMERPROC tproc ) { KillTimer(win, id); if ( win == (HWND)0 ) { id = timer_id++; } TimerElem *te = new TimerElem( win, id, ms, tproc ); timer_cs.enter(); timer_elems.addItem( te, PTRLIST_POS_LAST ); timer_cs.leave(); return id; } void KillTimer( HWND win, int id ) { timer_cs.enter(); for( int i = 0; i < timer_elems.getNumItems(); i++ ) if ( timer_elems[i]->win == win && timer_elems[i]->id == id ) { delete timer_elems[i]; timer_elems.delByPos( i ); i--; } timer_cs.leave(); } CriticalSection send_cs; MSG *send_msg; int sending = 0; int send_ret; pthread_t message_thread = (pthread_t)-1; int _GetMessage( MSG *m, HWND, UINT, UINT, int block=1) { MEMSET( m, 0, sizeof( MSG ) ); message_thread = pthread_self(); #ifdef WASABI_COMPILE_WND XEvent e; #endif // wnd int curtime; int done = 0; int first = 1; static wa_msgbuf ipcm; static int qid = -1; int size; if ( qid == -1 ) { qid = WASABI_API_LINUX->linux_getIPCId(); } if ( sending ) { *m = *send_msg; done = 1; } while( !done && (block || first)) { if ( qid != -1 ) { if ( (size = msgrcv( qid, &ipcm, IPC_MSGMAX , 0, IPC_NOWAIT )) != -1 ) { m->hwnd = None; m->message = WM_WA_IPC; m->wParam = (WPARAM)&ipcm; break; } } curtime = GetTickCount(); timer_cs.enter(); for( int i = 0; i < timer_elems.getNumItems(); i++ ) { if ( timer_elems[i]->nexttime < curtime ) { if (block) while( timer_elems[i]->nexttime < curtime ) timer_elems[i]->nexttime += timer_elems[i]->delta; m->hwnd = timer_elems[i]->win; m->message = WM_TIMER; m->wParam = (WPARAM)timer_elems[i]->id; m->lParam = (LPARAM)timer_elems[i]->tproc; done = 1; } } timer_cs.leave(); if ( !done && ! first ) Sleep( 1 ); else first = 0; #ifdef WASABI_API_WND if ( !done && XPending( Linux::getDisplay() ) ) { int n = XEventsQueued( Linux::getDisplay(), QueuedAlready ); for ( int i = 0; !done && i < n; i++ ) { XNextEvent( Linux::getDisplay(), &e ); if ( Linux::convertEvent( m, &e ) ) done = 1; } if ( done ) break; } #endif // wnd } #ifdef WASABI_API_WND activeWindow = m->hwnd; #endif // wnd return m->message != WM_QUIT; } int GetMessage( MSG *m, HWND w, UINT f, UINT l) { return _GetMessage(m, w, f, l, 1); } // on linux, we don't really simply peek when PM_NOREMOVE is used, // we just don't block, which is the only thing we want to accomplish here int PeekMessage( MSG *m, HWND w, UINT f, UINT l, UINT remove) { if (remove == PM_NOREMOVE) return _GetMessage(m, w, f, l, 0); else _GetMessage(m, w, f, l, 1); } int DispatchMessage( MSG *m ) { if ( m->message == WM_TIMER && m->hwnd == None ) { TIMERPROC tproc = (TIMERPROC)m->lParam; tproc( m->hwnd, m->message, m->wParam, 0 ); return 1; } int ret = 0; #ifdef WASABI_COMPILE_WND api_window *rootwnd = (api_window *)GetWindowLong( m->hwnd, GWL_USERDATA ); if ( rootwnd ) { ret = rootwnd->wndProc( m->hwnd, m->message, m->wParam, m->lParam ); rootwnd->performBatchProcesses(); } #endif // wnd if ( sending ) { send_ret = ret; sending = 0; } return ret; } int SendMessage( HWND win, UINT msg, WPARAM wParam, LPARAM lParam ) { MSG m; m.hwnd = win; m.message = msg; m.wParam = wParam; m.lParam = lParam; int ret; if ( pthread_equal( message_thread, pthread_self() ) ) { return DispatchMessage( &m ); } else { send_cs.enter(); sending = 1; send_msg = &m; while( sending ) { Sleep( 1 ); } ret = send_ret; send_cs.leave(); return ret; } } #endif // timer | wnd int MulDiv( int m1, int m2, int d ) { __asm__ volatile ( "mov %0, %%eax\n" "mov %1, %%ebx\n" "mov %2, %%ecx\n" "mul %%ebx\n" "div %%ecx\n" : : "m" (m1), "m" (m2), "m" (d) : "%eax", "%ebx", "%ecx", "%edx" ); } void ExitProcess( int ret ) { exit( ret ); } #ifdef WASABI_COMPILE_WND void Linux::initContextData( HWND h ) { int *data; XPointer xp; ASSERT( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &xp )); data = (int *)MALLOC( GWL_ENUM_SIZE * sizeof( int ) ); data[GWL_HWND] = h; XSaveContext( Linux::getDisplay(), h, Linux::getContext(), (char *)data ); } void Linux::nukeContextData( HWND h ) { int *data; XPointer xp; if ( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &xp ) ) return; data = (int *)xp; ASSERT( data[GWL_HWND] == h ); if ( data[GWL_INVALIDREGION] ) { XDestroyRegion( (HRGN)data[GWL_INVALIDREGION] ); } XDeleteContext( Linux::getDisplay(), h, Linux::getContext() ); FREE( data ); } void SetWindowLong( HWND h, contextdata type, LONG value ) { XPointer data; if ( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &data ) ) return; ASSERT( ((int *)data)[GWL_HWND] == h ); ((int*)data)[type] = value; } LONG GetWindowLong( HWND h, contextdata type ) { XPointer data; if ( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &data ) ) return 0; ASSERT( ((int *)data)[GWL_HWND] == h ); return ((int*)data)[type]; } void MoveWindowRect( HWND h, int x, int y ) { XPointer xp; int *data; if ( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &xp ) ) return; data = (int *)xp; ASSERT( data[GWL_HWND] == h ); data[GWL_RECT_RIGHT] -= data[GWL_RECT_LEFT] - x; data[GWL_RECT_BOTTOM] -= data[GWL_RECT_TOP] - y; data[GWL_RECT_LEFT] = x; data[GWL_RECT_TOP] = y; } void SetWindowRect( HWND h, RECT *r ) { int *data; XPointer xp; if ( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &xp ) ) return; data = (int *)xp; ASSERT( data[GWL_HWND] == h ); data[GWL_RECT_LEFT] = r->left; data[GWL_RECT_TOP] = r->top; data[GWL_RECT_RIGHT] = r->right; data[GWL_RECT_BOTTOM] = r->bottom; } int GetWindowRect( HWND h, RECT *r ) { int *data; XPointer xp; if ( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &xp ) ) return 0; data = (int *)xp; ASSERT( data[GWL_HWND] == h ); r->left = data[GWL_RECT_LEFT]; r->top = data[GWL_RECT_TOP]; r->right = data[GWL_RECT_RIGHT]; r->bottom = data[GWL_RECT_BOTTOM]; POINT offset = { 0, 0}; while( (h = data[GWL_PARENT]) != Linux::RootWin() ) { if ( XFindContext( Linux::getDisplay(), h, Linux::getContext(), &xp ) ) return 0; data = (int *)xp; ASSERT( data[GWL_HWND] == h ); offset.x += data[GWL_RECT_LEFT]; offset.y += data[GWL_RECT_TOP]; } r->left += offset.x; r->top += offset.y; r->right += offset.x; r->bottom += offset.y; return 1; } int GetUpdateRect( HWND h, RECT *ret, BOOL ) { HRGN invalid = (HRGN)GetWindowLong( h, GWL_INVALIDREGION ); if ( ! invalid || XEmptyRegion( invalid ) ) return 0; XRectangle xr; XClipBox( invalid, &xr ); ret->left = xr.x; ret->top = xr.y; ret->right = xr.x + xr.width; ret->bottom = xr.y + xr.height; return 1; } void GetUpdateRgn( HWND h, HRGN r, BOOL ) { XSubtractRegion( r, r, r ); HRGN invalid = (HRGN)GetWindowLong( h, GWL_INVALIDREGION ); if ( ! invalid ) return; XUnionRegion( r, invalid, r ); XRectangle xr; RECT rct; GetWindowRect( h, &rct ); xr.x = 0; xr.y = 0; xr.width = rct.right - rct.left; xr.height = rct.bottom - rct.top; HRGN tmp = XCreateRegion(); XUnionRectWithRegion( &xr, tmp, tmp ); XIntersectRegion( r, tmp, r ); XDestroyRegion( tmp ); } void InvalidateRect( HWND h, const RECT *r, BOOL ) { HRGN invalid = (HRGN)GetWindowLong( h, GWL_INVALIDREGION ); if ( ! invalid ) { invalid = XCreateRegion(); SetWindowLong( h, GWL_INVALIDREGION, (LONG)invalid ); } XRectangle xr; if ( r == NULL ) { RECT rct; GetWindowRect( h, &rct ); xr.x = 0; xr.y = 0; xr.width = rct.right - rct.left; xr.height = rct.bottom - rct.top; } else { xr.x = r->left; xr.y = r->top; xr.width = r->right - r->left; xr.height = r->bottom - r->top; } XUnionRectWithRegion( &xr, invalid, invalid ); PostMessage( h, WM_PAINT, 0, 0 ); } void InvalidateRgn( HWND h, HRGN r, BOOL ) { HRGN invalid = (HRGN)GetWindowLong( h, GWL_INVALIDREGION ); if ( ! invalid ) { invalid = XCreateRegion(); SetWindowLong( h, GWL_INVALIDREGION, (LONG)invalid ); } ASSERT( r != invalid ); XUnionRegion( invalid, r, invalid ); PostMessage( h, WM_PAINT, 0, 0 ); } void ValidateRect( HWND h, const RECT *r ) { HRGN invalid = (HRGN)GetWindowLong( h, GWL_INVALIDREGION ); if ( ! invalid ) return; XRectangle xr; if ( r == NULL ) { XDestroyRegion( invalid ); SetWindowLong( h, GWL_INVALIDREGION, 0 ); return; } xr.x = r->left; xr.y = r->top; xr.width = r->right - r->left; xr.height = r->bottom - r->top; HRGN tmp = XCreateRegion(); XUnionRectWithRegion( &xr, tmp, tmp ); XSubtractRegion( invalid, tmp, invalid ); XDestroyRegion( tmp ); } void ValidateRgn( HWND h, HRGN r ) { HRGN invalid = (HRGN)GetWindowLong( h, GWL_INVALIDREGION ); if ( ! invalid ) return; ASSERT( r != invalid ); XSubtractRegion( invalid, r, invalid ); } #endif // wnd int SubtractRect( RECT *out, RECT *in1, RECT *in2 ) { int ret; if ( in1->left >= in2->left && in1->right <= in2->right ) { out->left = in1->left; out->right = in1->right; if ( in1->top >= in2->top && in2->bottom >= in2->top && in2->bottom <= in2->bottom ) { out->top = in1->bottom; out->bottom = in2->bottom; ret = 1; } else if ( in1->top <= in2->top && in1->bottom >= in2->top && in1->bottom <= in2->bottom ) { out->top = in1->top; out->bottom = in2->top; ret = 1; } else { ret = 0; } } else if ( in1->top >= in2->top && in1->bottom <= in2->bottom ) { out->top = in1->top; out->bottom = in1->bottom; if ( in1->left >= in2->left && in2->right >= in2->left && in2->right <= in2->right ) { out->left = in1->right; out->right = in2->right; ret = 1; } else if ( in1->left <= in2->left && in1->right >= in2->left && in1->right <= in2->right ) { out->left = in1->left; out->right = in2->left; ret = 1; } else { ret = 0; } } else { ret = 0; } return ret; } int EqualRect( RECT *a, RECT *b ) { return ( a->top == b->top && a->bottom == b->bottom && a->left == b->left && a->right == b->right ); } #ifdef WASABI_COMPILE_WND HWND WindowFromPoint( POINT p ) { int x, y; Window child; XTranslateCoordinates( Linux::getDisplay(), Linux::RootWin(), Linux::RootWin(), p.x, p.y, &x, &y, &child ); return child; } #endif // wnd void CopyFile( const char *f1, const char *f2, BOOL b ) { COPYFILE( f1, f2 ); } DWORD GetModuleFileName(void *pid, const char *filename, int bufsize) { char procbuffer[512]; sprintf(procbuffer, "/proc/%d/exe", (int)pid); return readlink(procbuffer, (char *)filename, bufsize); } const char *CharPrev(const char *lpszStart, const char *lpszCurrent) { if (lpszCurrent-1 >= lpszStart) return lpszCurrent-1; return lpszStart; } #endif