#!/system/php/bin/php <?php global $old_mtime, $mtime, $window; function add_me_to_tray($my_name, $my_icon, $tooltip, $pixbuf=true) { global $mtime; // Если директории в формате /run/user/CURRENT_USER/systray/PID не существует, то создаем ее if (!is_dir($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid())) { mkdir($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid(),0700,true); } if ($pixbuf==true) { file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/icon_pixbuf", $my_icon); } else { file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/icon_name", $my_icon); } touch($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/.updated"); file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/title", $my_name); file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/tooltip", $tooltip); $mtime = filectime($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/.updated"); } function get_action() { if (!is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/action")) {return false;} $action=file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/action"); return trim($action); } function check_for_actions($window) { global $old_mtime, $mtime, $context_menu; $old_mtime = $mtime; $mtime = filectime($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/.updated"); if ($mtime!=$old_mtime) { // Если файл изменился - значит произошло событие if (get_action()=="Activate") { // И это событие - активация // Все последующее - для активации окна на передний план $window->set_visible(true); $window->show(); $window->activate_focus(); $window->present_with_time($time); $window->show(); } if (get_action()=="ContextMenu") { // Создаем простейшее контекстное меню. Реализовано в форме окна, поэтому не закрывается по потере фокуса $display = new GdkDisplay(); global $x,$y; $x = $display->get_mouse_positionX(); // Возле курсора мыши, само собой $y = $display->get_mouse_positionY(); unset($display); $context_menu = new GtkWindow(); $context_menu->set_type_hint(2); $context_menu->set_size_request(200, 200); $context_menu->set_decorated(false); $vbox = new GtkBox(GtkOrientation::VERTICAL); $vbox->set_border_width(0); $o_button = GtkButton::new_with_label("Open"); // Менюшка Open будет открывать наше окно $o_button->connect("clicked", function() {global $x,$y, $window, $context_menu;$time=time();$window->show();$window->present_with_time($time);$window->activate_focus();$context_menu->destroy();}); $vbox->add($o_button); $o_button->show(); $button = GtkButton::new_with_label("Close"); // Менюшка Close будет его закрывать. $button->connect("clicked", function() {Gtk::main_quit();}); $vbox->add($button); $button->show(); $context_menu->add($vbox); $context_menu->show(); $vbox->show(); $context_menu->move($x+22,$y+22); // Не на иконке же рисовать меню - нарисуем чуть правее и ниже $context_menu->activate_focus(); } } clearstatcache(); // Пых кеширует date modify по умолчанию, надо чистить } Gtk::init(); $window = new GtkWindow(); $window->set_size_request(250, 250); $window->set_title("SystemTrayExaple"); $window->set_decorated(true); $vbox = new GtkBox(GtkOrientation::VERTICAL); $vbox->set_border_width(1); $label = new GtkLabel("Ну типа контент"); $vbox->add($label); $button = GtkButton::new_with_label("Кнобка !"); $vbox->add($button); $window->add($vbox); $window->connect("destroy", function() { Gtk::main_quit(); }); $window->show_all(); // Добавляем нашу программку в трей add_me_to_tray("Test App", "viber", "Test App в трее", false); // Добавляем таймер для периодического чека Gtk::timeout_add(150, function () {global $window; check_for_actions($window);return true;}); $window->set_visible(true); Gtk::main();
<?php global $tray_box, $old_time, $mtime; function rmrf($dir) { $files = array_diff(scandir($dir), array('.','..')); foreach ($files as $file) { (is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file"); } return rmdir($dir); } function check_tray() { global $tray_box, $old_time, $mtime, $item_button; // Сперва получаем список всех директорий, и определяем какая из них отсутствует в /proc, чтобы прибить трей $scan = scandir($_SERVER['XDG_RUNTIME_DIR']."/systray/"); foreach ($scan as $process) { if (is_numeric($process)) { if (is_dir("/proc/$process")) { // Процесс все еще существует, можем смотреть, было ли изменено состояние $old_mtime[$process] = $mtime[$process]; $mtime[$process] = filectime($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/.updated"); if ($mtime[$process]!=$old_mtime[$process]) { // И только если наше гостевое приложение обновило свои данные в системном трее - тогда обновляем и мы if (is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/icon_name")) {$icon = file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/icon_name");} if (is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/title")) {$title = file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/title");} if (is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/tooltip")) {$tooltip = file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/tooltip");} if (!isset($item_button[$process])) { // Если процесс новенький, то создаем под него кнопку $item_button[$process] = new GtkButton(); $item_button[$process]->connect("button-press-event",function($button, $event) {process_click($button, $event);}); } else { // Если процесс уже есть в трее, то удаляем внутренности кнопки, оставляя все остальное. GObject не умеет в удаление событий, а это уменьшит утечку памяти foreach ($item_button[$process]->get_children() as &$value) { $value->destroy(); } } $item_box[$process] = new GtkBox(GtkOrientation::HORIZONTAL); $image = GtkImage::new_from_icon_name("$icon", 5); $image->set_pixel_size(22); $item_box[$process]->add($image); $image->show(); $item_button[$process]->add($item_box[$process]); $item_button[$process]->set_name($process); $item_button[$process]->set_relief(GtkReliefStyle::NONE); $item_button[$process]->set_has_tooltip(true); $item_button[$process]->set_tooltip_text($tooltip); $tray_box->add($item_button[$process]); $item_box[$process]->show(); $item_button[$process]->show(); $tray_box->show(); } } else { // Процесс более не найден - прибиваем его rmrf($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process); $item_button[$process]->destroy(); } } } } function process_click($item, $event) { $process = $item->get_name(); if ($event->button->button == 1) {file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/action", "Activate");} // Нажали левую кнопку - записали Activate if ($event->button->button == 3) {file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/action", "ContextMenu");} // Нажали правую - записали ContextMenu touch($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/.updated"); return false; } Gtk::init(); function GtkWindowDestroy($widget=NULL, $event=NULL) { Gtk::main_quit(); } if (!is_dir($_SERVER['XDG_RUNTIME_DIR']."/systray")) {mkdir($_SERVER['XDG_RUNTIME_DIR']."/systray",0700);} // Если мы запустились первыми в системе, и директории нет - создаем $win = new GtkWindow(); $win->set_default_size(300, 200); $win->connect("destroy", "GtkWindowDestroy"); $tray_box = new GtkBox(GtkOrientation::HORIZONTAL); $win->add($tray_box); $win->show_all(); Gtk::timeout_add(150, function () {check_tray();return true;}); // Держим палец на трее каждые 150 мс, используя ГыТыКа таймаут. Без паники, процессорное время тратится разве что на ls и stat. Gtk::main();
Послесловие.
В чем преимущество этой спецификации по сравнению с уже существующими реализациями xembed и SNI (dbus)?