21 сентября 2011 г.

Скрытие текстового меню gtk-приложений


На скриншоте обычный текстовый редактор, на первый взгляд. Однако если приглядеться:заголовок окна и под ним сразу панель инструментов с иконками. На самом деле там, как почти у любого приложения, должно быть текстовое меню с обычными пунктами типа «Файл», «Правка», «Вид», «Справка».
Такое меню иногда можно скрыть средствами самой программы, например, умеет так Firefox, и Nautilus Elementary. Но можно заставить так делать все программы (правда только написанные на gtk). Ведь насколько часто нужно это самое меню? По-моему, довольно редко, у многих программ.
Однако, для этого пришлось выполнить ряд нетривиальных для меня действий, впрочем, при желании всё по деталям можно найти в интернетах. И для арчеводов привычная поблажка — в AUR'е есть уже готовый пакет gtk2-libwinmenu (спасибо тебе, добрый человек, который это сделал).
Ну а для всех остальных дальнейшие действия.

Создаём файл main.c со следующим содержанием (за это тоже спасибо доброму анонимусу):

/*
gcc -shared  `pkg-config gtk+-x11-2.0 --cflags --libs` -o libwinmenu.so main.c
*/

#include 
#include 

#define _gtk_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID
#define _gtk_marshal_NONE__NONE _gtk_marshal_VOID__VOID

static GObject* (*old_gtk_menu_bar_constructor)(GType type, guint n_construct_properties, GObjectConstructParam *construct_params) = NULL;
static GObject* (*old_gtk_window_constructor)(GType type, guint n_construct_properties, GObjectConstructParam *construct_params) = NULL;
static void     (*old_widget_show) (GtkWidget * widget) = NULL;

static void toggle_menu_bar (GtkWidget *widget, gpointer user_data)
{
   GtkWidget * wdg = GTK_WIDGET(user_data);
   GTK_WIDGET_GET_CLASS(wdg)->show = old_widget_show;
   if (GTK_WIDGET_VISIBLE(wdg))
      gtk_widget_hide(wdg);
   else
      gtk_widget_show(wdg);
}

static void anchor_event (GtkWidget *widget, GtkWidget *previous_toplevel, gpointer   user_data)
{
   GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
   if (GTK_WIDGET_TOPLEVEL (toplevel)) {
      if (previous_toplevel) 
          g_signal_handlers_disconnect_by_func(previous_toplevel, G_CALLBACK(toggle_menu_bar), widget);
      g_signal_connect (toplevel, "toggle-menu-bar", G_CALLBACK(toggle_menu_bar), widget);
    }
}

static void show (GtkWidget *widget)
{
   if (GTK_IS_MENU_BAR(widget))
      return;
   old_widget_show(widget);
}

GObject* new_gtk_menu_bar_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params)
{
   GObject * ret = old_gtk_menu_bar_constructor(type,n_construct_properties,construct_params);
   g_signal_connect (ret, "hierarchy-changed", G_CALLBACK(anchor_event), NULL);
   g_signal_connect (ret, "can_activate_accel", G_CALLBACK(gtk_true), NULL);

   return ret;
}

G_MODULE_EXPORT void
gtk_module_init (gint * argc, gchar *** argv)
{
   GtkWidget *fc;
   GObjectClass *klass;
   const gchar *app_whitelist = "gnome-terminal, gimp, mousepad";

   if (strstr (app_whitelist, g_get_prgname()) != NULL)
 return;

   g_signal_new ("toggle-menu-bar",
                 GTK_TYPE_WINDOW,
                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                 0,
                 NULL, NULL,
                 _gtk_marshal_VOID__VOID,
                 G_TYPE_NONE,
                 0);

   fc = gtk_menu_bar_new();
   klass = GTK_MENU_BAR_GET_CLASS(fc);
   old_gtk_menu_bar_constructor = klass->constructor;
   klass->constructor = new_gtk_menu_bar_constructor;
   old_widget_show = GTK_WIDGET_CLASS(klass)->show;
   GTK_WIDGET_CLASS(klass)->show = show;

   fc = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   klass = GTK_WINDOW_GET_CLASS(fc);

   gtk_binding_entry_add_signal (gtk_binding_set_by_class (klass), GDK_m, GDK_CONTROL_MASK,
                                "toggle-menu-bar", 0);
   gtk_binding_entry_add_signal (gtk_binding_set_by_class (klass), GDK_m, GDK_MOD1_MASK,
                                "toggle-menu-bar", 0);
}
Так как не уверена, что текст в блоге отобразится полностью адекватно, вот тут вроде оригинальный пастебин, а тут резервный на всякий пожарный.
Затем находясь в директории с этим файлом выполняем команду:
gcc -shared `pkg-config gtk+-x11-2.0 --cflags --libs` -fPIC -o libwinmenu.so main.c
После этого в той же директории появляется файл libwinmenu.so Его нужно положить в папку с модулями GTK:
sudo cp libwinmenu.so /usr/lib/gtk-2.0/modules/
Не помню, получаются ли у файла сразу правильные права, поэтому пусть будет:
sudo chmod a+x /usr/lib/gtk-2.0/modules/libwinmenu.so

Дальнейшие действия нужно уже выполнить и в случае, если пакет устанавливался из AUR'а.
Вообще писали, что строку GTK_MODULE=winmenu нужно поместить в файл ~/.gnomerc, но это если среда Gnome. А вообще вроде как подойдёт любая автозагрузка (кто-то писал, что обычный файл автозагрузки openbox прекрасно подходит, но у меня вот не заработало). Мой вариант: строка export GTK_MODULES=winmenu && перед строкой загрузки оконного менеджера в файле ~/.xinitrc.
Если не получается, можно просто запустить из консоли export GTK_MODULES=winmenu && gedit (ну или с какой-нибудь другой программой на gtk2), и если всё в порядке, то проблема только в том, куда прописывать эту строку.
Теперь приложения на gtk2 запускаются без текстового меню. Чтобы вернуть его, нужно нажать Alt+M или Ctrl+M, их же повторно, чтобы снова скрыть меню. Если нужно другое сочетание, нужно менять изначальный файл — строки в самом конце: GDK_m означает клавишу M, GDK_CONTROL_MASK — это Ctrl, ну и GDK_MOD1_MASK — Alt, как не сложно догадаться.
Для приложений на gtk3, конечно, не работает.




5 комментариев:

  1. Для меня основное приложение это браузер, поэтому на меню внимание мало обращаю, у google с этим все нормально. Но пост интересный, это и на ubuntu пойдет?

    И для блогер, чтоб код нормально отобразился и полностью есть например этот сайт

    http://www.elliotswan.com/postable/

    трансформирует в unicode вроде и потом нормально отображается

    ОтветитьУдалить
  2. У меня тоже браузер — в фф всё средствами самой программы скрывается. у меня только тулбар основной и табы, сама прога всегда запускается на первом рабочем столе, на весь экран и без рамки окна.
    В убунте это работать должно, только надо угадать с тем, куда вот это GTK_MODULES вставить потом. Потому что там не из .xinitrc.

    Да нет, с юникодом всё там в порядке и так. Там вон длинные строки куда-то за боковую панель уплыли, а переносы сами не расставляются, видимо, потому что я в тег pre это всё запихала для сохранения всех табуляций.

    ОтветитьУдалить
  3. Интересный патч. А нельзя ли этот libwinmenu.so выложить уже скомпиленный? (Что-то я никак зависимости "побороть" не могу.) Спасибо!

    ОтветитьУдалить
  4. Александр, а попробуйте: http://rghost.ru/23303871
    Я только не знаю, работать ему непоборенные зависимости не помешают ли?

    ОтветитьУдалить
  5. Спасибо! Работает! :)
    Правда, на thunar не действует. Жаль. А так - прикольно. Спасибо, ещё раз! ;)

    ОтветитьУдалить