Обработать завершение сессии в Linux

September 12th, 2011 Begemot

Пытаюсь тут портировать на линукс свой менеджер заметок написанный на с++ and wxWidgets. Столкнулся с тем что не могу отследить событие когда пользователь уходит – logout, reboot, shut down. А мне то надо как-то данные сохранять. Под виндой, есть EVT_QUERY_END_SESSION, библиотека ловит виндовое сообщение и сообщает мне о событии, пускай с большими проблемами но что-то сделать можно – сохранить данные, корректно закрыть приложение, заповним положение\размеры окна и мелкие настройки. Под макосом не пробовал, но говорят работает. Под Линуксом нет – в инете полно вопросов, жалоб – но решения нет.

Пытался использовать средства gtk (или posix, или чье это там), типа

#include <stdio.h> // for File I/O #include <signal.h> // for signals #include <unistd.h> // for sleep() void handler(int sig) { /* Just print to stderr and redirect 2>> test.txt in the shell */ fprintf(stderr, "got %d\n", signal); signal(sig, handler); // let signal be caught again } int main() { signal(SIGTERM,handler); signal(SIGINT,handler); signal(SIGQUIT, handler); // Maybe we're getting QUIT first? signal(SIGHUP, handler); // If the terminal closes, we might get this signal(SIGPIPE, handler); // Don't know why we'd get this, but check anyway while(1) // loop forever with most signals trapped, impolitely waiting for a kill -9 ;)

// run }

Не фига, когда руками прибиваю приложение Ctrl+c в терминале или kill … срабатывает, когда вырубаю систему нет, сообщения не получаю. В инете все пишут тоже самое не работает.  Code::Blocks тоже не сохраняет данные, если что-то изменить и сделать logout.  

Получается линукс, или убунта, или гном не дает возможность приложению узнать что сессия завершается и корретно завершить работу, сохранив данные. Вот вам и Linux with a human face 🙁

Есть правда какой-то EggSMClient в виде сорцов, LGPL, его например использует GEdit и молодец, корректно обрабатывает все. Но что-то про EggSMClient ни упоминаний толком, ни документации – особенно ничего не находится, а так разобратся без бутылки не получается. Печально все это 🙁


wxLANGUAGE_ENGLISH != wxLANGUAGE_ENGLISH

September 3rd, 2010 Begemot

Я зол, я дико зол, надо выпустить пар, но постараюсь без крепких выражений.

Прикрутил полгода назад локализацию, уже есть несколько языков – английский, русский, китайский и голландский. Выпускаю новую версию, получаю через день 5 писем. Мол “поставили новую версию, но интерфейс теперь на каком-то странном языке, наверное на голландском, чо делать?”.

Первым ответил что небось сами переключили случайно, потом понимаю что проблема есть, начинаю разбираться. Изучаю свой код ищу где мог ошибиться. Потом оказывается что идентификаторы языков в wxWidgets (wxLANGUAGE_ENGLISH etc) не константы, а просто enum. И недавно туда всунули новый Боснийский язык, причем не в конец, а середину как и положенно по алфовиту, в результате все что ниже его поплыло.

в результате wxLANGUAGE_ENGLISH, хранившиеся в конфиге, вместо 57го стал 58м, а 57м по стал голландский, который оказался у меня тоже. вот такая хрень.

Пока тупо написал workaround – читаю инфорамцию о языке по другому пути в конфиге, в результате у всех заново спросит какой язык они хотят. Но это лучше чем запустить и увидеть голландский.

Но это не решение проблемы, решения я пока особо не вижу, а вы? Как хранить в конфиге информацию о языке?


Сохраняем CF_DIB\CF_DIBV5 в файл

August 27th, 2010 Begemot

Казалось бы простое дело, а убил большую часть дня на это. И то сделал только благодаря помощи одного человека, автора программы для работы с битмапами, иконками и т.д. Приятно разговаривать с человеком который хорошо разбирается в теме:) В итоге узнал очень много сокровенных знаний, которым и буду делится.

Итак у нас есть данные в памяти в форматах CF_DIB/CF_DIBV5 (да я знаю что это форматы буфера обмена!) ну или другими словами у нас есть BITMAPINFO. А мы хотим получить wxBitmap.

Сначала нам нужно получить HBITMAP, который потом уже можно конвертить в wxBitmap используя wxBitmap::SetHBITMAP(да в документации ее нету).

Много писать мне уже сегодня лень, поэтому просто покажу код, там в комментариях описаны все проблемы и решения.

 

        if (BITMAPINFO * bi = static_cast<BITMAPINFO*>(p->data.GetData()))
        {
              /*   там с клипбоардом вообще мрак, MS сам свои спецификации не соблюдает, я тебе уже жаловался, когда в классы всё это барахло заворачивал
                    когда в biCompression указано BI_BITFIELDS за структурой следуют 12 байт маски цветовых каналов, надо на 12 байт смещать начало пиксельного массива
                    1) 12 байт пропускать надо? только если biCompression==BI_BITFIELDS
                    2) по-хорошему, их надо не пропускать, а анализировать и использовать
                    3) работает ли твой код в случае с 8-битным битмапом (т.е. таким, который включает палитру)?
   
                    в третьем с конца - нужно пропустить кол-во элементов палитры
                    так, а как узнать количество элементов палитры?
                    biClrUsed или (1<<biBitCount), если biClrUsed=0  и это делать только если biBitCount <= 8
            */

                int offset = 0;
                if (bi->bmiHeader.biBitCount <= 8)
                {
                    offset = bi->bmiHeader.biClrUsed ? bi->bmiHeader.biClrUsed : (1 << bi->bmiHeader.biBitCount);
                }
                else
                {
                    if (bi->bmiHeader.biCompression == BI_BITFIELDS) offset = 3;
                }

/*
  Правильно использовать independing bitmap - но похоже тут у wxBitmap проблемы, мы получаем правильный hBitmap (видно на экране), но в файле пробелмы - на определенной
  цветности\форматах (тестировалось только для DIB)
  поэтому использую DDB - тут теряется цветность, итоговая цветность не равна оригинальной, а зависит от текущих настроек экрана... но зато работает.
  // (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset  instead of &bi->bmiColors + shift - for support both versions DIB\DIBV5
*/

#if 0
             // using independent bitmap
             char * pBits;
             HBITMAP hBitmap = CreateDIBSection(CreateCompatibleDC(NULL), bi, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
             memcpy(pBits, (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset, abs(bi->bmiHeader.biHeight) * (((bi->bmiHeader.biWidth * bi->bmiHeader.biBitCount + 31) & ~31) / 8));
#else         // use DDB (dependent) - we lost information about color depth - bad way
             
             HBITMAP hBitmap = CreateDIBitmap(GetDC(NULL), &bi->bmiHeader, CBM_INIT, (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset, bi, DIB_RGB_COLORS);
#endif
//           //------------------------------------------------------------------------------------------------------------------
//              { // write to screen to test it
//                  HDC screen = GetDC(NULL);
//                  HDC bitmap = CreateCompatibleDC(screen);
//                  HBITMAP old = (HBITMAP)SelectObject(bitmap, hBitmap);
//                  int w = 0, h = 0, d = 0;
//                  w = bi->bmiHeader.biWidth; h = bi->bmiHeader.biHeight; d = bi->bmiHeader.biBitCount;
//                  BitBlt(screen, 100, 100, w, h, bitmap, 0, 0, SRCCOPY);
//                  SelectObject(bitmap, old);
//                  DeleteDC(bitmap);
//                  ReleaseDC(NULL, screen);
//              }
//             //------------------------------------------------------------------------------------------------------------------

                wxBitmap bitmap;
                bitmap.SetHBITMAP(hBitmap);
                if (bitmap.IsOk())
                {
                    bool res = bitmap.SaveFile(file, BegUtils::DetermineImageType(file));
                    wxASSERT(res);
                }
                else wxASSERT(0);
            }

 

Да p->data.GetData() может содержать либо просто DIB либо 5ю версию.

Если кто знает другое решение, которое позволит обойти проблему и сохранять данные корректно, или кто пофиксит баг в wxBitmap или хотя бы сформулирует и закинет в трекер, пишите:)


wxWidgets определение размера экрана

June 22nd, 2010 Begemot

В wxWidgets есть wxSystemSettings::GetMetric() который, кроме прочего, позволяет узнать разрешение экрана, но к сожалению не правильно 🙁

Проблемы появляются на мультимониторных конфигурациях. Вызов wxSystemSettings::GetMetric(wxSYS_SCREEN_X); дает нам ширину только первого монитора под Windows (сам проверял) и суммарную ширину обоих мониторов под Линуксом (сведенья из инета). Подозреваю что это баг, но разбиратся, формулировать и спорить некогда, поэтому просто написал воркараунд для себя

#ifdef __WIN32__  
    // wxSystemSettings::GetMetric does not support multy-monitor configuration at Windows, and return parameters only for first monitor, but it seems wots fine under Linux
    const int maxWidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
    const int maxHeight = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
#else
    const int maxWidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X);
    const int maxHeight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y);
#endif

Updated:
Эхх стало стыдно за плохой стиль:(

// wxSystemSettings::GetMetric does not support multy-monitor configuration at Windows and return parameters only for first monitor, but it seems works well under Linux
wxSize BegUtils::GetVirtualDisplayResolution()
{
#ifdef __WIN32__  
    return wxSize(::GetSystemMetrics(SM_CXVIRTUALSCREEN), ::GetSystemMetrics(SM_CYVIRTUALSCREEN));
#else
    return wxSize(wxSystemSettings::GetMetricwxSYS_SCREEN_X), wxSystemSettings::GetMetricwxSYS_SCREEN_Y));
#endif
}


...

    const wxSize resolution = BegUtils::GetVirtualDisplayResolution();

QSqlRelationalTableModel – отстой, и другие грабли

May 31st, 2010 Begemot

Не хотел жаловаться, но все таки не выдержал. Да и все-таки стоит немного рассказать о популярном мифе о зрелости и вылизанности Qt.

Может конечно мне просто не повезло и я попал в самую, самую дремучую чащу. Но судя по всему QSqlRelationalTableModel более чем глючный класс. “Now I do not use QSqlRelationalTable model at all. Useless class. To many errors… and not so good design also.” Qt bugtracker, Дык вот, я полностью согласен. В попытках его освоить нашел 3 или  4 бага, потерял кучу дней. Научившись обходить часть из них и модицифировав базу данных под его “требования”, уперся в очередную проблему. Выжимка из моих проблем с QSqlRelationalTableModel  и ссылки на баги на форуме1.

В итоге я нашел путь как обойти и эту проблему, но там сильно кривой код для того, чтобы его оставлять в продакшене. В ближайших планах отказ от QSqlRelationalTableModel и реализация отношений самостоятельно.

Сегодня утром пишу код для работы с датами, выбрал формат для хранения в бд второй, для отображения на экране. В итоге не работает. Думал опять код работы с бд глючит. Оказалось нет, правду видимо мне сказали что у меня карма плохая поэтому я на баги нарываюсь – из 7 встроенных в Qt форматов перевода времени в строку и обратно, я выбрал Qt::SystemLocaleLongDate – единственный который не работает:) Добавил баг в багтрекер, можно поздравить с почином. Но на фоне QSqlRelationalTableModel, это уже мелочи – просто выбрал другой формат времени, ерунда:)

Ну и раз уж я недавно жаловался на то что взял wxCalendarCtrl и там было пару мелких проблем, то справедливости ради обязан заметить, что QCalendarWidgets – тоже не идеален, сегодня прикручивал. Есть проблемы как в отображении, так и в поведении, и я бы не сказал что совсем мелкие… Но сейчас я уже почему-то спокойнее к этому отношусь 🙂


Бег по минному полю к Qt

May 16th, 2010 Begemot

Последнее время что-то все чаще и чаще при разработки с wxWidgets приходит в голову аналогия с бегом по минному полю – никогда не знаешь что рванет, но каждый шаг этого ожидаешь.

Обновился в транке то последней ревизии – бах. Взял компонент который раньше не юзал – бах, бах. , попробовал под мак компилять – бах, бах, бах. И это не образно, это реально то на что я попал за неделю. Каждая проблема это время, нервы, разочарование.

Сейчас повторяется недавняя история, когда я помучавшись с wxWidgets плюнул и написал программу на Objective-C\Cocoa. Потратив пару дней на написание прототипа гуи, вернее самых зачатков прототипа – для того что бы проверить архитеетуру\логику работы. GUI в принципе не самое простое, но и ничего сверх естественного.

Дальше начались проблемы, посидев полдня с напильником под виндой, перейдя с с wxNotebook на AUI версию – удалось добиться приемлемого поведения под виндой. Пошел под Mac OS – там все было достаточно печально, тот же wxAuiNoteebook ведет себя совсем не так как надо (и это я не использовал никаких продвинутых возможностей, типа открепления\перемещения вкладок, просто их показ).

Конечно, я уверен что не решаемых проблем особо нет – можно было опять же достать напильник (что в принципе нормально), изменить подход к построению гуи, вернутся на родной Noteebook, попробовать второй, третий вариант и так далее.

Но что-то я уже был злой и раздосадованный, человек (можно сказать апологет wxWidgets!) стал убеждать, что для моей задачи лучше все-таки использовать Qt, заказчик был изначально больше за Qt чем за wxWidgets…

В общем теперь я очень срочно учу Qt 🙂 Пока остановились на варианте – пишу прототип на нем и потом мы смотрим, сравниваем  и выбираем, но что-то мне подсказывает, что я знаю чем все закончится 🙂


Я негодую, SetMinSize

December 23rd, 2009 Begemot

Заметил что после последнего апдейта транка, баг в программе – оказывается изменилась логика библиотеки. Я запоминаю и восстанавливаю при старте программы размеры главного окна, баг был в том, что после запуска не удавалось уменьшить размер окна – сделать его меньше чем оно было в прошлый раз. Оказалось wxFrame::Create(parent, id, _caption, pos, size, style); использует переданные size and pos для вызова SetMinSize(), там даже есть коммент

   // assume the user doesn’t want this window to shrink beneath its initial
    // size, this worked like this in wxWidgets 2.8 and before and generally
    // often makes sense for child windows (for top level ones it definitely
    // does not as the user should be able to resize the window)
    //
    // note that we can’t use IsTopLevel() from ctor

Хорошо хоть в той ревизии с которой я последний раз делал public release этого еще не было. Логика сего действия мне абсолютно непонятна…

В общем потихоньку переезжаю на тройку, тестирую, выгребаю несовместимости.

Posted in Использование | Tags: ,
Comments Off on Я негодую, SetMinSize


Фокусы с cfg, разбор полетов

December 13th, 2009 Begemot

Продолжая тему с потерей конфига. Кратко напомню суть проблемы – периодически при загрузки компа конфиг файлы программы – “плохие”. Либа выдает ошибку конвертации в юникод и данных нет. Как показали эксперименты, файл есть, примерно нужного размера но заполнен нулевыми символами (???). Причем конфига у меня два, первый открыт все время работы программы и закрывается при выходе\получении сообщения о выключении компа. Второй открывается локально при выходе\завершении и тут же закрывается. Проблема с обоими.

Я тут повозился полдня, подебажил сорцы, по перезагружался много раз – результаты такие.

Версия Сергея:

Теоретически, при любом сбое файл настроек должен ведь сохраниться. Я вижу пока только одну возможность – в момент flush когда он уже пометил старый файл пустым и не успел записать данные.

Неправильная, там внутрях используется wxTempFile, который обрабатывает это дело умно – пишет данные в временный файл, убивает оригинал, переименовывает временный в оригинал.

 

Eсли просто выключать комп все ок.

Если инициировать процесс выключения (в обработчике я пишу в конфиг и удаляю его), и сразу же нажать рессет. То в 60% случаев – проблема проявляется. Причем устойчивое появление проблемы мне удалось наблюдать только если программа стоит в автозагрузке, если же нет – то вроде все ок (это мне вообще мозг ломает…).

Сорцы смотрел, дебажил, эксперементировал. Почему такое может быть понять не могу – в мою картину мира не вписывается, то что трабла происходит только при ресете  – ну что может там такого происходить???  Судя по сорцам – все просто и правильно. А то что мне не удается это поймать когда запускаю руками программу, а не из автозагрузки – вообще путает все карты.

Уже не знаю что и думать. Наверное буду писать систему дублирования 🙁


Иногда вещи не такие как ты думаешь

December 8th, 2009 Begemot

Как думаете что будет в str?

wxConfigBase * config = wxConfigBase::Get();

config->Write(_T("/Options/teeestg"), _T("-\\&-"));

wxString str = config->Read(_T("/Options/teeestg"), str);

неправильно, будет “-&-”. Тоже самое и при использовании % вместо &. Проблема оказывается в том что при чтении из конфига автоматически раскрываются переменные окружения.

if you have the following in your config file:

  # config file for my program
  UserData = $HOME/data

  # the following syntax is valud only under Windows
  UserData = %windir%\\data.dat
 

the call to config->Read("UserData") will return something like "/home/zeitlin/data" if you’re lucky enough to run a Linux system 😉

Although this feature is very useful, it may be annoying if you read a value which containts ‘$’ or ‘%’ symbols (% is used for environment variables expansion under Windows) which are not used for environment variable expansion. In this situation you may call SetExpandEnvVars(false) just before reading this value and SetExpandEnvVars(true) just after. Another solution would be to prefix the offending symbols with a backslash.

Чтобы это отключить, необходимо вызвать config->SetExpandEnvVars(false);

С одной стороны вроде все описано и логично, но с другой я все таки не понимаю почему в сочетании \$, код библиотеки удаляет первый слеш, вот сижу думаю это баг и надо его запостить в трекер или все-таки нет.

Думаете надуманная ситуация? Ничего подобного, баг нашел юзер у которого имя пользователя в винде начинается с $ и это вызвано корпоративными стандартами, в этом случае путь к файлам пользователя получается “C:\Documents and

Settings\$xxx\Application Data\xxx\” ну и сразу после установки, программа не может открыть базу данных…

Posted in Программирование | Tags:
Comments Off on Иногда вещи не такие как ты думаешь


Проблемы с потерей конфиг файла

November 3rd, 2009 Begemot

Хочу пожаловаться, может кто поможет советом. Есть программы на написанные на wxWidgets и MFC, стартуют вместе с виндой и завершаются с ней же. При старте, как и рекомендует хелп делаю

wxFileConfig * config = new wxFileConfig(wxGetApp().GetAppName(), wxGetApp().GetVendorName(), dataPath + wxT("clipdiary.cfg")); // will be deleted by wxWidgets automatically

wxConfigBase::Set(config);

Потом периодически читаю\пишу, иногда, но не всегда делаю Flush(true). При выходе из программы и\или при получении сообщения EVT_QUERY_END_SESSION пишу кое-какие данные в цфг, делаю Flush(true). В самом конце делаю делаю вот такое

int MyApp::OnExit()
{
    // Баг, столкнулся с тем что почему-то не пишет в файл надо или flush или удалять указатель (при удалении он сам все пишет)
    // по идее библиотека должна сама удалять объек. но не удаляет. так что будем вручную.
    delete wxConfigBase::Set(NULL);       // Flush and delete config

    return wxApp::OnExit();
}

А проблема в том, что периодически (редко) при запуске программы – конфиг файл оказывается пустой, в смысле или совсем пустой или отсутствующий, не могу точно сказать. Программа при загрузке выдает сообщение ошибку wxWidgets, что-то типа “ошибка конвертации в юникод” точно не помню. Скорее всего проблема происходит при выключении компьютера, наверняка при каком-то “плохом” завершении работы. Раньше об этом писали пользователи, жаловались на то что пропадают настройки… Недавно поймал такое у себя – висели три мои программы на mfc, wxWidgets trunk и  2.8.10 – две последние потеряли все настройки, мфц’шная нет. Вот думаю куда копать и что делать, уже думал делать дублирование настроек во втором файле, но какой-то это странный путь 🙂 Может кто знает как сделать правильно?

 

Кстати, вот инструкция как реализовать ручную загрузку функций из DLL или как в программе использовать функции из Vista API?