Hacker-friendly Metacity window selector, v0.0.1
- From: Jan Nieuwenhuizen <janneke gnu org>
- To: gtk-app-devel-list gnome org
- Subject: Hacker-friendly Metacity window selector, v0.0.1
- Date: Thu, 27 May 2004 16:22:50 +0200
The title is just a tiny bit misleading. As suggested by Rob Adams
http://bugzilla.gnome.org/show_bug.cgi?id=140925
I wrote a simple window list popup for metacity, see below (please
bear with me, my first contact with gtk+).
I'd want to ask you how I use EWHM/libwnck/Metacity to make a
keybinding that pops-up my window list. This should be possible, see
the bug report discussion, but I cannot seem to find how.
Maybe I should look at the `extremely complicated Metacity extensions
mechanism', but somehow that does sound too appealing.
Greetings,
Jan.
[is this more on toping for the desktop-devel-list?]
/*
keywise.c: Keyboard Window Selector v0.0.1
Hacker-friendly Metacity window selector.
The idea of Keywise is to popup a window list (a bit similar to
WindowMaker) that shows all windows grouped by class, with
predictable accelerators. For example:
M-TAB 1 (better yet: KP_1 KP_1)
should focus your Emacs or Browser window if you have only one open,
or cycle through the list of Emacs windows to choose from.
RATIONALE
Metacity is a simple, solid and well supported window manager for
GNOME. It so lightweight, that it does not have sensible or
customisable window selection, which makes it very awkward to use.
Metacity does support a minimalist keybinding and popup for cycling
through all available windows that happen to be in the active
workspace, which is most unfriendly, inefficient, and hardly ever
what you want [I've now learned that this marvelless pick-and-pray
selection method is inspired by Microsoft Windows XP Home Edition --
sigh].
Adding any custom, customisable or window-class based focussing that
supports keyboard accelerators to Metacity is [sadly] not an option
(see http://bugzilla.gnome.org/show_bug.cgi?id=140925). I think
that is an oversight not only because every decent window manager
since fvwm has supported it, but also because advocating use of the
mouse is evil. The good news is that full extensibility of GNOME
window management is supposedly supported by using libwnck (should
study EWMH: keybinding to menu popup from Metacity).
USAGE
* Metacity -- FIXME how to bind key to: focus keywise menu?
hack: bind KP_1 -> run_command_1 -> keywise --standalone
* Sawfish -- silly, but handy for testing
cat >> ~/.sawfishrc <<EOF
(defun focus-window (w)
(unshade-window w)
(display-window w)
(warp-cursor-to-window w 15 10))
(defun windows-of-name (name)
(filter (lambda (w) (string-match name (window-name w))) (managed-windows)))
(bind-keys global-keymap "H-TAB"
'(focus-window (car (windows-of-name "keywise"))))
;; ' /*
EOF
sawfish-client -f restart
Copyright (C) 2004 Jan Nieuwenhuizen <janneke gnu org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307, USA.
This started as a hacked-up window-menu.c, the keen eye may still
find pieces:
Copyright (C) 2003 Sun Microsystems, Inc.
Copyright (C) 2001 Free Software Foundation, Inc.
Copyright (C) 2000 Helix Code, Inc.
window-menu.c Authors:
Mark McLoughlin <mark skynet ie>
George Lebl <jirka 5z com>
Jacob Berkman <jacob helixcode com>
*/
#include <gnome.h>
/* But I'm not told whether this unstability is good (active hacking)
or bad (will be dropped), and how much at that. */
#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>
#include <libwnck/class-group.h>
/* Do something like
gdk-pixbuf-csource --raw --build-list default_icon_data default_icon.png \
> icons.h */
#include "icons.h"
#define STANDALONE_HACK
gboolean standalone_p = 0;
/* Ugh. -- hello world mode */
#define CLASS_GROUP_COUNT 10
#define APP_NAME_COUNT 5
char const *CLASS_NAME_GROUPS[CLASS_GROUP_COUNT][APP_NAME_COUNT] =
{
{ "emacs", 0, 0, 0, 0 },
{ "terminal", "xterm", "rxvt", 0, 0 },
{ "xdvi", "xpdf", "gv", "ggv", "gpdf" },
{ "mozilla", "galeon", "firebird", "epiphany", 0 },
{ "eog", "gimp", "display", 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ "ungrouped", 0, 0, 0, 0 }
};
typedef struct
{
GtkWidget *main;
GtkWidget *frame;
GtkWidget *menu;
GtkWidget *no_windows_item;
int size;
} Window_list;
/* Utility. */
static int
class_group_number (WnckWindow *window)
{
int i, j;
WnckClassGroup *group = wnck_window_get_class_group (window);
char const *name = wnck_class_group_get_name (group);
size_t len = strlen (name);
for (i = 0; i < CLASS_GROUP_COUNT && CLASS_NAME_GROUPS[i]; i++)
for (j = 0; j < APP_NAME_COUNT && CLASS_NAME_GROUPS[i][j]; j++)
if (!strncasecmp (name, CLASS_NAME_GROUPS[i][j],
MIN (strlen (CLASS_NAME_GROUPS[i][j]), len)))
return i;
return CLASS_GROUP_COUNT - 1;
}
static GdkPixbuf *
get_default_window_icon (void)
{
static GdkPixbuf *icon = NULL;
if (!icon)
icon = gdk_pixbuf_new_from_inline (-1, default_icon_data, FALSE, NULL);
return icon;
}
static void
menu_item_set_window_icon (GtkWidget *menu_item, WnckWindow *window)
{
GdkPixbuf *pixbuf;
int width, height;
int icon_size;
icon_size = -1;
if (window)
pixbuf = wnck_window_get_icon (window);
if (!pixbuf)
pixbuf = get_default_window_icon ();
if (icon_size == -1)
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size);
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
GtkWidget *image = gtk_image_new ();
if (icon_size != -1
&& (width > icon_size || height > icon_size))
{
double scale = ((double) icon_size) / MAX (width, height);
GdkPixbuf *newbuf = gdk_pixbuf_scale_simple (pixbuf, width * scale,
height * scale,
GDK_INTERP_BILINEAR);
gtk_image_set_from_pixbuf (GTK_IMAGE (image), newbuf);
g_object_unref (newbuf);
}
else
gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
GTK_WIDGET (image));
gtk_widget_show (image);
}
GtkAccelGroup *
widget_get_accel_group (GtkWidget *widget)
{
GtkAccelGroup *group = g_object_get_data (G_OBJECT (widget),
"keywise-accel-group");
if (!group)
{
group = gtk_accel_group_new ();
g_object_set_data_full (G_OBJECT (widget), "keywise-accel-group",
group, (GDestroyNotify) g_object_unref);
}
return group;
}
/* Gtk Widget callbacks. */
static gint
main_widget_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
gtk_widget_destroy (widget);
gtk_main_quit ();
return TRUE;
}
#ifdef STANDALONE_HACK
static gint
standalone_main_widget_delete_event (GtkWidget *widget, GdkEvent *event,
gpointer data)
{
gtk_main_quit ();
return TRUE;
}
#endif
static void
window_workspace_window_activate (WnckWindow *window)
{
WnckWorkspace *workspace = wnck_window_get_workspace (window);
wnck_workspace_activate (workspace);
if (wnck_window_is_minimized (window))
wnck_window_unminimize (window);
wnck_window_activate (window);
}
static void
widget_destroy_window_list (GtkWidget *widget, Window_list *window_list)
{
if (window_list->menu)
gtk_widget_destroy (window_list->menu);
window_list->menu = NULL;
if (window_list->no_windows_item)
g_object_unref (window_list->no_windows_item);
window_list->no_windows_item = NULL;
g_free (window_list);
}
static void
widget_destroy_window_list_menu (GtkWidget *widget, Window_list *window_list)
{
window_list->menu = NULL;
}
static void
widget_hide_menu (GtkWidget *menu, Window_list *window_list)
{
gtk_frame_set_shadow_type (GTK_FRAME (window_list->frame), GTK_SHADOW_NONE);
}
/* Window_list */
static WnckScreen *
window_list_get_screen (Window_list *window_list)
{
GdkScreen *screen = gtk_widget_get_screen (window_list->main);
return wnck_screen_get (gdk_screen_get_number (screen));
}
static WnckWorkspace *
window_list_get_active_workspace (Window_list *window_list)
{
WnckScreen *screen = window_list_get_screen (window_list);
return wnck_screen_get_active_workspace (screen);
}
static void
window_list_position_menu (GtkMenu *menu,
int *x, int *y,
gboolean *push_in, gpointer user_data)
{
GtkWidget *widget = GTK_WIDGET (user_data);
GtkRequisition requisition;
gint menu_xpos;
gint menu_ypos;
gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
/* Hmm, we're called too early, before having a window? */
if (widget->window)
gdk_window_get_origin (widget->window, &menu_xpos, &menu_ypos);
menu_xpos += widget->allocation.x;
menu_ypos += widget->allocation.y;
GdkScreen *screen = gtk_widget_get_screen (widget);
int height = gdk_screen_get_height (screen);
int width = gdk_screen_get_width (screen);
*x = (width - requisition.width) / 2;
*y = (height - requisition.height) / 2;
*push_in = TRUE;
}
static GtkWidget *
_menu_add_window (GtkWidget *menu, WnckWindow *window, char const *label)
{
GtkWidget *menu_item = gtk_image_menu_item_new_with_mnemonic (label);
gtk_menu_item_set_accel_path (GTK_MENU_ITEM (menu_item), NULL);
menu_item_set_window_icon (menu_item, window);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
gtk_widget_show (menu_item);
return menu_item;
}
char const *
window_get_basename (WnckWindow *window)
{
WnckClassGroup *group = wnck_window_get_class_group (window);
char const *group_name = wnck_class_group_get_name (group);
char const *name = wnck_window_get_name (window);
size_t len = 0;
if (group_name)
len = strlen (group_name);
if (name && group_name && len && strlen (name) > len
&& !strncasecmp (name, group_name, len))
return name + len;
if (name)
return name;
if (group_name)
return group_name;
return _ ("Unknown Window");
}
char *
window_get_label (WnckWindow *window, int i, gboolean strip_group_name,
WnckWorkspace *active)
{
char *label;
WnckClassGroup *group = wnck_window_get_class_group (window);
char const *group_name = wnck_class_group_get_name (group);
char const *name = window_get_basename (window);
char const *sep = " - ";
if (strip_group_name || !strcasecmp (name, group_name))
{
sep = "";
group_name = "";
}
WnckWorkspace *workspace = wnck_window_get_workspace (window);
char *workspace_name;
if (active != workspace)
workspace_name = g_strdup_printf (" [%s]",
wnck_workspace_get_name (workspace));
else
workspace_name = g_strdup_printf ("");
if (!wnck_window_is_minimized (window))
label = g_strdup_printf ("_%d %s%s%s%s", (i + 1) % 10, group_name, sep,
name, workspace_name);
else
label = g_strdup_printf ("[_%d %s%s%s%s]", (i + 1) % 10, group_name, sep,
name, workspace_name);
g_free (workspace_name);
return label;
}
static GtkWidget *
menu_add_window (GtkWidget *menu, WnckWindow *window, int i,
gboolean strip_group_name, WnckWorkspace *active)
{
WnckClassGroup *group = wnck_window_get_class_group (window);
char const *group_name = wnck_class_group_get_name (group);
char *label = window_get_label (window, i, strip_group_name, active);
GtkWidget *menu_item = _menu_add_window (menu, window, label);
if (window)
{
g_signal_connect_swapped (menu_item, "activate",
G_CALLBACK (window_workspace_window_activate),
window);
#ifdef STANDALONE_HACK
if (standalone_p)
g_signal_connect (menu_item, "activate",
G_CALLBACK (standalone_main_widget_delete_event), 0);
#endif
}
g_free (label);
return menu_item;
}
static gboolean
window_list_menu_keypad (Window_list *window_list)
{
int i;
GList *car, *list;
GSList *scar;
WnckScreen *screen;
GList *windows;
if (window_list->no_windows_item)
g_object_unref (window_list->no_windows_item);
window_list->no_windows_item = NULL;
g_signal_connect (window_list->menu, "destroy",
G_CALLBACK (widget_destroy_window_list_menu), window_list);
screen = window_list_get_screen (window_list);
windows = wnck_screen_get_windows (screen);
GSList *grouped[CLASS_GROUP_COUNT];
for (i = 0; i < CLASS_GROUP_COUNT; i++)
grouped[i] = NULL;
/* TODO: ordering of group, use currently selected and workspace in
ordering. */
for (car = windows; car; car = car->next)
if (!wnck_window_is_skip_tasklist (car->data))
{
int n = class_group_number (car->data);
grouped[n] = g_slist_append (grouped[n], car->data);
}
WnckWorkspace *active = window_list_get_active_workspace (window_list);
GtkAccelGroup *accel_group = widget_get_accel_group (window_list->main);
for (i = 0; i < CLASS_GROUP_COUNT; i++)
if (windows && grouped[i])
{
WnckClassGroup *group = wnck_window_get_class_group (grouped[i]->data);
char const *group_name = wnck_class_group_get_name (group);
if (!grouped[i]->next)
{
GtkWidget *menu_item = menu_add_window (window_list->menu,
grouped[i]->data, i, 0,
active);
}
else
{
char *label = g_strdup_printf ("_%d %s", (i + 1) % 10, group_name);
GtkWidget *menu_item = _menu_add_window (window_list->menu,
grouped[i]->data, label);
GtkWidget *sub_menu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), sub_menu);
for (scar = grouped[i]; scar; scar = scar->next)
menu_add_window (sub_menu, scar->data, i, 1, active);
}
}
car = GTK_MENU_SHELL (window_list->menu)->children;
if (0 && !car)
{
window_list->no_windows_item
= gtk_image_menu_item_new_with_mnemonic (_ ("No Windows Open"));
gtk_widget_set_sensitive (window_list->no_windows_item, FALSE);
gtk_widget_show (window_list->no_windows_item);
gtk_menu_shell_append (GTK_MENU_SHELL (window_list->menu),
window_list->no_windows_item);
}
return car != 0;
}
static gboolean
window_list_popup_menu (Window_list *window_list)
{
GList *car, *list;
int is_first = 0;
if (!window_list->menu)
{
GtkAccelGroup *accel_group = widget_get_accel_group (window_list->main);
is_first = 1;
window_list->menu = gtk_menu_new ();
g_signal_connect (window_list->menu, "hide",
G_CALLBACK (widget_hide_menu), window_list);
gtk_window_add_accel_group (GTK_WINDOW (window_list->main), accel_group);
gtk_menu_set_accel_group (GTK_MENU (window_list->menu), accel_group);
gtk_widget_show (window_list->menu);
gtk_menu_set_screen (GTK_MENU (window_list->menu),
gtk_widget_get_screen (window_list->main));
gtk_frame_set_shadow_type (GTK_FRAME (window_list->frame),
GTK_SHADOW_IN);
}
list = gtk_container_get_children (GTK_CONTAINER (window_list->menu));
for (car = list; car; car = car->next)
{
if (window_list->no_windows_item
&& car->data == window_list->no_windows_item)
window_list->no_windows_item = NULL;
gtk_container_remove (GTK_CONTAINER (window_list->menu), car->data);
}
g_list_free (list);
gboolean have_windows_p;
/* Your gnome-guile hook here. */
have_windows_p = window_list_menu_keypad (window_list);
if (is_first || ! have_windows_p)
return FALSE;
int button, activate_time = 0;
gtk_menu_popup (GTK_MENU (window_list->menu),
NULL, NULL,
window_list_position_menu,
window_list->frame,
button, activate_time);
return TRUE;
}
static gboolean
window_list_popup (GtkWidget *main, GdkEventFocus *event,
Window_list *window_list)
{
if (!standalone_p)
gtk_window_iconify (GTK_WINDOW (main));
window_list_popup_menu (window_list);
return TRUE;
}
Window_list *
main_fill (GtkWidget *main)
{
Window_list *window_list;
GtkWidget *alignment;
AtkObject *atk_obj;
window_list = g_new0 (Window_list, 1);
window_list->main = main;
atk_obj = gtk_widget_get_accessible (window_list->main);
atk_object_set_name (atk_obj, _("Keyboard Window Selector"));
atk_object_set_description (atk_obj,
_ ("Select window using keyboard."));
g_signal_connect (window_list->main, "destroy",
G_CALLBACK (widget_destroy_window_list), window_list);
window_list->frame = gtk_frame_new (NULL);
gtk_container_set_border_width (GTK_CONTAINER (window_list->frame), 0);
gtk_frame_set_shadow_type (GTK_FRAME (window_list->frame), GTK_SHADOW_NONE);
gtk_widget_show (window_list->frame);
gtk_container_add (GTK_CONTAINER (window_list->main), window_list->frame);
return window_list;
}
int
main (int argc, char **argv)
{
Window_list *window_list;
GtkWidget *toplevel_window;
gnome_init ("keywise", "1.0", argc, argv);
toplevel_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (toplevel_window), "delete_event",
(GtkSignalFunc) main_widget_delete_event, NULL);
window_list = main_fill (toplevel_window);
window_list_popup_menu (window_list);
/* This is kludgy: the toplevel window that we do not want to show,
gets a focus/uniconify event, and we catch that to popup the
menu. */
g_signal_connect (GTK_WIDGET (toplevel_window), "focus-in-event",
G_CALLBACK (window_list_popup), window_list);
gtk_window_set_decorated (GTK_WINDOW (toplevel_window), FALSE);
gtk_window_set_skip_taskbar_hint (GTK_WINDOW (toplevel_window), TRUE);
gtk_window_set_gravity (GTK_WINDOW (toplevel_window),
GDK_GRAVITY_NORTH_WEST);
gtk_window_move (GTK_WINDOW (toplevel_window), 0, 0);
#ifdef STANDALONE_HACK
/* Metacity hack: bind run_command to keywise --standalone. */
if (argc >= 2 && !strcmp ("--standalone", argv[1]))
{
standalone_p = 1;
//gtk_window_deiconify (GTK_WINDOW (toplevel_window));
window_list_popup_menu (window_list);
window_list_popup_menu (window_list);
}
else if (argc >= 2 && !strcmp ("--emacs", argv[1]))
{
standalone_p = 1;
fprintf (stderr, "select EMACS\n");
window_list_popup_menu (window_list);
}
else
#endif
/* We do not want to see the window... */
gtk_window_iconify (GTK_WINDOW (toplevel_window));
/* Show the toplevel window to get it to appear in sawfish's
(managed-windows), which enables to focus it with a
keystroke. */
gtk_widget_show (toplevel_window);
gtk_main ();
return 0;
}
--
Jan Nieuwenhuizen <janneke gnu org> | GNU LilyPond - The music typesetter
http://www.xs4all.nl/~jantien | http://www.lilypond.org
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]