Pull to refresh

Создание GTK+ апплета для LXPanel

Reading time 9 min
Views 5.4K
Статья о том, как написать свой апплет для LXPanel. Разработчики до сих пор не написали документации, поэтому учиться приходится по исходникам, которые наоборот богаты комментариями.
Я решил разобрать наглядно один пример и самый простой способ сборки в готовый *.so плагин.




Сборка идёт под Ubuntu, необходимы минимальные знания по С и GTK+.

Подготовка


  1. Получить исходники lxpanel:
    echo "deb-src http://mirror.yandex.ru/ubuntu maverick main restricted universe multiverse" >> /etc/apt/sources.list
    apt-get update && apt-get source lxpanel
    cd lxpanel-0.5.6

  2. Установить необходимые для сборки пакеты:
    apt-get build-dep lxpanel

  3. Проверка:
    LIBS="-lX11" ./configure --prefix=/usr --with-plugins=cpufreq
    make
    ls src/plugins/cpufreq/.libs/cpufreq.so


В качестве жертвы выбран плагин cpufreq, позже расскажу зачем. А писать будем плагин для переключения performance<->ondemand режимов процессора.

Написание


vim src/plugins/cpufreq/my_plugin.c


Собственно, весь код с комментариями:
  1. /**
  2.  * My_plugin
  3.  * Habrahabr
  4.  */
  5.  
  6. #include <sys/types.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <glib/gi18n.h>
  10.  
  11. #include <string.h>
  12.  
  13. #include "panel.h"
  14. #include "misc.h"
  15. #include "plugin.h"
  16.  
  17. //Для сравнения строк
  18. #define STREQV(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)
  19.  
  20. //Объявляем структуру нашего плагина
  21. typedef struct {
  22.     //Виджет, который будет содержать всебе иконку и менюшку
  23.     GtkWidget *namew;
  24. } my_plugin;
  25.  
  26. //Создаём объект
  27. static my_plugin *mp;
  28.  
  29. //структура Plugin объявляется в src/plugin.h
  30. //*mp_p - теперь класса Plugin
  31. //Который как минимум задаёт родительский контейнер mp_p->pwid
  32. //Наш виджет mp->namew в него "вставлен"
  33. //А значок, к примеру, содержится в mp->namew
  34. //Долго кощей смерть прятал
  35. static Plugin *mp_p;
  36.  
  37. //Назавание говорит за себя
  38. static char get_governor() {
  39.     FILE *fpipe;
  40.     const char governor[16];
  41.     char line[16];
  42.  
  43.     //Проблема с распознованием вывода cpufreq-info
  44.     //Скрипт /usr/local/bin/cpufreq-gov возвращает название текущего режима
  45.     fpipe = (FILE*)popen("/usr/local/bin/cpufreq-gov","r");
  46.     fgets(line, sizeof(line), fpipe);
  47.     pclose(fpipe);
  48.  
  49.     return line;
  50. }
  51.  
  52. //Назавание говорит за себя
  53. static char get_frequency() {
  54.     FILE *fpipe;
  55.     const char governor[16];
  56.     char line[16];
  57.  
  58.     //Проблема с распознованием вывода cpufreq-info
  59.     //Скрипт /usr/local/bin/cpufreq-gov возвращает название текущего режима
  60.     fpipe = (FILE*)popen("cpufreq-info -f -m","r");
  61.     fgets(line, sizeof(line), fpipe);
  62.     pclose(fpipe);
  63.     line[strlen(line)-1]='\0';
  64.  
  65.     return line;
  66. }
  67.  
  68. //Сделаем функцию обновления иконки при смене режима
  69. static void refresh_icon() {
  70.     //Узнаём текущий режим
  71.     const char governor[16];
  72.     char line[16] = get_governor();
  73.     sprintf(governor,"%s",line);
  74.  
  75.     //mp_p->pwid - родительский контейнер типа GTKWidget, котрый содержит в себе mp->namew
  76.     //Вынимаем виджет mp->namew и меняем значёк
  77.     gtk_container_remove(GTK_CONTAINER(mp_p->pwid), mp->namew);
  78.  
  79.     //Если режим "ondemand"
  80.     if(STREQV(governor,"ondemand"))
  81.     {
  82.         //Один значёк
  83.         mp->namew = gtk_image_new_from_file("/usr/share/lxpanel/images/cpufreq_ond.png");
  84.     }
  85.     else
  86.     {
  87.         //Иначе другой
  88.         mp->namew = gtk_image_new_from_file("/usr/share/lxpanel/images/cpufreq_perf.png");
  89.     }
  90.     //Вставляем виджет mp->namew обратно в родительский контейнер mp_p->pwid
  91.     gtk_container_add(GTK_CONTAINER(mp_p->pwid), mp->namew);
  92.     gtk_widget_show_all(mp_p->pwid);
  93. }
  94.  
  95. //Функция смены режима
  96. static void set_governor(GtkWidget *widget, char *p) {
  97.     const char exec[32];
  98.     sprintf(exec, "cpufreq-set -g %s", p);
  99.     system(exec);
  100.     refresh_icon();
  101. }
  102.  
  103. //Создаём меню
  104. static GtkWidget *mp_menu() {
  105.     GtkMenu *menu = gtk_menu_new();
  106.     GSList *group = NULL;
  107.     GtkWidget *menuitem, *radio1, *radio2;
  108.  
  109.     FILE *fpipe;
  110.     const char governor[16];
  111.     const char frequency[16];
  112.     char line[16];
  113.  
  114.     line = get_governor();
  115.     sprintf(governor,"%s",line);
  116.  
  117.     line = get_frequency();
  118.     sprintf(frequency,"%s",line);
  119.  
  120.     //1) Добавляем в меню текущую частоту, делаем пункт неактивным
  121.     menuitem = gtk_menu_item_new_with_label(frequency);
  122.     gtk_menu_append (menu, menuitem);
  123.     gtk_widget_set_sensitive(menuitem, FALSE);
  124.     gtk_widget_show (menuitem);
  125.  
  126.     //2) Добавляем разделитель
  127.     menuitem = gtk_separator_menu_item_new();
  128.     gtk_menu_append (menu, menuitem);
  129.     gtk_widget_show (menuitem);
  130.  
  131.     //3) 2 радио-кнопки для переключением между режимами
  132.     radio1 = gtk_radio_menu_item_new_with_label (group, "Экономия");
  133.     group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (radio1));
  134.     if(STREQV(governor,"ondemand"))
  135.     {
  136.         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (radio1), TRUE);
  137.     }
  138.     gtk_menu_append (menu, radio1);
  139.     gtk_widget_show (radio1);
  140.  
  141.     //4) Вторая кнопка
  142.     radio2 = gtk_radio_menu_item_new_with_label (group, "Производительность");
  143.     if(STREQV(governor,"performance"))
  144.     {
  145.         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (radio2), TRUE);
  146.     }
  147.     gtk_menu_append (menu, radio2);
  148.     gtk_widget_show (radio2);
  149.  
  150.     //При нажатии по радиокнопкам происходит переключение режимов
  151.     g_signal_connect(G_OBJECT(radio1), "toggled", G_CALLBACK(set_governor), "ondemand");
  152.     g_signal_connect(G_OBJECT(radio2), "toggled", G_CALLBACK(set_governor), "performance");
  153.  
  154.     return menu;
  155. }
  156.  
  157. //Обработчик события нажатия по иконке плагина
  158. static  gboolean clicked( GtkWidget *widget, GdkEventButton* evt, Plugin* plugin) {
  159.     gtk_menu_popup(mp_menu(), NULL, NULL, NULL, NULL, evt->button, evt->time);
  160.     return TRUE;
  161. }
  162.  
  163. //Собственно конструктор объекта
  164. static int constructor(Plugin *p) {
  165.     //Cоздаём объект
  166.     mechanism_mp = g_new0(my_plugin, 1);
  167.     p->priv = mechanism_mp;
  168.  
  169.     //Делаем родительский виджет чувствительным (следим за событиями)
  170.     //И убираем с него оформление окна
  171.     p->pwid = gtk_event_box_new();
  172.     GTK_WIDGET_SET_FLAGS(p->pwid, GTK_NO_WINDOW );
  173.     gtk_container_set_border_width( GTK_CONTAINER(p->pwid), 0 );
  174.  
  175.     //Устанавливаем первоначальный значёк
  176.     refresh_icon();
  177.  
  178.     //При нажатии срабатывает функция-обработчик clicked()
  179.     g_signal_connect (G_OBJECT (p->pwid), "button_press_event", G_CALLBACK (clicked), (gpointer) p);
  180.  
  181.     gtk_widget_show(mechanism_mp->namew);
  182. }
  183.  
  184. //Деструктор
  185. static void destructor(Plugin *p) {
  186.     my_plugin *mechanism_mp = (my_plugin *)p->priv;
  187.     g_free(mechanism_mp);
  188. }
  189.  
  190. //А здесь указываем информацию о нашем плагине
  191. PluginClass mp_plugin_class = {
  192.     PLUGINCLASS_VERSIONING,
  193.  
  194.     type : "my_plugin",
  195.     name : N_("My super plugin"),
  196.     version: "1.0",
  197.     description : N_("Habrahabr"),
  198.  
  199.     constructor : constructor,
  200.     destructor  : destructor,
  201.     //В нашем плагине не используются,
  202.     //но можно назначить обработчики для конфигурации, сохранения конфигурации
  203.     //И перерисовки виджета, при изменении конфигурации
  204.     config : NULL,
  205.     save : NULL,
  206.     panel_configuration_changed : NULL
  207. };
  208.  


Компиляция


Можно добавить наш плагин в скрипты сборки, но в этом нет необходимости и это крайне муторно. Поэтому просто возьмём плагин-жертву cpufreq и заменим его исходник src/plugins/cpufreq/cpufreq.c нашим.
Собираем:
LIBS="-lX11" ./configure --prefix=/usr --with-plugins=cpufreq
make

Готовый *.so лежит в src/plugins/cpufreq/.libs/, его нужно закинуть в папку с остальными плагинами, только под другим именем, например /usr/lib/lxpanel/plugins/my_plugin.so

Содержимое /usr/local/bin/cpufreq-gov:
  1. #!/bin/sh
  2.  
  3. echo `cpufreq-info -p | awk '{print $3}'`


2 иконки в /usr/share/lxpanel/images/:


В заключение


Для большей ясности советую почитать файлы, они богаты комментариями.
  • src/plugin.c
  • src/plugin.h
  • src/panel.c
  • src/panel.h


Удачи! Исходник примера: my_plugin.c
Tags:
Hubs:
+43
Comments 21
Comments Comments 21

Articles