Немного о libunique

Сегодня я хотел бы рассказать об одном из способов создания приложения, имеющего один экземпляр — библиотека LibUnique.

Зачем это нужно?

В моём случае это понадобилось при написании небольшого апплета-календарика для панели tint2. Хотелось, чтобы при клике на часы показывался календарик, при повторном клике — скрывался, как в gnome-panel. Сперва мне мешал тот факт, что в tint2 не было такого функционала. Но это же open source! Вечер на чтение кода, вечер на написание патча… Теперь в tint2 такой функционал есть.

Затем я начал искать, что же может послужить в качестве такого апплета, но не нашёл ничего подходящего. Orage был слишком тяжёлым, zenity --calendar выскакивал по центру экрана, и его нельзя было закрыть повторным щелчком на часы. В итоге я решил написать свой маленький календарик: gsimplecal. Тем более, что хотелось попробовать написать что-нибудь под Linux.

На его примере и рассмотрим использование LibUnique.

Давайте уже писать код


Для начала ссылка на полный листинг.

На самом деле всё очень просто, и суть этого поста скорее в том, чтобы дать знать о существовании такого решения. А с таким знанием потребуется выполнить всего несколько шагов:
  1. Для начала, разумеется, нам потребуется подключить заголовочный файл unique.h.
    #include <unique/unique.h>

  2. Затем, где-то в самом начале функции main, необходимо вызвать функцию unique_app_new, передав ей идентификатор приложения в виде доменного имени, вроде «ru.habrahabr.singletone_app».

    int main(int argc, char *argv[])
    {
        UniqueApp *app;
        app = unique_app_new("org.dmedvinsky.gsimplecal", NULL);


    Если вы хотите использовать собственные команды (из предопределённых есть ACTIVATE, NEW, OPEN, CLOSE), то можно вместо unique_app_new вызвать сразу unique_app_new_with_commands (команды можно добавить и отдельно, вызвав unique_app_add_command).

  3. После этого вызвать функцию unique_app_is_running, и действовать в зависимости от её результата:
    • true: послать уже существующему экземпляру одно из предопределённых или же кастомных сообщений.

          if (unique_app_is_running(app)) {
              unique_app_send_message(app, UNIQUE_CLOSE, NULL);
          }

    • false: программа запущена первый раз; нужно создать наше окно и присоединить к нему обработчик сообщений от libunique.

          else {
              create_main_window();
              unique_app_watch_window(app, GTK_WINDOW(main_window));
              g_signal_connect(app, "message-received", G_CALLBACK(message_received_cb), NULL);
          }
  4. Собственно, написать сам обработчик сообщений.

    static UniqueResponse message_received_cb(UniqueApp *app, UniqueCommand command,
            UniqueMessageData *message, guint time_, gpointer user_data)
    {
        if (command == UNIQUE_CLOSE) {
            gtk_signal_emit_by_name(GTK_OBJECT(main_window), "destroy");
        }

        return UNIQUE_RESPONSE_OK;
    }

  5. Не забыть подчистить за собой.
        g_object_unref(app);

  6. ...
  7. Profit!
+21
27 марта 2010, 21:27
18
damnerd 23,5

комментарии (24)

–13
oe24 #
Суховатый топик, без картинок.
+10
damnerd #
Можно, конечно, устроить tits-эффект, но мне не хотелось бы, чтобы в пост заходили только из-за них самых. Всё-таки, пост рассчитан на сознательных людей, которые знают, на каких сайтах их искать. А тут хотят почитать статейки по программированию.

Если вас интересуют скриншоты вышеназванных программ, они имеются по ссылкам. А по теме поста — libunique — их сделать сложно.
+2
junk #
Интересная статья, спасибо!

Программирование под линукс захватывающая тема.
+1
mechmind #
Передача сообщений идет через d-bus, свой сокет или подсистему gtk? Вопрос совместимости и длины списка зависимостей.
+5
damnerd #
Можно скомпилить с разными бэкендами — bacon или d-bus:

#ifdef HAVE_BACON
      if (strcmp (backend_name, "bacon") == 0)
        backend_gtype = unique_backend_bacon_get_type ();
#endif
#ifdef HAVE_DBUS
      if (strcmp (backend_name, "dbus") == 0)
        backend_gtype = unique_backend_dbus_get_type ();
#endif /* HAVE_DBUS */
#if !defined(HAVE_BACON) && !defined(HAVE_DBUS)
#error Need either bacon or dbus
#endif
0
kastigar #
А какая шина используется? Системная или сессионая? Это про d-bus
0
damnerd #
Сессионная, если не ошибаюсь.
0
morkow #
Что такое BACON?
0
damnerd #
Честно говоря, не знаю, но похоже, что их же система, работает на сокетах:

git.gnome.org/browse/unique/tree/unique/bacon/uniquebackend-bacon.c
+3
flashnik #
А… что мешает использовать стандартные средства создания единственного приложения? Всякие мьютексы и прочее? Или я неправ и такой подход уже устарел?
0
damnerd #
Да, в общем, ничто не мешает. :)
+1
flashnik #
Эм… А зачем тогда эта либа? Какие еще у нее преимущества? Или смысл в том, что новичку не надо разбираться с этим? Ну так в этом случае он в чужие сорцы и не полезет. А если свое пишет — то, по-хорошему, такие вещи можно написать и быстро, и раз на всю жизнь.
+1
bigbes #
А зачем тогда ваш коммент? Какие у него преимущества?
+1
flashnik #
Это был вопрос, какие преимущества еще есть, кроме того, что не надо разбираться самому, как это делать.
–2
damnerd #
Эта либа нужна для того же, для чего все остальные либы. Никто не заставляет Вас ими пользоваться. Можете и виджет календаря свой на libX11 написать. Хотя зачем Вам libX11?..

Какие ещё у неё преимущества?

Почему ещё? Я разве хоть одно назвал?
+3
flashnik #
Одно — очевидно. Не надо разбираться в том, как это делать самому через мьютексы.
Понятно, что каждая либа создается для того, чтобы снять часть рутины (или решение сложной задачи) с плеч других разработчиков.
+1
damnerd #
Именно. В том числе и эта либа.

Понятно, что если Вас не устраивает ситуация с зависимостями, которые придётся тянуть из-за этой либы, то надо придумывать своё решение. Но gtk и dbus у меня и так везде установлены, а либу на 300 КБ мне не жалко поставить.
0
smartly #
если б эта либа хоть бы кросс-платформенная была…
0
zencd #
Странно, но эти две цитаты ниже — от одного и того же человека.

0
zencd #
Это SEGFAULT, не обращайте внимания.
+2
smartly #
if kill $(cat /tmp/traycal.pid) ; then
    echo "closed"
else
    echo $$ >/tmp/traycal.pid
    exec rxvt-unicode -hold +sb -fn 9x15 -geometry 21x9-0-0 -e cal
fi

+1
morkow #
use named mutex и минус одна либа в зависимостях ;)

см. www.ibm.com/developerworks/linux/library/l-ipc2lin3.html

Вообще, я бы однозначно заплюсовал статью про межпроцессовую/межтредовую синхронизацию в линуксе. Вот это реально интересно.
+2
gribozavr #
Named mutes это в Windows. В Linux это SysV семафоры — semget(). И тут остаётся открытым вопрос, как выбрать значение ключа, чтобы не было конфликтов с другими программами. А в предложенном способе эта проблема решена.

0
morkow #
ftok()?

Я же говорю — отдельная статья на эту тему была бы в 100 раз интереснее! :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.