GUI для php, або схрещуємо написане розширення з скриншотером

Мова в даній статті піде, про практичне застосування розширення gtkPHP7, написане нами раніше в даній статті, і srcphp(скріншотера на php) вище написане цієї статті. З моменту публікації статті, де ми писали розширення gtkPHP7 минуло кілька днів. І я думав як побудувати цю бібліотеку, що б вона була в дусі php. Простий і зручною у використанні, а так само могла задовольнити (поки тільки мої) потреби в ній. За подробицями прошу під кат…


Сьогодні ми торкнемося кілька тем:
1. Принцип роботи розширення
2. Технічна сторона рішення
3. Інструменти дебага розширення

Принцип роботи розширення.
Головною метою було зручність використання, і як ми всі знаємо php працює в одному потоці, є звичайно розширення, які дозволяють нам створювати потоки, і працювати з ними, але в разі використання потоків, народжуються складності пов'язані з управлінням ними. Та й в принципі, поки в цьому немає необхідності. З цього для мене пріоритетом стала прямолінійність логіки роботи, тобто якщо ми викликаємо метод класу
$result = $gtk->alert("Привіт хабрахабр!");
var_dump($result);

то ми отримаємо такий результат:

і при натискання на кнопку ok

тобто Концепція під стати PHP, запуск->виконання будь любо послідовних дій->завершення скрипта.
Технічна сторона питання
Я вирішив розбити розширення: на головну частину (main.cpp), модулі, кожен з яких реалізують той чи інший віджет для використання в php… Для цього я створив папку src/ де лежить власне код, і всі вихідні коди переніс в нову директорію. Коли исходники перенесені, ми повинні поправити файл Makefile, наступним чином:
SOURCES = $(wildcard src/*.cpp)

вказавши де лежать файли з розширення .cpp
Далі наведу приклад класу реалізує вікно preview:
той же скрін і в шапці
Файл previewWindow.h
class GtkPhpPreviewWindow {

private:
Gtk::Window *mainWindow = nullptr; // покажчик на наше вікно

protected:
int statusUpload = 0; // прапор натискання кнопки upload
int statusCancel = 0; // прапор натискання на cancel

public:
std::string preview(char *fileSrc); // метод реалізує рэндэр вікна
void uploadClick(); // callback функції
void cancelClick();
std::string getStatusUpload(); // геттер в принципі зайвий
};


Далі, загляним в сам файл, а саме розберемо preview
listing preview
/**
* run preview window
*/
std::string GtkPhpPreviewWindow::preview(char *fileSrc) {
int argc = 0;
char **argv = NULL;

auto app = Gtk::Application::create(argc, argv, "org.gtkmm.gtkphp7");
auto refBuilder = Gtk::Builder::create();

try {
refBuilder->add_from_file("picview.glade");
} catch (const Glib::FileError &ex) {
std::cerr << "FileError: " << ex.what() << std::endl;
} catch (const Gtk::BuilderError &ex) {
std::cerr << "BuilderError: " << ex.what() << std::endl;
}

refBuilder->get_widget("window1",mainWindow);

Gtk::Image *image = nullptr;
refBuilder->get_widget("preview", image);

Gtk::Button *buttonUpload = nullptr;
refBuilder->get_widget("upload", buttonUpload);

buttonUpload->signal_clicked().connect(
sigc::mem_fun(*this, &GtkPhpPreviewWindow::uploadClick)
);

Gtk::Button *buttonCancel = nullptr;
refBuilder->get_widget("cancel", buttonCancel);

buttonCancel->signal_clicked().connect(
sigc::mem_fun(*this, &GtkPhpPreviewWindow::cancelClick)
);

image->set(fileSrc);
app->run(*mainWindow);
return getStatusUpload();
}


Метод виявився досить довгим, з цього спробуємо розглянути лише ключові моменти.
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.gtkphp7");
auto refBuilder = Gtk::Builder::create();
refBuilder->add_from_file("picview.glade");

Цими трьома рядками ми створюємо, інстанси програми, і builder (щось на кшталт фабрики в php). І подгружаем файл picview.glade…
Так для полегшення створення форм, я використовував glade — редактор форм для gtk.
preview in glade

Створивши файл, що описує положення елементів(або іншими словами layouts), у форматі xml. Ми просто подгружаем його, і беремо потрібні нам елементи:
Gtk::Button *buttonCancel = nullptr;
refBuilder->get_widget("cancel", buttonCancel); // отримуємо кнопку

buttonCancel->signal_clicked().connect(
sigc::mem_fun(*this, &GtkPhpPreviewWindow::cancelClick) // обробляємо клік на кнопку
);

У cancelClick:
/**
* callback cancel button
*/
void GtkPhpPreviewWindow::cancelClick() {
statusCancel = 1;
delete mainWindow;
}

Ми видаляємо mainWindow і ставимо status кнопки cancel як натиснута.
Останнім штрихом запускаємо додаток(в даному випадку відображаємо віконце):
app->run(*mainWindow);

У цей момент, создаеться «цикл» який тримає вікно відкритим і недає виконуватися далі. Але в той момент коли ми клікаємо по кнопці, ми видаляємо mainWindow, і програма переходить в метод:
/**
* get status upload
* @return
*/
std::string GtkPhpPreviewWindow::getStatusUpload() {
if (statusUpload == 1) {
return "upload";
} else {
return "cancel";
}
}

який повертає рядок вказує на те, що було натиснуто.
Інструменти дебага
Напевно кожен «C» програміст знає що таке gdb, але не кожен програміст знає про нього, хоча він буває дуже корисний, навіть якщо ви не займаєтеся розробкою на «C». Наприклад, я як то їм дізнався чому apache давав segfault і генерував core dump…
І так gdb — gnu debuger дебагер для c і c++, вміє багато чого: встановлювати точки зупинки, читати дампи пам'яті, будувати stack trace. Так от, уявіть, що ваше розширення падає з segfault а точніше php(так як розширення підключено до інтерпретатору). Начебто все компилировалось і повинно працювати. Але ні…
Насамперед нам необхідно перекомпілювати наш проект, з опцією -g. Для цього я поправив Makefile
COMPILER = g++ -g
 

І скомпілював з даною опцією. Після чого, був написаний php скрипт, який викликав core dump, і запущений під gdb:
$ gdb php
 
$ /.. висновок gdb ../
 
(gdb) run ./test.php # так ми запускаємо наш скрипт
 
$ .... тут десь стався coredump
 
(gdb) bt # викликаємо backtrace який і аналізуємо
 

Таким чином, gdb нам показує помилку яка сталася, і backtrace за якою можна точно визначити в чому проблема (щонайменше мені поки проект невеликий)
висновки
Після налагодження, розширення я інтегрував gui в скріншотер
новий код
#!/usr/bin/php
<?php
require_once('classis/autoload.php');

class screenShoter {

protected $_nameScreenshot = null;
protected $_config = null;
protected $_gtk = null;

public function __construct()
{
$this->_gtk = new GtkPhp();
$request = new Request();
if(isset($argv[1]) && $argv[1] == '--getToken') {
echo $request->getOauthLink();die;
}

$home = $_SERVER['HOME'];
$this->_config = include($home . '/.config/scrphp/config.php');
$this->_nameScreenshot = date('Y_m_d_G_i_s_') . 'screen.png';
$this->scrot();
}

public function scrot() {
$this->_gtk->alert("Привіт, для того щоб зробити скріншот натисніть Ok і виберіть область, на екрані");
system('scrot -s /tmp/'.$this->_nameScreenshot);
$resultPrev = $this->_gtk->preview('/tmp/'.$this->_nameScreenshot);
if($resultPrev == 'upload') {
$this->upload();
} else {
$this->scrot();
}
}

public function upload() {
$request = new Request();
$result = $request
->setToken($this->_config['token'])
->setFileNameOnDisk($this->_nameScreenshot)
->setPathToFile('/tmp/'.$this->_nameScreenshot)
->upload()
->publicateFile();
$url = $result['public_url'];
$this->_gtk->alert("Посилання на скрін:".$url);
}

}

new screenShoter();



Цей скрипт працює в три кроки:
1. Алерт який виводить підказку як зробити скріншот

2. Після вибору ділянки і створення скріншоту, відкривається preview

3. Після натискання на кнопку upload появляється alert з посиланням


І на закінчення:
Посилання на репозиторії:
1. gtkPHP7
2. srcphp скріншотер
Залежності:
1. gtkmm3
2. gtk3
3. glade
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.