VirtualBox

Changeset 92228 in vbox


Ignore:
Timestamp:
Nov 4, 2021 11:48:50 PM (3 years ago)
Author:
vboxsync
Message:

ValKit/ClipUtil: Added a --wait <ms> action and a windows/os2 only --close action to make it easy to replicate conditions for bugref:10137 (ClipUtil.exe --zap --wait 60000). bugref:10133

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp

    r92161 r92228  
    177177    { "--target",                   't',                            RTGETOPT_REQ_STRING  },
    178178#endif
     179#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
     180    { "--close",                    'k',                            RTGETOPT_REQ_NOTHING },
     181#endif
     182    { "--wait",                     'w',                            RTGETOPT_REQ_UINT32 },
    179183    { "--quiet",                    'q',                            RTGETOPT_REQ_NOTHING },
    180184    { "--verbose",                  'v',                            RTGETOPT_REQ_NOTHING },
     
    227231#elif defined(RT_OS_OS2)
    228232/** Anchorblock handle. */
    229 static HAB      g_hOs2Hab = NULLHANDLE;
     233static HAB      g_hOs2Ab = NULLHANDLE;
    230234/** The message queue handle.   */
    231235static HMQ      g_hOs2MsgQueue = NULLHANDLE;
     
    236240/** Set if we're the clipboard owner. */
    237241static bool     g_fOs2ClipboardOwner = false;
     242/** Set when we receive a WM_TIMER message during DoWait(). */
     243static bool volatile g_fOs2TimerTicked = false;
    238244
    239245#elif defined(RT_OS_WINDOWS)
    240246/** Set if we've opened the clipboard. */
    241 static bool     g_fOpenedClipboard = false;
     247static bool     g_fWinOpenedClipboard = false;
     248/** Set when we receive a WM_TIMER message during DoWait(). */
     249static bool volatile g_fWinTimerTicked = false;
     250/** Window that becomes clipboard owner when setting data. */
     251static HWND     g_hWinWnd = NULL;
    242252
    243253#else
     
    275285                g_aFormats[i].fFormat = WinAddAtom(WinQuerySystemAtomTable(), g_aFormats[i].pszFormat);
    276286                if (g_aFormats[i].fFormat == 0)
    277                     RTMsgError("WinAddAtom(,%s) failed: %#x", g_aFormats[i].pszFormat, WinGetLastError(g_hOs2Hab));
     287                    RTMsgError("WinAddAtom(,%s) failed: %#x", g_aFormats[i].pszFormat, WinGetLastError(g_hOs2Ab));
    278288            }
    279289
     
    308318    if (AdHoc.fFormat == 0)
    309319    {
    310         RTMsgError("Invalid format '%s' (%#x)", pszFormat, WinGetLastError(g_hOs2Hab));
     320        RTMsgError("Invalid format '%s' (%#x)", pszFormat, WinGetLastError(g_hOs2Ab));
    311321        return NULL;
    312322    }
     
    412422        case WM_ADJUSTWINDOWPOS:
    413423            break;
     424
     425        /*
     426         * We use this window fielding WM_TIMER during DoWait.
     427         */
     428        case WM_TIMER:
     429            if (SHORT1FROMMP(mp1) == 1)
     430                g_fOs2TimerTicked = true;
     431            break;
    414432    }
    415433    return NULL;
    416434}
    417435
     436
    418437/**
    419438 * Initialize the OS/2 bits.
     
    421440static RTEXITCODE CuOs2Init(void)
    422441{
    423     g_hOs2Hab = WinInitialize(0);
    424     if (g_hOs2Hab == NULLHANDLE)
     442    g_hOs2Ab = WinInitialize(0);
     443    if (g_hOs2Ab == NULLHANDLE)
    425444        return RTMsgErrorExitFailure("WinInitialize failed!");
    426445
    427     g_hOs2MsgQueue = WinCreateMsgQueue(g_hOs2Hab, 10);
     446    g_hOs2MsgQueue = WinCreateMsgQueue(g_hOs2Ab, 10);
    428447    if (g_hOs2MsgQueue == NULLHANDLE)
    429         return RTMsgErrorExitFailure("WinCreateMsgQueue failed: %#x", WinGetLastError(g_hOs2Hab));
     448        return RTMsgErrorExitFailure("WinCreateMsgQueue failed: %#x", WinGetLastError(g_hOs2Ab));
    430449
    431450    static char s_szClass[] = "VBox-ClipUtilClipboardClass";
    432451    if (!WinRegisterClass(g_hOs2Wnd, (PCSZ)s_szClass, CuOs2WinProc, 0, 0))
    433         return RTMsgErrorExitFailure("WinRegisterClass failed: %#x", WinGetLastError(g_hOs2Hab));
     452        return RTMsgErrorExitFailure("WinRegisterClass failed: %#x", WinGetLastError(g_hOs2Ab));
    434453
    435454    g_hOs2Wnd = WinCreateWindow(HWND_OBJECT,                             /* hwndParent */
     
    444463                                NULL);                                   /* pPresParams */
    445464    if (g_hOs2Wnd == NULLHANDLE)
    446         return RTMsgErrorExitFailure("WinCreateWindow failed: %#x", WinGetLastError(g_hOs2Hab));
     465        return RTMsgErrorExitFailure("WinCreateWindow failed: %#x", WinGetLastError(g_hOs2Ab));
    447466
    448467    return RTEXITCODE_SUCCESS;
     
    457476    if (g_fOs2OpenedClipboard)
    458477    {
    459         if (!WinCloseClipbrd(g_hOs2Hab))
    460             return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Hab));
     478        if (!WinCloseClipbrd(g_hOs2Ab))
     479            return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
    461480        g_fOs2OpenedClipboard = false;
    462481    }
     
    468487    g_hOs2MsgQueue = NULLHANDLE;
    469488
    470     WinTerminate(g_hOs2Hab);
    471     g_hOs2Hab = NULLHANDLE;
     489    WinTerminate(g_hOs2Ab);
     490    g_hOs2Ab = NULLHANDLE;
    472491
    473492    return RTEXITCODE_SUCCESS;
     
    482501    if (g_fOs2OpenedClipboard)
    483502        return RTEXITCODE_SUCCESS;
    484     if (WinOpenClipbrd(g_hOs2Hab))
    485     {
     503    if (WinOpenClipbrd(g_hOs2Ab))
     504    {
     505        if (g_uVerbosity > 0)
     506            RTMsgInfo("Opened the clipboard\n");
    486507        g_fOs2OpenedClipboard = true;
    487508        return RTEXITCODE_SUCCESS;
    488509    }
    489     return RTMsgErrorExitFailure("WinOpenClipbrd failed: %#x", WinGetLastError(g_hOs2Hab));
     510    return RTMsgErrorExitFailure("WinOpenClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
    490511}
    491512
     
    494515
    495516/**
     517 * Window procedure for the clipboard owner window on Windows.
     518 */
     519static LRESULT CALLBACK CuWinWndProc(HWND hWnd, UINT idMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF
     520{
     521    if (g_uVerbosity > 2)
     522        RTMsgInfo("CuWinWndProc: hWnd=%p idMsg=%#05x wParam=%#zx lParam=%#zx\n", hWnd, idMsg, wParam, lParam);
     523
     524    switch (idMsg)
     525    {
     526        case WM_TIMER:
     527            if (wParam == 1)
     528                g_fWinTimerTicked = true;
     529            break;
     530    }
     531    return DefWindowProc(hWnd, idMsg, wParam, lParam);
     532}
     533
     534
     535/**
     536 * Initialize the Windows bits.
     537 */
     538static RTEXITCODE CuWinInit(void)
     539{
     540    /* Register the window class: */
     541    static wchar_t s_wszClass[] = L"VBox-ClipUtilClipboardClass";
     542    WNDCLASSW WndCls = {0};
     543    WndCls.style            = CS_NOCLOSE;
     544    WndCls.lpfnWndProc      = CuWinWndProc;
     545    WndCls.cbClsExtra       = 0;
     546    WndCls.cbWndExtra       = 0;
     547    WndCls.hInstance        = (HINSTANCE)GetModuleHandle(NULL);
     548    WndCls.hIcon            = NULL;
     549    WndCls.hCursor          = NULL;
     550    WndCls.hbrBackground    = (HBRUSH)(COLOR_BACKGROUND + 1);
     551    WndCls.lpszMenuName     = NULL;
     552    WndCls.lpszClassName    = s_wszClass;
     553
     554    ATOM uAtomWndClass      = RegisterClassW(&WndCls);
     555    if (!uAtomWndClass)
     556        return RTMsgErrorExitFailure("RegisterClassW failed: %u (%#x)", GetLastError(), GetLastError());
     557
     558    /* Create the clipboard owner window: */
     559    g_hWinWnd = CreateWindowExW(WS_EX_TRANSPARENT,                      /* fExStyle */
     560                                s_wszClass,                             /* pwszClass */
     561                                L"VirtualBox Clipboard Utility",        /* pwszName */
     562                                0,                                      /* fStyle */
     563                                0, 0, 0, 0,                             /* x, y, cx, cy */
     564                                HWND_MESSAGE,                           /* hWndParent */
     565                                NULL,                                   /* hMenu */
     566                                (HINSTANCE)GetModuleHandle(NULL),       /* hinstance */
     567                                NULL);                                  /* pParam */
     568    if (g_hWinWnd == NULL)
     569        return RTMsgErrorExitFailure("CreateWindowExW failed: %u (%#x)", GetLastError(), GetLastError());
     570
     571    return RTEXITCODE_SUCCESS;
     572}
     573
     574/**
    496575 * Terminates the Windows bits.
    497576 */
    498577static RTEXITCODE CuWinTerm(void)
    499578{
    500     if (g_fOpenedClipboard)
    501     {
    502         if (!CloseClipboard())
    503             return RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError());
    504         g_fOpenedClipboard = false;
    505     }
    506     return RTEXITCODE_SUCCESS;
     579    RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
     580    if (g_fWinOpenedClipboard)
     581    {
     582        if (CloseClipboard())
     583            g_fWinOpenedClipboard = false;
     584        else
     585            rcExit = RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError());
     586    }
     587
     588    if (g_hWinWnd != NULL)
     589    {
     590        if (!DestroyWindow(g_hWinWnd))
     591            rcExit = RTMsgErrorExitFailure("DestroyWindow failed: %u (%#x)", GetLastError(), GetLastError());
     592        g_hWinWnd = NULL;
     593    }
     594
     595    return rcExit;
    507596}
    508597
     
    513602static RTEXITCODE WinOpenClipboardIfNecessary(void)
    514603{
    515     if (g_fOpenedClipboard)
     604    if (g_fWinOpenedClipboard)
    516605        return RTEXITCODE_SUCCESS;
    517     if (OpenClipboard(NULL))
    518     {
    519         g_fOpenedClipboard = true;
     606    if (OpenClipboard(g_hWinWnd))
     607    {
     608        if (g_uVerbosity > 0)
     609            RTMsgInfo("Opened the clipboard\n");
     610        g_fWinOpenedClipboard = true;
    520611        return RTEXITCODE_SUCCESS;
    521612    }
     
    585676
    586677
     678#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
     679/**
     680 * Closes the clipboard if open.
     681 */
     682static RTEXITCODE CuCloseClipboard(void)
     683{
     684# if defined(RT_OS_OS2)
     685    if (g_fOs2OpenedClipboard)
     686    {
     687        if (!WinCloseClipbrd(g_hOs2Ab))
     688            return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
     689        g_fOs2OpenedClipboard = false;
     690        if (g_uVerbosity > 0)
     691            RTMsgInfo("Closed the clipboard.\n");
     692    }
     693# else
     694    if (g_fWinOpenedClipboard)
     695    {
     696        if (!CloseClipboard())
     697            return RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError());
     698        g_fWinOpenedClipboard = false;
     699        if (g_uVerbosity > 0)
     700            RTMsgInfo("Closed the clipboard.\n");
     701    }
     702# endif
     703    else if (g_uVerbosity > 0)
     704        RTMsgInfo("No need to close clipboard, not opened.\n");
     705
     706    return RTEXITCODE_SUCCESS;
     707}
     708#endif /* RT_OS_OS2 || RT_OS_WINDOWS */
     709
     710
    587711/**
    588712 * Lists the clipboard format.
     
    597721        uint32_t       idx       = 0;
    598722        ULONG          fFormat   = 0;
    599         while ((fFormat = WinEnumClipbrdFmts(g_hOs2Hab)) != 0)
     723        while ((fFormat = WinEnumClipbrdFmts(g_hOs2Ab)) != 0)
    600724        {
    601725            char szName[256] = {0};
     
    763887    {
    764888        ULONG fFmtInfo = 0;
    765         if (WinQueryClipbrdFmtInfo(g_hOs2Hab, pFmtDesc->fFormat, &fFmtInfo))
    766         {
    767             ULONG uData = WinQueryClipbrdData(g_hOs2Hab, pFmtDesc->fFormat);
     889        if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo))
     890        {
     891            ULONG uData = WinQueryClipbrdData(g_hOs2Ab, pFmtDesc->fFormat);
    768892            if (fFmtInfo & CFI_POINTER)
    769893            {
     
    813937        else
    814938            rcExit = RTMsgErrorExitFailure("WinQueryClipbrdFmtInfo(,%s,) failed: %#x\n",
    815                                            pFmtDesc->pszName, WinGetLastError(g_hOs2Hab));
     939                                           pFmtDesc->pszName, WinGetLastError(g_hOs2Ab));
    816940    }
    817941    return rcExit;
     
    9651089            memcpy(pvShared, pvData, cbData);
    9661090
    967             if (WinSetClipbrdData(g_hOs2Hab, (uintptr_t)pvShared, pFmtDesc->fFormat, CFI_POINTER))
     1091            if (WinSetClipbrdData(g_hOs2Ab, (uintptr_t)pvShared, pFmtDesc->fFormat, CFI_POINTER))
     1092            {
     1093                if (g_uVerbosity > 0)
     1094                    RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, pvShared, cbData);
    9681095                rcExit = RTEXITCODE_SUCCESS;
     1096            }
    9691097            else
    9701098            {
    9711099                rcExit = RTMsgErrorExitFailure("WinSetClipbrdData(,%p LB %#x,%s,) failed: %#x\n",
    972                                                pvShared, cbData, pFmtDesc->pszName, WinGetLastError(g_hOs2Hab));
     1100                                               pvShared, cbData, pFmtDesc->pszName, WinGetLastError(g_hOs2Ab));
    9731101                DosFreeMem(pvShared);
    9741102            }
     
    10241152            if (rcExit == RTEXITCODE_SUCCESS)
    10251153            {
    1026                 if (!SetClipboardData(pFmtDesc->fFormat, hDstData))
     1154                if (SetClipboardData(pFmtDesc->fFormat, hDstData))
     1155                {
     1156                    if (g_uVerbosity > 0)
     1157                        RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, hDstData, cbData + cbZeroPadding);
     1158                }
     1159                else
    10271160                {
    10281161                    rcExit = RTMsgErrorExitFailure("SetClipboardData(%s) failed: %u (%#x)\n",
     
    12261359    {
    12271360        ULONG fFmtInfo = 0;
    1228         if (WinQueryClipbrdFmtInfo(g_hOs2Hab, pFmtDesc->fFormat, &fFmtInfo))
     1361        if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo))
    12291362            rcExit = RTMsgErrorExitFailure("Format '%s' is present");
    12301363    }
     
    12591392    {
    12601393        ULONG fFmtInfo = 0;
    1261         if (WinEmptyClipbrd(g_hOs2Hab))
    1262         {
    1263             WinSetClipbrdOwner(g_hOs2Hab, g_hOs2Wnd); /* Probably unnecessary? */
    1264             WinSetClipbrdOwner(g_hOs2Hab, NULLHANDLE);
     1394        if (WinEmptyClipbrd(g_hOs2Ab))
     1395        {
     1396            WinSetClipbrdOwner(g_hOs2Ab, g_hOs2Wnd); /* Probably unnecessary? */
     1397            WinSetClipbrdOwner(g_hOs2Ab, NULLHANDLE);
    12651398            g_fOs2ClipboardOwner = false;
    12661399        }
    12671400        else
    1268             rcExit = RTMsgErrorExitFailure("WinEmptyClipbrd() failed: %#x\n", WinGetLastError(g_hOs2Hab));
     1401            rcExit = RTMsgErrorExitFailure("WinEmptyClipbrd() failed: %#x\n", WinGetLastError(g_hOs2Ab));
    12691402    }
    12701403    return rcExit;
     
    12861419
    12871420/**
     1421 * Waits/delays at least @a cMsWait milliseconds.
     1422 *
     1423 * @returns Success indicator.
     1424 * @param   cMsWait     Minimum wait/delay time in milliseconds.
     1425 */
     1426static RTEXITCODE DoWait(uint32_t cMsWait)
     1427{
     1428    uint64_t const msStart = RTTimeMilliTS();
     1429    if (g_uVerbosity > 1)
     1430        RTMsgInfo("Waiting %u ms...\n", cMsWait);
     1431
     1432#if defined(RT_OS_OS2)
     1433    /*
     1434     * Arm a timer which will timeout after the desired period and
     1435     * quit when we've dispatched it.
     1436     */
     1437    g_fOs2TimerTicked = false;
     1438    if (WinStartTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/, cMsWait + 1) != 0)
     1439    {
     1440        QMSG Msg;
     1441        while (WinGetMsg(g_hOs2Ab, &Msg, NULL, 0, 0))
     1442        {
     1443            WinDispatchMsg(g_hOs2Ab, &Msg);
     1444            if (g_fOs2TimerTicked || RTTimeMilliTS() - msStart >= cMsWait)
     1445                break;
     1446        }
     1447
     1448        if (!WinStopTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/))
     1449            RTMsgWarning("WinStopTimer failed: %#x", WinGetLastError(g_hOs2Ab));
     1450    }
     1451    else
     1452        return RTMsgErrorExitFailure("WinStartTimer(,,,%u ms) failed: %#x", cMsWait + 1, WinGetLastError(g_hOs2Ab));
     1453
     1454#elif defined(RT_OS_WINDOWS)
     1455    /*
     1456     * Arm a timer which will timeout after the desired period and
     1457     * quit when we've dispatched it.
     1458     */
     1459    g_fWinTimerTicked = false;
     1460    if (SetTimer(g_hWinWnd, 1 /*idEvent*/, cMsWait + 1, NULL /*pfnTimerProc*/) != 0)
     1461    {
     1462        MSG Msg;
     1463        while (GetMessageW(&Msg, NULL, 0, 0))
     1464        {
     1465            TranslateMessage(&Msg);
     1466            DispatchMessageW(&Msg);
     1467            if (g_fWinTimerTicked || RTTimeMilliTS() - msStart >= cMsWait)
     1468                break;
     1469        }
     1470
     1471        if (!KillTimer(g_hWinWnd, 1 /*idEvent*/))
     1472            RTMsgWarning("KillTimer failed: %u (%#x)", GetLastError(), GetLastError());
     1473    }
     1474    else
     1475        return RTMsgErrorExitFailure("SetTimer(,,%u ms,) failed: %u (%#x)", cMsWait + 1, GetLastError(), GetLastError());
     1476
     1477#else
     1478/** @todo X11 needs to run it's message queue too, because if we're offering
     1479 *        things on the "clipboard" we must reply to requests for them.  */
     1480    /*
     1481     * Just a plain simple RTThreadSleep option (will probably not be used in the end):
     1482     */
     1483    for (;;)
     1484    {
     1485        uint64_t cMsElapsed = RTTimeMilliTS() - msStart;
     1486        if (cMsElapsed >= cMsWait)
     1487            break;
     1488        RTThreadSleep(cMsWait - cMsElapsed);
     1489    }
     1490#endif
     1491
     1492    if (g_uVerbosity > 2)
     1493        RTMsgInfo("Done waiting after %u ms.\n", RTTimeMilliTS() - msStart);
     1494    return RTEXITCODE_SUCCESS;
     1495}
     1496
     1497
     1498/**
    12881499 * Display the usage to @a pStrm.
    12891500 */
     
    12921503    RTStrmPrintf(pStrm,
    12931504                 "usage: %s [--get <fmt> [--get ...]] [--get-file <fmt> <file> [--get-file ...]]\n"
    1294                  "       %s [--zap] [--put <fmt> <content> [--put ...]] [--put-file <fmt> <file> [--put-file ...]]\n"
     1505                 "       %s [--zap] [--put <fmt> <content> [--put ...]] [--put-file <fmt> <file> [--put-file ...]] [--wait <ms>]\n"
    12951506                 "       %s [--check <fmt> <expected> [--check ...]] [--check-file <fmt> <file> [--check-file ...]]\n"
    12961507                 "           [--check-no <fmt> [--check-no ...]]\n"
    12971508                 , RTProcShortName(), RTProcShortName(), RTProcShortName());
    12981509    RTStrmPrintf(pStrm, "\n");
    1299     RTStrmPrintf(pStrm, "Options: \n");
     1510    RTStrmPrintf(pStrm, "Actions/Options:\n");
    13001511
    13011512    for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
     
    13161527            case 't':   pszHelp = "Selects the target clipboard."; break;
    13171528#endif
     1529#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
     1530            case 'k':   pszHelp = "Closes the clipboard if open (win,os2)."; break;
     1531#endif
     1532            case 'w':   pszHelp = "Waits a given number of milliseconds before continuing."; break;
    13181533            case 'v':   pszHelp = "More verbose execution."; break;
    13191534            case 'q':   pszHelp = "Quiet execution."; break;
     
    13341549            RTStrmPrintf(pStrm, "  %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
    13351550    }
    1336     RTStrmPrintf(pStrm, "Note! Options are processed in the order they are given.\n");
     1551    RTStrmPrintf(pStrm,
     1552                 "\n"
     1553                 "Note! Options are processed in the order they are given.\n");
    13371554
    13381555    RTStrmPrintf(pStrm, "\nFormats:\n");
     
    13651582    RTEXITCODE rcExit = CuOs2Init();
    13661583#elif defined(RT_OS_WINDOWS)
    1367     RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
     1584    RTEXITCODE rcExit = CuWinInit();
    13681585#else
    13691586    RTEXITCODE rcExit = CuX11Init();
     
    13831600        switch (rc)
    13841601        {
     1602#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
     1603            case 'k':
     1604                rcExit2 = CuCloseClipboard();
     1605                break;
     1606#endif
     1607
    13851608            case 'l':
    13861609                rcExit2 = ListClipboardContent();
     
    15111734#endif
    15121735
     1736            case 'w':
     1737                rcExit2 = DoWait(ValueUnion.u32);
     1738                break;
     1739
    15131740            case 'q':
    15141741                g_uVerbosity = 0;
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette