0012 타일한 창이 원래 크기로 돌아오지 않음

From 카카오톡 on Wine
Jump to navigationJump to search

0011과 한 칸 옆에 있는 같은 종류의 구멍입니다. Super + 오른쪽으로 창을 화면 반쪽에 붙였다가 Super + 아래로 풀면, GNOME은 타일을 푼 것으로 알고 창은 타일된 크기 그대로 남습니다. 그 크기가 화면 가장자리에 딱 붙어 있어서, 크기를 조절하려고 변을 잡으려 해도 잡을 자리가 없습니다. 포인터 모양도 안 바뀝니다.

추적을 켜고 대화 창 하나에 Super + 오른쪽, Super + 아래를 차례로 눌러보면 이렇게 나옵니다.

xdg_toplevel_handle_configure hwnd=0x900e8 570x960,0      ← 평소
xdg_toplevel_handle_configure hwnd=0x900e8 960x1048,0x4   ← Super + 오른쪽
xdg_toplevel_handle_configure hwnd=0x900e8 0x0,0          ← Super + 아래
xdg_toplevel_handle_configure hwnd=0x900e8 960x1048,0     ← 그 뒤로 계속

0x4가 WAYLAND_SURFACE_CONFIG_STATE_TILED입니다. 타일에서 나올 때 컴포지터가 보내는 것은 0x0에 상태 없음, "크기는 네가 정해라"입니다. 그런데 창이 아는 크기는 타일이 강요한 960x1048 하나뿐이라 그 값을 도로 내밉니다.

드라이버가 타일 상태를 모르는 것은 아닙니다. 크기를 얼마나 엄격하게 지킬지 고를 때는 봅니다.

/* The Wayland maximized and fullscreen states are very strict about
 * surface size, so don't let the application override it. The tiled state
 * is not as strict, but it indicates a strong size preference, so try to
 * respect it. */
if (state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED |
             WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN |
             WAYLAND_SURFACE_CONFIG_STATE_TILED))
{
    flags |= SWP_NOSENDCHANGING;
}

그런데 WS_MAXIMIZE를 켜고 끄는 줄은 최대화만 봅니다.

if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE)
    && !(state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN))
    NtUserSetWindowLong(hwnd, GWL_STYLE, style ^ WS_MAXIMIZE, FALSE);

이게 0011의 수법을 그대로 쓸 수 없는 이유입니다. 타일로 들어갈 때 Win32 쪽에는 아무 표시도 남지 않습니다. 실제로 타일된 창의 스타일을 찍어보면 0x960f0000으로, WS_MAXIMIZE(0x01000000)가 없습니다. 저장된 normal placement가 없으니 SC_RESTORE를 보내도 되돌릴 것이 없습니다.

맞는 비유는 최대화가 아니라 윈도의 스냅입니다. 스냅된 창도 WS_MAXIMIZE를 달지 않고, 스냅 직전 크기는 창이 아니라 셸이 기억합니다. 여기서도 같이 했습니다. 기억할 곳이 없으니 드라이버가 기억합니다.

BOOL tiled;
RECT tiled_rect;

타일 상태가 들어오면 그 직전의 창 사각형을 적어두고, 타일 상태가 나가면 돌려줍니다.

else if ((state & WAYLAND_SURFACE_CONFIG_STATE_TILED) && !data->tiled)
{
    data->tiled = TRUE;
    data->tiled_rect = data->rects.window;
}
else if (!(state & WAYLAND_SURFACE_CONFIG_STATE_TILED) && data->tiled)
{
    data->tiled = FALSE;
    restoring_from_tiled = !(style & (WS_MAXIMIZE | WS_MINIMIZE)) &&
                           !IsRectEmpty(&data->tiled_rect);
}

적어둔 것은 이미 창 사각형이라 map_rect_from_surface를 다시 통과시키면 안 됩니다. 매핑이 끝난 뒤에 통째로 갈아끼웁니다. 쓰이는 것은 크기뿐입니다. 다른 모든 configure와 마찬가지로 아래에서 SWP_NOMOVE가 붙기 때문입니다.

SetRect(&rect, 0, 0, width, height);
rect = map_rect_from_surface(surface, rect);
OffsetRect(&rect, data->rects.window.left, data->rects.window.top);

if (restoring_from_tiled)
{
    TRACE("hwnd=%p restoring from tiled\n", hwnd);
    rect = data->tiled_rect;
}

순서가 중요합니다. 갈아끼우기는 SWP_NOSIZE를 붙일지 정하는 줄보다 앞에 있어야 합니다. 타일 해제 configure는 크기가 0x0이라 그대로 두면 빈 사각형이 되고, SWP_NOSIZE가 붙어 아무것도 안 하게 됩니다.

최대화와 전체화면은 0011이 맡으므로 여기서는 비켜섭니다. 컴포지터가 최대화된 창을 네 변 모두 타일됐다고 부를 수도 있어서, 그 경우 최대화된 사각형을 "돌아갈 크기"로 적어두지 않도록 기억을 지웁니다.

if (state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED |
             WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN))
{
    data->tiled = FALSE;
    SetRectEmpty(&data->tiled_rect);
}

0011과 마찬가지로 최소화된 창은 건드리지 않습니다.

크기는 돌아오지만 위치는 돌아오지 않습니다. 타일을 풀면 창은 타일돼 있던 화면 가장자리에 붙은 채로 원래 크기를 되찾습니다. 이건 더 고칠 수 없습니다. xdg-shell에는 창이 좌표를 넣어 스스로 움직이는 요청이 없기 때문입니다. 드라이버에서 toplevel을 움직이는 호출은 xdg_toplevel_move 하나뿐인데, 사용자가 제목 표시줄을 눌러 시작하는 대화형 드래그라서 좌석과 버튼 시리얼이 있어야 합니다. 좌표를 넣는 set_position은 서브서피스에만 있고 toplevel에는 없습니다. 창을 어디에 놓을지는 컴포지터가 정합니다. Win32 쪽 사각형만 옛 위치로 옮기면 화면에 보이는 자리는 그대로인데 내부 좌표만 어긋나서 마우스가 엉뚱한 곳을 가리키게 됩니다. 타일 전 위치를 기억해줘야 할 쪽은 뮤터입니다.