Commit 3a3cd9fa authored by Francois Boisvert's avatar Francois Boisvert Committed by Alexandre Julliard

Fixed some bugs in thread safeness for wnd struct.

parent 0dddc09b
......@@ -3544,8 +3544,6 @@ BOOL WINAPI DestroyMenu( HMENU hMenu )
(!pTPWnd || (lppop->hWnd != pTPWnd->hwndSelf)))
DestroyWindow( lppop->hWnd );
MENU_ReleaseTopPopupWnd();
if (lppop->items) /* recursively destroy submenus */
{
int i;
......@@ -3558,8 +3556,13 @@ BOOL WINAPI DestroyMenu( HMENU hMenu )
HeapFree( SystemHeap, 0, lppop->items );
}
USER_HEAP_FREE( hMenu );
MENU_ReleaseTopPopupWnd();
}
else return FALSE;
else
{
MENU_ReleaseTopPopupWnd();
return FALSE;
}
}
return (hMenu != MENU_DefSysPopup);
}
......
......@@ -410,13 +410,9 @@ static BOOL DCE_AddClipRects( WND *pWndStart, WND *pWndEnd,
if( pWndStart->pDriver->pIsSelfClipping( pWndStart ) )
return TRUE; /* The driver itself will do the clipping */
for (; pWndStart != pWndEnd; pWndStart = WIN_LockWndPtr(pWndStart->next))
for (WIN_LockWndPtr(pWndStart); pWndStart != pWndEnd; WIN_UpdateWndPtr(&pWndStart,pWndStart->next))
{
if( !(pWndStart->dwStyle & WS_VISIBLE) )
{
WIN_ReleaseWndPtr(pWndStart);
continue;
}
if( !(pWndStart->dwStyle & WS_VISIBLE) ) continue;
rect.left = pWndStart->rectWindow.left + x;
rect.top = pWndStart->rectWindow.top + y;
......@@ -424,13 +420,11 @@ static BOOL DCE_AddClipRects( WND *pWndStart, WND *pWndEnd,
rect.bottom = pWndStart->rectWindow.bottom + y;
if( IntersectRect( &rect, &rect, lpRect ))
if(!REGION_UnionRectWithRgn( hrgnClip, &rect ))
{
WIN_ReleaseWndPtr(pWndStart);
break;
}
WIN_ReleaseWndPtr(pWndStart);
{
if(!REGION_UnionRectWithRgn( hrgnClip, &rect )) break;
}
}
WIN_ReleaseWndPtr(pWndStart);
return (pWndStart == pWndEnd);
}
......
......@@ -1598,14 +1598,15 @@ BOOL WINAPI CheckRadioButton( HWND hwndDlg, UINT firstID,
UINT lastID, UINT checkID )
{
WND *pWnd = WIN_FindWndPtr( hwndDlg );
if (!pWnd) return FALSE;
for (WIN_UpdateWndPtr(&pWnd,pWnd->child); pWnd;WIN_UpdateWndPtr(&pWnd,pWnd->next))
if ((pWnd->wIDmenu == firstID) || (pWnd->wIDmenu == lastID))
{
WIN_ReleaseWndPtr(pWnd);
break;
}
if (!pWnd) return FALSE;
if (pWnd->wIDmenu == lastID)
......
......@@ -87,7 +87,8 @@ HWND WINAPI SetFocus( HWND hwnd )
{
if ( wndPtr->dwStyle & ( WS_MINIMIZE | WS_DISABLED) )
goto CLEANUP;
if (!(wndPtr = wndPtr->parent)) goto CLEANUP;
WIN_UpdateWndPtr(&wndPtr,wndPtr->parent);
if (!wndPtr) goto CLEANUP;
hwndTop = wndPtr->hwndSelf;
}
......
......@@ -120,23 +120,25 @@ static BOOL MDI_MenuDeleteItem(WND* clientWnd, HWND hWndChild )
for( index = id+1; index <= clientInfo->nActiveChildren +
clientInfo->idFirstChild; index++ )
{
wndPtr = WIN_FindWndPtr(MDI_GetChildByID(clientWnd,index));
if( !wndPtr )
WND *tmpWnd = WIN_FindWndPtr(MDI_GetChildByID(clientWnd,index));
if( !tmpWnd )
{
TRACE(mdi,"no window for id=%i\n",index);
WIN_ReleaseWndPtr(tmpWnd);
continue;
}
/* set correct id */
wndPtr->wIDmenu--;
tmpWnd->wIDmenu--;
n = sprintf(buffer, "%d ",index - clientInfo->idFirstChild);
if (wndPtr->text)
lstrcpynA(buffer + n, wndPtr->text, sizeof(buffer) - n );
if (tmpWnd->text)
lstrcpynA(buffer + n, tmpWnd->text, sizeof(buffer) - n );
/* change menu */
ModifyMenuA(clientInfo->hWindowMenu ,index ,MF_BYCOMMAND | MF_STRING,
index - 1 , buffer );
WIN_ReleaseWndPtr(tmpWnd);
}
retvalue = TRUE;
END:
......@@ -711,7 +713,7 @@ static LONG MDICascade(WND* clientWnd, MDICLIENTINFO *ci)
SWP_DRAWFRAME | SWP_NOACTIVATE | SWP_NOZORDER);
}
}
HeapFree( SystemHeap, 0, heapPtr );
WIN_ReleaseWinArray(heapPtr);
}
if( total < ci->nActiveChildren )
......@@ -787,7 +789,7 @@ static void MDITile( WND* wndClient, MDICLIENTINFO *ci, WPARAM wParam )
x += xsize;
}
}
HeapFree( SystemHeap, 0, heapPtr );
WIN_ReleaseWinArray(heapPtr);
}
if( total < ci->nActiveChildren ) ArrangeIconicWindows( wndClient->hwndSelf );
......@@ -902,10 +904,12 @@ static void MDI_UpdateFrameText( WND *frameWnd, HWND hClient,
if (!clientWnd)
return;
WIN_ReleaseWndPtr(clientWnd);
if (!ci)
{
WIN_ReleaseWndPtr(clientWnd);
return;
}
/* store new "default" title if lpTitle is not NULL */
if (lpTitle)
......@@ -944,13 +948,14 @@ static void MDI_UpdateFrameText( WND *frameWnd, HWND hClient,
strcat( lpBuffer, "]" );
}
}
WIN_ReleaseWndPtr(childWnd);
}
else
{
strncpy(lpBuffer, ci->frameTitle, MDI_MAXTITLELENGTH );
lpBuffer[MDI_MAXTITLELENGTH]='\0';
}
WIN_ReleaseWndPtr(childWnd);
}
else
lpBuffer[0] = '\0';
......@@ -959,6 +964,9 @@ static void MDI_UpdateFrameText( WND *frameWnd, HWND hClient,
if( repaint == MDI_REPAINTFRAME)
SetWindowPos( frameWnd->hwndSelf, 0,0,0,0,0, SWP_FRAMECHANGED |
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER );
WIN_ReleaseWndPtr(clientWnd);
}
......
......@@ -2552,6 +2552,7 @@ LONG NC_HandleSysCommand( HWND hwnd, WPARAM16 wParam, POINT16 pt )
break;
case SC_CLOSE:
WIN_ReleaseWndPtr(wndPtr);
return SendMessage16( hwnd, WM_CLOSE, 0, 0 );
case SC_VSCROLL:
......
......@@ -145,6 +145,8 @@ void USER_QueueCleanup( HQUEUE16 hQueue )
/* Free the message queue */
QUEUE_DeleteMsgQueue( hQueue );
WIN_ReleaseDesktop();
}
}
......@@ -170,6 +172,8 @@ static void USER_AppExit( HTASK16 hTask, HINSTANCE16 hInstance, HQUEUE16 hQueue
hInstance = GetExePtr( hInstance );
if( GetModuleUsage16( hInstance ) <= 1 )
USER_ModuleUnload( hInstance );
WIN_ReleaseDesktop();
}
......@@ -249,7 +253,11 @@ BOOL WINAPI ExitWindowsEx( UINT flags, DWORD reserved )
/* We have to build a list of all windows first, as in EnumWindows */
if (!(list = WIN_BuildWinArray( WIN_GetDesktop(), 0, NULL ))) return FALSE;
if (!(list = WIN_BuildWinArray( WIN_GetDesktop(), 0, NULL )))
{
WIN_ReleaseDesktop();
return FALSE;
}
/* Send a WM_QUERYENDSESSION message to every window */
......@@ -269,9 +277,10 @@ BOOL WINAPI ExitWindowsEx( UINT flags, DWORD reserved )
if (!IsWindow( (*ppWnd)->hwndSelf )) continue;
SendMessage16( (*ppWnd)->hwndSelf, WM_ENDSESSION, result, 0 );
}
HeapFree( SystemHeap, 0, list );
WIN_ReleaseWinArray(list);
if (result) USER_ExitWindows();
WIN_ReleaseDesktop();
return FALSE;
}
......
......@@ -54,7 +54,6 @@ static WORD wDragHeight= 3;
/* thread safeness */
static CRITICAL_SECTION WIN_CritSection;
static int ilockCounter = 0;
/***********************************************************************
* WIN_LockWnds
......@@ -64,6 +63,9 @@ static int ilockCounter = 0;
void WIN_LockWnds()
{
/*
This code will be released in the future
info : francois@macadamian.com
EnterCriticalSection(&WIN_CritSection);
*/
}
......@@ -76,6 +78,9 @@ void WIN_LockWnds()
void WIN_UnlockWnds()
{
/*
This code will be released in the future
info : francois@macadamian.com
LeaveCriticalSection(&WIN_CritSection);
*/
}
......@@ -87,12 +92,24 @@ void WIN_UnlockWnds()
*/
int WIN_SuspendWndsLock()
{
/*
int isuspendedLocks = WIN_CritSection.RecursionCount;
WIN_CritSection.RecursionCount = 0;
LeaveCriticalSection(&WIN_CritSection);
int isuspendedLocks = 0;
/* make sure that the lock is not suspended by different thread than
the owning thread */
if(WIN_CritSection.OwningThread != GetCurrentThreadId())
{
return 0;
}
/* set the value of isuspendedlock to the actual recursion count
of the critical section */
isuspendedLocks = WIN_CritSection.RecursionCount;
/* set the recursion count of the critical section to 1
so the owning thread will be able to leave it */
WIN_CritSection.RecursionCount = 1;
/* leave critical section*/
WIN_UnlockWnds();
return isuspendedLocks;
*/
}
/***********************************************************************
......@@ -100,13 +117,20 @@ int WIN_SuspendWndsLock()
*
* Restore the suspended locks on WND structures
*/
void WIN_RestoreWndslock(int ipreviousLocks)
void WIN_RestoreWndsLock(int ipreviousLocks)
{
/*
EnterCriticalSection(&WIN_CritSection);
if(!ipreviousLocks)
{
return;
}
/* restore the lock */
WIN_LockWnds();
/* set the recursion count of the critical section to the
value of suspended locks (given by WIN_SuspendWndsLock())*/
WIN_CritSection.RecursionCount = ipreviousLocks;
*/
}
/***********************************************************************
* WIN_FindWndPtr
*
......@@ -118,25 +142,25 @@ WND * WIN_FindWndPtr( HWND hwnd )
if (!hwnd || HIWORD(hwnd)) goto error2;
ptr = (WND *) USER_HEAP_LIN_ADDR( hwnd );
/* Lock all WND structures for thread safeness
WIN_LockWnds(ptr);
and increment destruction monitoring
/* Lock all WND structures for thread safeness*/
WIN_LockWnds();
/*and increment destruction monitoring*/
ptr->irefCount++;
*/
if (ptr->dwMagic != WND_MAGIC) goto error;
if (ptr->hwndSelf != hwnd)
{
ERR( win, "Can't happen: hwnd %04x self pointer is %04x\n",
hwnd, ptr->hwndSelf );
ERR( win, "Can't happen: hwnd %04x self pointer is %04x\n",hwnd, ptr->hwndSelf );
goto error;
}
/* returns a locked pointer */
return ptr;
error:
/* Unlock all WND structures for thread safeness
WIN_UnlockWnds(ptr);
and decrement destruction monitoring value
/* Unlock all WND structures for thread safeness*/
WIN_UnlockWnds();
/* and decrement destruction monitoring value */
ptr->irefCount--;
*/
error2:
if ( hwnd!=0 )
SetLastError( ERROR_INVALID_WINDOW_HANDLE );
......@@ -152,12 +176,13 @@ error2:
*/
WND *WIN_LockWndPtr(WND *initWndPtr)
{
if(!initWndPtr) return 0;
/*
/* Lock all WND structures for thread safeness*/
WIN_LockWnds();
/*and increment destruction monitoring*/
initWndPtr->irefCount++;
*/
return initWndPtr;
}
......@@ -169,22 +194,26 @@ WND *WIN_LockWndPtr(WND *initWndPtr)
*/
void WIN_ReleaseWndPtr(WND *wndPtr)
{
if(!wndPtr) return;
/*Decrement destruction monitoring value
/*Decrement destruction monitoring value*/
wndPtr->irefCount--;
Check if it's time to release the memory
/* Check if it's time to release the memory*/
if(wndPtr->irefCount == 0)
{
Add memory releasing code here
/*Add memory releasing code here*/
}
else if(wndPtr->irefCount < 0)
{
/* This else if is useful to monitor the WIN_ReleaseWndPtr function */
TRACE(win,"forgot a Lock on %p somewhere\n",wndPtr);
}
unlock all WND structures for thread safeness
/*unlock all WND structures for thread safeness*/
WIN_UnlockWnds();
*/
}
/***********************************************************************
* WIN_ReleaseWndPtr
* WIN_UpdateWndPtr
*
* Updates the value of oldPtr to newPtr.
*/
......@@ -376,17 +405,21 @@ HWND WIN_FindWinToRepaint( HWND hwnd, HQUEUE16 hQueue )
pWnd->hwndSelf );
}
else if ((pWnd->hmemTaskQ == hQueue) &&
(pWnd->hrgnUpdate || (pWnd->flags & WIN_INTERNAL_PAINT))) break;
(pWnd->hrgnUpdate || (pWnd->flags & WIN_INTERNAL_PAINT)))
break;
else if (pWnd->child )
if ((hwndRet = WIN_FindWinToRepaint( pWnd->child->hwndSelf, hQueue )) )
goto end;
{
WIN_ReleaseWndPtr(pWnd);
return hwndRet;
}
}
if(!pWnd)
{
hwndRet = 0;
goto end;
return 0;
}
hwndRet = pWnd->hwndSelf;
......@@ -397,10 +430,12 @@ HWND WIN_FindWinToRepaint( HWND hwnd, HQUEUE16 hQueue )
{
WIN_UpdateWndPtr(&pWnd,pWnd->next);
}
if (pWnd) hwndRet = pWnd->hwndSelf;
TRACE(win,"found %04x\n",hwndRet);
end:
if (pWnd)
{
hwndRet = pWnd->hwndSelf;
WIN_ReleaseWndPtr(pWnd);
}
TRACE(win,"found %04x\n",hwndRet);
return hwndRet;
}
......@@ -574,6 +609,11 @@ BOOL WIN_CreateDesktopWindow(void)
TRACE(win,"Creating desktop window\n");
/* Initialisation of the critical section for thread safeness */
InitializeCriticalSection(&WIN_CritSection);
MakeCriticalSectionGlobal(&WIN_CritSection);
if (!ICONTITLE_Init() ||
!WINPOS_CreateInternalPosAtom() ||
!(class = CLASS_FindClassByAtom( DESKTOP_CLASS_ATOM, 0 )))
......@@ -632,11 +672,6 @@ BOOL WIN_CreateDesktopWindow(void)
return FALSE;
SendMessageA( hwndDesktop, WM_NCCREATE, 0, 0 );
/* Initialisation of the critical section for thread safeness
InitializeCriticalSection(&WIN_CritSection);
MakeCriticalSectionGlobal(&WIN_CritSection);
*/
pWndDesktop->flags |= WIN_NEEDS_ERASEBKGND;
return TRUE;
}
......@@ -748,8 +783,10 @@ static HWND WIN_CreateWindowEx( CREATESTRUCTA *cs, ATOM classAtom,
wndPtr->owner = NULL;
else
{
wndPtr->owner = WIN_GetTopParentPtr(WIN_FindWndPtr(cs->hwndParent));
WND *tmpWnd = WIN_FindWndPtr(cs->hwndParent);
wndPtr->owner = WIN_GetTopParentPtr(tmpWnd);
WIN_ReleaseWndPtr(wndPtr->owner);
WIN_ReleaseWndPtr(tmpWnd);
}
}
......@@ -776,6 +813,7 @@ static HWND WIN_CreateWindowEx( CREATESTRUCTA *cs, ATOM classAtom,
wndPtr->userdata = 0;
wndPtr->hSysMenu = (wndPtr->dwStyle & WS_SYSMENU)
? MENU_GetSysMenu( hwnd, 0 ) : 0;
wndPtr->irefCount = 1;
if (classPtr->cbWndExtra) memset( wndPtr->wExtra, 0, classPtr->cbWndExtra);
......@@ -967,10 +1005,11 @@ static HWND WIN_CreateWindowEx( CREATESTRUCTA *cs, ATOM classAtom,
/* Abort window creation */
WARN(win, "aborted by WM_xxCREATE!\n");
WIN_DestroyWindow( wndPtr );
WIN_ReleaseWndPtr(WIN_DestroyWindow( wndPtr ));
retvalue = 0;
end:
WIN_ReleaseWndPtr(wndPtr);
return retvalue;
}
......@@ -1318,7 +1357,11 @@ BOOL WINAPI DestroyWindow( HWND hwnd )
}
WIN_UpdateWndPtr(&siblingPtr,siblingPtr->next);
}
if (siblingPtr) DestroyWindow( siblingPtr->hwndSelf );
if (siblingPtr)
{
DestroyWindow( siblingPtr->hwndSelf );
WIN_ReleaseWndPtr(siblingPtr);
}
else break;
}
......@@ -1345,7 +1388,7 @@ BOOL WINAPI DestroyWindow( HWND hwnd )
/* Destroy the window storage */
WIN_DestroyWindow( wndPtr );
WIN_ReleaseWndPtr(WIN_DestroyWindow( wndPtr ));
retvalue = TRUE;
end:
WIN_ReleaseWndPtr(wndPtr);
......@@ -2312,11 +2355,13 @@ HWND WINAPI GetParent( HWND hwnd )
*/
WND* WIN_GetTopParentPtr( WND* pWnd )
{
while( pWnd && (pWnd->dwStyle & WS_CHILD))
WND *tmpWnd = WIN_LockWndPtr(pWnd);
while( tmpWnd && (tmpWnd->dwStyle & WS_CHILD))
{
WIN_UpdateWndPtr(&pWnd,pWnd->parent);
WIN_UpdateWndPtr(&tmpWnd,tmpWnd->parent);
}
return pWnd;
return tmpWnd;
}
/*****************************************************************
......
......@@ -660,6 +660,7 @@ static void WINPOS_GetWinOffset( HWND hwndFrom, HWND hwndTo,
offset->y += wndPtr->rectClient.top;
WIN_UpdateWndPtr(&wndPtr,wndPtr->parent);
}
WIN_ReleaseWndPtr(wndPtr);
}
/* Translate origin to destination window coords */
......@@ -676,8 +677,8 @@ static void WINPOS_GetWinOffset( HWND hwndFrom, HWND hwndTo,
offset->y -= wndPtr->rectClient.top;
WIN_UpdateWndPtr(&wndPtr,wndPtr->parent);
}
WIN_ReleaseWndPtr(wndPtr);
}
WIN_ReleaseWndPtr(wndPtr);
}
......@@ -1632,10 +1633,13 @@ BOOL WINPOS_SetActiveWindow( HWND hWnd, BOOL fMouse, BOOL fChangeFocus)
hOldActiveQueue = hActiveQueue;
if( (wndTemp = WIN_FindWndPtr(hwndActive)) )
{
wIconized = HIWORD(wndTemp->dwStyle & WS_MINIMIZE);
WIN_ReleaseWndPtr(wndTemp);
}
else
TRACE(win,"no current active window.\n");
WIN_ReleaseWndPtr(wndTemp);
/* call CBT hook chain */
if ((cbtStruct = SEGPTR_NEW(CBTACTIVATESTRUCT16)))
{
......@@ -1650,6 +1654,7 @@ BOOL WINPOS_SetActiveWindow( HWND hWnd, BOOL fMouse, BOOL fChangeFocus)
/* Unlock the active queue before returning */
if ( pOldActiveQueue )
QUEUE_Unlock( pOldActiveQueue );
WIN_ReleaseWndPtr(wndPtr);
return wRet;
}
}
......@@ -1775,6 +1780,7 @@ BOOL WINPOS_SetActiveWindow( HWND hWnd, BOOL fMouse, BOOL fChangeFocus)
wndTemp->hwndLastActive = hWnd;
wIconized = HIWORD(wndTemp->dwStyle & WS_MINIMIZE);
WIN_ReleaseWndPtr(wndTemp);
SendMessageA( hWnd, WM_NCACTIVATE, TRUE, 0 );
#if 1
SendMessageA( hWnd, WM_ACTIVATE,
......@@ -1855,10 +1861,8 @@ BOOL WINPOS_ActivateOtherWindow(WND* pWnd)
while( !WINPOS_CanActivate(pWndTo) )
{
/* by now owned windows should've been taken care of */
WIN_ReleaseWndPtr(pWndTo);
pWndTo = WIN_LockWndPtr(pWndPtr->next);
WIN_ReleaseWndPtr(pWndPtr);
pWndPtr = WIN_LockWndPtr(pWndTo);
WIN_UpdateWndPtr(&pWndTo,pWndPtr->next);
WIN_UpdateWndPtr(&pWndPtr,pWndTo);
if( !pWndTo ) break;
}
WIN_ReleaseWndPtr(pWndPtr);
......
......@@ -416,6 +416,7 @@ static void EVENT_ProcessEvent( XEvent *event )
pWnd = NULL; /* Not for a registered window */
}
}
WIN_LockWndPtr(pWnd);
if ( !pWnd && event->xany.window != X11DRV_GetXRootWindow() )
ERR( event, "Got event %s for unknown Window %08lx\n",
......@@ -515,6 +516,7 @@ static void EVENT_ProcessEvent( XEvent *event )
event_names[event->type], pWnd? pWnd->hwndSelf : 0 );
break;
}
WIN_ReleaseWndPtr(pWnd);
}
/***********************************************************************
......@@ -586,11 +588,21 @@ static BOOL EVENT_QueryZOrder( WND* pWndCheck )
{
BOOL bRet = FALSE;
HWND hwndInsertAfter = HWND_TOP;
WND* pWnd, *pWndZ = WIN_GetDesktop()->child;
WND *pDesktop = WIN_GetDesktop();
WND *pWnd, *pWndZ = WIN_LockWndPtr(pDesktop->child);
Window w, parent, *children = NULL;
unsigned total, check, pos, best;
if( !__check_query_condition(&pWndZ, &pWnd) ) return TRUE;
if( !__check_query_condition(&pWndZ, &pWnd) )
{
WIN_ReleaseWndPtr(pDesktop->child);
WIN_ReleaseDesktop();
return TRUE;
}
WIN_LockWndPtr(pWndZ);
WIN_LockWndPtr(pWnd);
WIN_ReleaseWndPtr(pDesktop->child);
WIN_ReleaseDesktop();
parent = __get_common_ancestor( X11DRV_WND_GetXWindow(pWndZ),
X11DRV_WND_GetXWindow(pWnd),
......@@ -607,7 +619,7 @@ static BOOL EVENT_QueryZOrder( WND* pWndCheck )
check = __td_lookup( w, children, total );
best = total;
for( pWnd = pWndZ; pWnd; pWnd = pWnd->next )
for( WIN_UpdateWndPtr(&pWnd,pWndZ); pWnd;WIN_UpdateWndPtr(&pWnd,pWnd->next))
{
/* go through all windows in Wine z-order... */
......@@ -633,6 +645,8 @@ static BOOL EVENT_QueryZOrder( WND* pWndCheck )
WIN_LinkWindow( pWndCheck->hwndSelf, hwndInsertAfter);
}
if( children ) TSXFree( children );
WIN_ReleaseWndPtr(pWnd);
WIN_ReleaseWndPtr(pWndZ);
return bRet;
}
......
......@@ -378,7 +378,10 @@ void X11DRV_WND_ForceWindowRaise(WND *wndPtr)
WND *wndPrev,*pDesktop = WIN_GetDesktop();
if( !wndPtr || !X11DRV_WND_GetXWindow(wndPtr) || (wndPtr->flags & WIN_MANAGED) )
{
WIN_ReleaseDesktop();
return;
}
/* Raise all windows up to wndPtr according to their Z order.
* (it would be easier with sibling-related Below but it doesn't
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment