LINUX.ORG.RU

Лишний ConfigureNotify с бредовыми данными

 


0

3

Если менять размер окну то сначала приходит ConfigureNotify с x=1, y=24, потом 2xConfigureNotify с нормальными данными:

        {
        case ConfigureNotify :
            printf("%d, %d : %u, %u\n",
                    event.xconfigure.x, event.xconfigure.y,
                    event.xconfigure.width, event.xconfigure.height);
            break;
        }
# Двигаю окно
353, 100 : 791, 600
363, 113 : 791, 600
# Меняю ширину окна на один пиксель (курсором)
1, 24 : 791, 600
363, 113 : 791, 600
363, 113 : 791, 600

Откуда вообще берется такая фигня с x=1 и y=24? Мало того, если я спрашиваю XGetWindowAttributes то там x и y вообще либо всегда 100, 100, либо пресловутые 1,24.

sudo cast Zubok.

★★★★★

Откуда вообще берется такая фигня с x=1 и y=24?

Очевидно, берётся она от X-сервера. А в нём она берётся из-за того, что кто-то окно туда сдвинул, а потом сдвинул в другое место, пока никто не заметил. Обычно этот кто-то — window manager.

Если ты хочешь «адекватности» в смысле минимально необходимого числа событий, чтобы сразу р-раз и понятно было, брось эти попытки. Поведение сильно разнится в зависимости от использованной DE. Например, Enlightment окошки Expose'ит не целиком, как остальные WM, а полосками, в несколько заходов.

i-rinat ★★★★★
()

А ты выведи еще вместе с координатами значение флага send_event, который идет в событии ConfigureNotify. Вот первое сообщение наверняка будет с флагом false (то есть от сервера), а два следующих, скорее всего, будут с флагом true (то есть от WM).

Для того, чтобы понять, почему такие координаты, надо прочесть ICCCM (4.1.5. Configuring the Window, 4.2.3. Window Move, 4.2.4. Window Resize) и для уточнений — EWMH.

Немного обгоню чтение и скажу, что 1, 24 - это координаты относительно parent window, а не root window. Именно эти координаты передает по правилам сервер (send_event=false).

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

Блин, как-то до меня не дошло, что это координаты от угла заголовка окна. Догадался бы - понял что это виноват менеджер.

Я так понял что имеет смысл

if (event.xconfigure.send_event)
{ /* На случай если есть WM */
   tx = event.xconfigure.x;
   ty = event.xconfigure.y;
}
else
{ /* На случай если нет WM */
   XTranslateCoordinates(..., x, y, &nx, &ny, ...)
   tx = nx;
   ty = ny;
}

Просто игнор сообщения с send_event=false я так понимаю не комильфо.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от Zubok

Большое спасибо!

Если кто-то будет потом вставать на эти же грабли, сделал так, работает как надо:

    if (event->xconfigure.send_event)
    { /* Window manager's message,
         see https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5 */
        if ((event->xconfigure.x != wdata->widget.position.x) ||
            (event->xconfigure.y != wdata->widget.position.y))
        {
            data.extra = 0;
            data.x     = event->xconfigure.x;
            data.y     = event->xconfigure.y;
            SmlHandlerProcess(index, SML_EVENT_SYSTEM_WIDGET_MOVE, &data);

            return;
        }
    }
    else
    { /* Direct message from X-server */
        i32      cx, cy;
        Window   child;

        XTranslateCoordinates(
            Smilo()->screen.xdisplay, wdata->xwindow,
            XRootWindow(Smilo()->screen.xdisplay, Smilo()->screen.ascreen),
            event->xconfigure.x, event->xconfigure.y, &cx, &cy, &child);

        cx -= event->xconfigure.x;
        cy -= event->xconfigure.y;

        if ((cx != wdata->widget.position.x) ||
            (cy != wdata->widget.position.y))
        {
            data.extra = 0;
            data.x     = cx;
            data.y     = cy;
            SmlHandlerProcess(index, SML_EVENT_SYSTEM_WIDGET_MOVE, &data);

            return;
        }
    }

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

Ну, да. Что-то вроде этого.

В GTK примерно так и сделано - каждый раз на !send_event вызывается XTranslateCoordinates. (см. https://github.com/GNOME/gtk/blob/master/gdk/x11/gdkdisplay-x11.c#L810)

Еще можно сделать более умную процедуру сжатия сообщений ConfigureNotify, чтобы XTranslateCoordinates попусту не дергать каждый раз, так как это roundtrip чистой воды - запрос каждый раз уходит на сервер и ожидается ответ. Подобный механизм в qt сделан (см. http://code.metager.de/source/xref/lib/qt/src/gui/kernel/qapplication_x11.cpp...).

Еще на расширение XSync глянь.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от PPP328

Нашел весьма любопытный разговор Oven Taylor и Keith Packard по поводу XTranslateCoordinates.

https://mail.gnome.org/archives/gtk-devel-list/2001-November/msg00440.html

Тут Пакард считает, что XTranslateCoordinates вообще не нужен и что можно использовать последнее запомненное значение координат окна из синтетического сообщения ConfigureNotify. Он говорит, что практически все WM все равно после реального ConfigureNotify отсылают синтетический, откуда можно получить координату без преобразований. Также он утверждает, что в Xt он так и сделал. Я глянул код - и действтительно:

            case ConfigureNotify:
                if (w->core.window != event->xconfigure.window)
                    return;  /* in case of SubstructureNotify */
#define NEQ(x)  ( w->core.x != event->xconfigure.x )
                if( NEQ(width) || NEQ(height) || NEQ(border_width) ) {
                        sizechanged = TRUE;
#undef NEQ
                        w->core.width = event->xconfigure.width;
                        w->core.height = event->xconfigure.height;
                        w->core.border_width = event->xconfigure.border_width;
                }
                if (event->xany.send_event /* ICCCM compliant synthetic ev */
                    /* || w->shell.override_redirect */
                    || w->shell.client_specified & _XtShellNotReparented)
                {
                    w->core.x = event->xconfigure.x;
                    w->core.y = event->xconfigure.y;
                    w->shell.client_specified |= _XtShellPositionValid;
                }
                else w->shell.client_specified &= ~_XtShellPositionValid;
                if (XtIsWMShell(wid) && !wmshell->wm.wait_for_wm) {
                    /* Consider trusting the wm again */
                    register struct _OldXSizeHints *hintp
                        = &wmshell->wm.size_hints;
#define EQ(x) (hintp->x == w->core.x)
                    if (EQ(x) && EQ(y) && EQ(width) && EQ(height)) {
                        wmshell->wm.wait_for_wm = TRUE;
                    }
#undef EQ
                }
                break;

Кстати, очень похоже на код Qt, но надо на него подробнее взглянуть. Я полагаю, что в Qt тоже полагаются на значения от оконного менеджера (trust). А в Gtk, видимо, посчитали иначе.

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

Он говорит, что практически все WM все равно после реального ConfigureNotify отсылают синтетический, откуда можно получить координату без преобразований.

Ну да, поэтому это *практически* будет работать. За исключением тех случаев когда wm не будет отсылать свое сообщение. Ну или wmа вообще не будет. Или к нам НЕ придет сообщение. Скажем в сети потерялось. Игнорить все !send? Опять же идиотизм.

https://github.com/GNOME/gtk/blob/master/gdk/x11/gdkdisplay-x11.c#L826

И не вычитают размер рамки? Жизнь - боль, ибо синтетика посылает позицию от угла рамки, а реал при транслейте - внутренний top-left. В QT хотя бы ее добавляют http://code.metager.de/source/xref/lib/qt/src/gui/kernel/qapplication_x11.cpp...

The only effect is that the application is slightly behind the server when resizing occurs, but this is benign as the synthetic event will follow shortly.

Ога, а потом интерфейсы на 8 гигах памяти тормозят.

But I've had trouble finding things that all window managers do, even if they _are_ required by the ICCCM.

Собственно ЧИТД.

(trust)

Не помню, кто сказал, но «never trust any standart». Даже если в стандарте что-то там написано можно вообразить сотни вариантов когда это не будет работать. Отсуюда тонны багов типа «переключил монитор - окно переместилось за пределы экрана»

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

И не вычитают размер рамки? Жизнь - боль, ибо синтетика посылает позицию от угла рамки, а реал при транслейте - внутренний top-left.

Это противоречит EWMH (Window Geometry). Обратить внимание на различия client window (это само окно приложения) и frame window (окно-рамка с декорациями):

Window manager implementors should refer to the ICCCM for definitive specifications of how to handle MapRequest and ConfigureRequest events. However, since these aspects of the ICCCM are easily misread, this document offers the following clarifications:

When generating synthetic ConfigureNotify events, the position given MUST be the top-left corner of the client window in relation to the origin of the root window (i.e., ignoring win_gravity) (ICCCM Version 2.0, §4.2.3)

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 2)
Ответ на: комментарий от PPP328

Ну да, поэтому это *практически* будет работать. За исключением тех случаев когда wm не будет отсылать свое сообщение. Ну или wm вообще не будет. Или к нам НЕ придет сообщение. Скажем в сети потерялось. Игнорить все !send? Опять же идиотизм.

Нет, они этот вопрос обсуждают. Хотя я вот в коде Xt этого не вижу (потому что не знаю его):

I believe most window managers *will* send the synthetic event, but that it will always come after the real event. Xt allows for the event to be missing by calling TranslateCoordinates in case the event doesn't arrive.

Вот в Qt есть XTranslateCoordinates и они, похоже, следуют этой же логике. Комментарий в коде в этом месте:

        if (!isCPos) {
            // we didn't get an updated position of the toplevel.
            // either we haven't moved or there is a bug in the window manager.
            // anyway, let's query the position to be certain.
            int x, y;
            Window child;
            XTranslateCoordinates(X11->display, internalWinId(),
                                  QApplication::desktop()->screen(d->xinfo.screen())->internalWinId(),
                                  0, 0, &x, &y, &child);
            newCPos.rx() = x;
            newCPos.ry() = y;
        }

Если сжатие событий (опциональное оно там, судя по коду - if (d->extra->compress_events) {) выключено, то получаем классическую схему, где XTranslateCoordinates вызывается каждый раз по приходу события от сервера.

Если сжатие включено, то у них появляется возможность «проглотить» промежуточные ConfigureNotify от сервера. Причем серверное ConfigureNotify игнорируется в случае, если в очереди есть хоть одно синтетическое ConfigureNotify, причем даже не последнее. Даже последовательность synthetic->synthetic->real, получается, не вызовет XTranslateCoordinates на последнее событие, так как isCPos будет true.

UPD: Хотя... надо понять, они сканируют события от старых к новым или наоборот. Тогда, может быть, наоборот, видят, что последнее событие - синтетическое, т. е. real-> synthetic->synthetic

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 2)
Ответ на: комментарий от Zubok

Они скипают XTranslateCoordinates чтобы много раз вызвать Peek? А Peek разве не тыкает сервер? Шило на мыло?

Это противоречит EWMH (Window Geometry).

Да, был не прав, перепроверил. Ставлю XMove(50, 150), получаю 51, 174. Спасибо, укажу это в документации. Хз, как лучше с точки зрения пользователя фреймворка это сделать - если вызывать Move(0,0), то чего ждет пользователь - что frame или client будет передвинуто на 0,0? А когда приходят координаты, то опять же, ожидается frame или client?

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

Они скипают XTranslateCoordinates чтобы много раз вызвать Peek?

Нет, много раз вызвать XCheckTypedWindowEvent(), которая ищет в очереди событий нужные, удаляет их и возвращает их.

А Peek разве не тыкает сервер? Шило на мыло?

Нет, не тыкает. Это функция xlib работает на стороне клиента с очередью событий, которые уже пришли от сервера, но еще не обработаны. Очередь находится на стороне клиента. И XCheckTypedWindowEvent() тоже, кстати, на стороне клиента. Никаких запросов таких нет, roundtrips тоже нет, соответственно.

Они скипают XTranslateCoordinates чтобы много раз вызвать Peek?

Они скипают ConfigureNotify, и еще часто этот трюк применяется к Expose. Если начал резко дергать окно туда-сюда, то сервер тебе пришлет кучу событий ConfigureNotify (или Expose). Если выполнять их по мере прихода, то все промежуточные перерисовки будут выполняться шлейфом. Вот ты остановился, а окно все вслед за твоими изменениями перерисовывается или передает шлейфы новых координат. Суть в том, что, промежуточные события тупо удаляются, а берется только последнее. В данном случае последнее ConfigureNotify.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 3)
Ответ на: комментарий от Zubok

Суть в том, что, промежуточные события тупо удаляются, а берется только последнее. В данном случае последнее ConfigureNotify.

Не ну я понимаю, что такой трюк полезен с Expose когда `if (event.xexpose.count > 0) return;`, но для Configure... Они не пробовали хоть чуть-чуть соптимизировать обработчик этого события? Или поступить как посутпают некоторые wm? В смысле когда окно становится засветленным\затемненным, по диагоналям рисуются линии, а по центру табличка с размером окна.

Вот ты остановился, а окно все вслед за твоими изменениями перерисовывается

Только если происходит что-то похожее на разрыв реалтайма в эмбеддед - если обработчик события наговнокожен на пару секунд исполнения. У меня сейчас есть тестовое приложение, которое в окне 800х600 на старом процессоре (T440) рисует много текстур (без видеокарты) с fps 100 и разрыва нет - когда есть разрыв, то окно закрывается не сразу, а сейчас его нет и все завершается мгновенно.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

Если у тебя быстрая машина и очень быстрые алгоритмы, то у тебя и очередь сообщений не будет успевать накапливаться сильно. А если перерисовка медленная и обширная, то могут и накапливаться.

Они не пробовали хоть чуть-чуть соптимизировать обработчик этого события? Или поступить как посутпают некоторые wm?

GTK и Qt перерисовывают, когда к ним приходит событие Expose. Это не они должны оптимизировать, а оконный менеджер. Он просто не дает команду на изменение размера окна, пока ты рамку окна не отпустил. (как в twm, например). Приложения получат просто одно сообщение. А если оконный менеджер динамически растягивает окна с динамической перерисовкой и масштабированием всех виджетов, то у тебя от сервера шквал сообщений ConfigureNotify и Expose попрет.

В Qt вот есть сжатие на уровне обработчика события (опциональное и я не знаю, когда его включают или выключают). Новое сообщение он все равно сразу обработает, а потом только посмотрит, а есть ли в очереди еще сообщения. Если ты все успел обработать, то и на обработку только одно новое пойдет.

postEvent() posts the event on a queue for later dispatch. The next time Qt's main event loop runs, it dispatches all posted events, with some optimization. For example, if there are several resize events, they are compressed into one. The same applies to paint events: QWidget::update() calls postEvent(), which eliminates flickering and increases speed by avoiding multiple repaints.

В GTK вот не вижу подобного с ConfigureNotify. Но я не знаю кода GTK. Там может быть оптимизация как раз уровнем выше. То есть все события где-то могут записываться, а потом уже решается, что и когда отрисовывать, отбрасывать промежуточные уровни или нет. Но это не особо принципиально, на каком уровне делать. Может, и не делается вовсе.

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

Он просто не дает команду на изменение размера окна, пока ты рамку окна не отпустил. (как в twm, например).

Плохо, очень плохо. На ресайз обычно стоит перерасчет положения виджетов окна. Это что получается, что по время ресайза у твм они не будут пересчитываться?

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от Zubok

Какой пздц, слов нет. Ресйз чаще всего нужен чтобы резиновые интерфейсы потянуть чтобы кнопки на какое-то расстояние раздвинуть или поле до каких-то пределов увеличить. Или чтобы word-wrap не срабатывал. А тут такой подвох.

PPP328 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.