1. Глава 13: Введение в GTK

В этой главе мы начнем изучение библиотеки GTK (GIMP Toolkit) — одного из самых популярных инструментов для создания графического интерфейса пользователя (GUI) в Linux, включая Raspberry Pi. GTK позволяет разрабатывать интерактивные приложения с окнами, кнопками, текстовыми полями и другими элементами, которые мы видим в современных десктопных программах.

Вы узнаете:

  • Как установить GTK на Raspberry Pi.

  • Основные принципы архитектуры GTK, включая его событийно-ориентированную модель.

  • Что такое GObject и почему он является фундаментом для всех элементов GTK.

  • Как создать простую GTK-программу.

### Установка GTK на Raspberry Pi

Прежде чем мы сможем приступить к программированию графических приложений, нам необходимо установить саму библиотеку GTK и необходимые инструменты для компиляции. Эти инструкции актуальны для операционных систем на базе Debian, таких как Raspberry Pi OS.

Чтобы начать работу с GTK на Raspberry Pi Zero 2 W (или любой другой модели Raspberry Pi с Debian-подобной ОС), установите необходимые пакеты, выполнив следующие команды в терминале:

sudo apt update
sudo apt install libgtk-3-dev
  • sudo apt update: Обновляет список доступных пакетов и их версий из репозиториев. Это всегда хороший первый шаг перед установкой новых пакетов.

  • sudo apt install libgtk-3-dev: Устанавливает пакеты разработчика для GTK 3. Пакет libgtk-3-dev включает в себя заголовочные файлы и библиотеки, необходимые для компиляции программ, использующих GTK.

Проверьте, что у вас установлены следующие утилиты, которые понадобятся для компиляции программ на C:

  • pkg-config: Утилита, которая помогает компилятору найти необходимые пути к заголовочным файлам и библиотекам для GTK. Она автоматизирует процесс указания всех флагов компиляции.

  • gcc или clang: Компилятор языка C. gcc (GNU Compiler Collection) обычно установлен по умолчанию в большинстве дистрибутивов Linux.

Для компиляции программ с GTK:

После написания вашей GTK-программы на C, вы будете компилировать её с помощью следующей команды. Обратите внимание на использование pkg-config, которое значительно упрощает процесс:

gcc main.c -o app `pkg-config --cflags --libs gtk+-3.0`
  • gcc main.c: Указывает компилятору GCC скомпилировать исходный файл main.c.

  • -o app: Создает исполняемый файл с именем app. Вы можете выбрать любое другое имя.

  • ` `pkg-config --cflags --libs gtk+-3.0` `: Это ключевая часть. Команда pkg-config запрашивает у системы все необходимые флаги компилятора (--cflags для заголовочных файлов) и флаги компоновщика (--libs для библиотек), которые нужны для сборки приложения, использующего GTK 3. Результат этой команды (например, -I/usr/include/gtk-3.0 ... -lgtk-3 -lgdk-3 ...) вставляется в команду gcc перед её выполнением.

### Архитектура событий

GTK, как и большинство современных графических библиотек, использует событийно-ориентированную модель программирования. Это означает, что ваш код не выполняет действия линейно от начала до конца, а вместо этого «ждёт» наступления определенных событий и реагирует на них.

  • Событие — это любое действие, которое может произойти в графическом интерфейсе. Примеры событий: клик мышью по кнопке, нажатие клавиши на клавиатуре, изменение размера окна, перемещение курсора и т.д.

  • Сигнал — это механизм GTK, который «испускается» виджетом (элементом GUI) в ответ на определенное событие. Например, кнопка испускает сигнал "clicked" (нажата), когда пользователь по ней кликает.

  • Обработчик события (callback function) — это обычная функция на C, которую вы пишете, и которая будет вызвана GTK, когда соответствующий сигнал будет испущен.

Как это работает:

Вы «подключаете» (connect) ваш обработчик события к сигналу определенного виджета. Когда виджет испускает этот сигнал, GTK автоматически вызывает вашу функцию-обработчик.

Пример подключения обработчика сигнала:

g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), NULL);
  • g_signal_connect: Функция GTK для подключения сигнала к обработчику.

  • button: Указатель на виджет, от которого мы ожидаем сигнал (например, кнопка).

  • "clicked": Имя сигнала, который мы хотим перехватить. Это стандартизированные имена сигналов GTK.

  • G_CALLBACK(on_button_clicked): Макрос G_CALLBACK используется для безопасного приведения типа вашей функции-обработчика (on_button_clicked) к ожидаемому типу указателя функции. on_button_clicked — это имя вашей функции, которая будет вызвана при клике.

  • NULL: Это user_data (пользовательские данные), которые могут быть переданы в функцию-обработчик. Если данные не нужны, передается NULL.

Таким образом, каждый виджет может испускать сигналы, которые мы можем «перехватывать» с помощью функции g_signal_connect, чтобы ваш код мог реагировать на действия пользователя.

### Что такое GObject?

GObject — это не просто библиотека, это целая объектно-ориентированная система, реализованная на языке C. Она является фундаментальной основой GTK и многих других библиотек проекта GNOME. GObject предоставляет C возможности, которые обычно ассоциируются с объектно-ориентированными языками, такими как C++ или Java, но при этом сохраняет низкоуровневый контроль C.

Ключевые возможности GObject:

  • Наследование: Объекты могут наследовать свойства и поведение от родительских объектов. Например, все виджеты GTK наследуются от базового класса GObject.

  • Интерфейсы: Позволяют объектам реализовывать определенные наборы функций, не связываясь напрямую с конкретной иерархией наследования.

  • Сигналы и слоты: Это и есть та самая событийная модель, о которой мы говорили выше. Объекты могут «испускать» сигналы (события), а другие части кода могут «подключаться» к этим сигналам с помощью «слотов» (функций-обработчиков).

  • Свойства (Properties): Позволяют объектам иметь именованные атрибуты, к которым можно получать доступ и устанавливать их значения универсальным способом.

Ключевые понятия и функции GObject:

  • GObject: Это базовый тип всех объектов, построенных с использованием системы GObject, включая все виджеты GTK.

  • g_object_set(): Функция для установки значений свойств объекта.

  • g_object_get(): Функция для получения значений свойств объекта.

  • g_signal_connect(): Как уже было упомянуто, эта функция используется для подключения обработчиков событий (слотов) к сигналам, испускаемым объектами.

  • g_object_unref(): Функция для уменьшения счетчика ссылок на объект GObject. Когда счетчик ссылок достигает нуля, объект автоматически уничтожается. Это часть системы управления памятью GObject.

Понимание GObject критически важно для эффективной работы с GTK, поскольку именно эта система управляет жизненным циклом виджетов, их взаимодействием и свойствами.

### Простой пример GTK-программы

Давайте создадим нашу первую минимальную GTK-программу, которая просто откроет пустое окно с заданным заголовком и размером.

Название файла: simple_gtk_window.c

#include <gtk/gtk.h> // Подключаем основную библиотеку GTK. Этого заголовочного файла достаточно для большинства базовых функций.

// Callback-функция, которая будет вызвана при "активации" приложения.
// 'app' - указатель на объект GtkApplication, представляющий наше приложение.
// 'user_data' - пользовательские данные, которые мы можем передать (в данном случае NULL).
static void on_activate(GtkApplication *app, gpointer user_data) {
    // Создаем новое окно приложения. GtkApplicationWindow является основным окном для приложения.
    // Оно автоматически связывается с GtkApplication, что обеспечивает правильное управление жизненным циклом.
    GtkWidget *window = gtk_application_window_new(app);

    // Устанавливаем заголовок окна.
    // GTK_WINDOW(window) - это макрос приведения типа, который "приводит"
    // общий GtkWidget* к более специфичному GtkWindow*, позволяя использовать функции GtkWindow.
    gtk_window_set_title(GTK_WINDOW(window), "Пример GTK");

    // Устанавливаем размер окна по умолчанию (ширина, высота).
    gtk_window_set_default_size(GTK_WINDOW(window), 200, 100);

    // Показываем все виджеты в окне (само окно и все его дочерние элементы, если они есть).
    // GTK-виджеты не видны по умолчанию; их нужно явно показать.
    gtk_widget_show_all(window);
}

int main(int argc, char **argv) {
    // Создаем новый объект GtkApplication.
    // "com.example.GTKApp" - уникальный ID приложения (рекомендуется использовать обратный домен).
    // G_APPLICATION_FLAGS_NONE - флаги приложения (пока без дополнительных флагов).
    GtkApplication *app = gtk_application_new("com.example.GTKApp", G_APPLICATION_FLAGS_NONE);

    // Подключаем сигнал "activate" к нашей функции on_activate.
    // Сигнал "activate" испускается, когда приложение запускается и готово к отображению своих окон.
    // G_CALLBACK() преобразует указатель на функцию в нужный тип для g_signal_connect.
    g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);

    // Запускаем приложение GTK. Эта функция передает управление циклу обработки событий GTK.
    // Она блокирует выполнение программы до тех пор, пока приложение не завершится.
    int status = g_application_run(G_APPLICATION(app), argc, argv);

    // Уменьшаем счетчик ссылок на объект GtkApplication.
    // Когда счетчик ссылок достигает нуля, объект освобождается.
    // Это важно для предотвращения утечек памяти, так как GtkApplication является GObject.
    g_object_unref(app);

    return status; // Возвращаем код завершения приложения.
}

Компиляция программы:

Сохраните код выше как simple_gtk_window.c и скомпилируйте его, используя pkg-config для автоматического подключения необходимых библиотек и заголовочных файлов:

gcc simple_gtk_window.c -o gtk_example `pkg-config --cflags --libs gtk+-3.0`

Запуск и Ожидаемый Вывод:

После успешной компиляции вы можете запустить программу:

./gtk_example

Ожидаемый результат:

На экране появится простое пустое окно с заголовком «Пример GTK» и размерами примерно 200x100 пикселей. Окно будет активно до тех пор, пока вы его не закроете.