Fix a variety of focus race conditions in all focus modes, or at least

2004-10-04  Elijah Newren  <newren@math.utah.edu>

	Fix a variety of focus race conditions in all focus modes, or at
	least make them harder to trigger (fixes #152000)

	* src/core.[ch] (meta_core_user_lower_and_unfocus): add a
	timestamp parameter; pass it along to
	meta_workspace_focus_default_window

	* src/display.[ch] (meta_display_get_current_time_roundtrip): new
	function

	* src/display.c (event_callback): pass a timestamp to the
	meta_workspace_activate and meta_workspace_focus_default_window
	function calls

	* src/frames.c (meta_frames_button_press_event): pass a timestamp
	to meta_core_user_lower_and_unfocus

	* src/keybindings.c (handle_activate_workspace): pass a timestamp
	to meta_workspace_activate, (process_workspace_switch_grab): pass
	a timestamp to meta_workspace_focus_default_window and
	meta_workspace_activate, (handle_toggle_desktop): pass a timestamp
	to meta_workspace_focus_default_window,
	(do_handle_move_to_workspace): pass a timestamp to
	meta_workspace_activate_with_focus, (handle_workspace_switch):
	meta_workspace_activate

	* src/screen.c (meta_screen_new): pass a timestamp to
	meta_workspace_activate

	* src/window.c (meta_window_free): pass a timestamp to
	meta_workspace_focus_default_window, (idle_calc_showing): don't
	increment the focus sentinel here, (meta_window_minimize): pass a
	timestamp to meta_workspace_focus_default_window,
	(meta_window_client_message), pass a timestamp to
	meta_workspace_focus_default_window

	* src/workspace.h (meta_workspace_activate): add timestamp
	parameter, (meta_workspace_activate_with_focus): add timestamp
	parameter, (meta_workspace_focus_default_window): add timestamp
	parameter

	* src/workspace.c (meta_workspace_focus_mru_window): make this
	function take a timestamp and use it for meta_window_focus or
	XSetInputFocus, (meta_workspace_activate_with_focus): make this
	function take a timestamp and pass it along to meta_window_focus
	and meta_workspace_focus_default_window,
	(meta_workspace_activate): make this function take a timestamp and
	pass it to meta_workspace_activate_with_focus),
	(meta_workspace_focus_default_window): make this function take a
	timestamp, warn if its 0 but try to handle that case sanely, and
	pass the timestamp on to meta_window_focus or
	meta_workspace_focus_mru_window or XSetInputFocus
This commit is contained in:
Elijah Newren 2004-10-04 20:32:59 +00:00 committed by Elijah Newren
parent 7d4c302598
commit 16b9aff47c
11 changed files with 165 additions and 62 deletions

View File

@ -1,3 +1,58 @@
2004-10-04 Elijah Newren <newren@math.utah.edu>
Fix a variety of focus race conditions in all focus modes, or at
least make them harder to trigger (fixes #152000)
* src/core.[ch] (meta_core_user_lower_and_unfocus): add a
timestamp parameter; pass it along to
meta_workspace_focus_default_window
* src/display.[ch] (meta_display_get_current_time_roundtrip): new
function
* src/display.c (event_callback): pass a timestamp to the
meta_workspace_activate and meta_workspace_focus_default_window
function calls
* src/frames.c (meta_frames_button_press_event): pass a timestamp
to meta_core_user_lower_and_unfocus
* src/keybindings.c (handle_activate_workspace): pass a timestamp
to meta_workspace_activate, (process_workspace_switch_grab): pass
a timestamp to meta_workspace_focus_default_window and
meta_workspace_activate, (handle_toggle_desktop): pass a timestamp
to meta_workspace_focus_default_window,
(do_handle_move_to_workspace): pass a timestamp to
meta_workspace_activate_with_focus, (handle_workspace_switch):
meta_workspace_activate
* src/screen.c (meta_screen_new): pass a timestamp to
meta_workspace_activate
* src/window.c (meta_window_free): pass a timestamp to
meta_workspace_focus_default_window, (idle_calc_showing): don't
increment the focus sentinel here, (meta_window_minimize): pass a
timestamp to meta_workspace_focus_default_window,
(meta_window_client_message), pass a timestamp to
meta_workspace_focus_default_window
* src/workspace.h (meta_workspace_activate): add timestamp
parameter, (meta_workspace_activate_with_focus): add timestamp
parameter, (meta_workspace_focus_default_window): add timestamp
parameter
* src/workspace.c (meta_workspace_focus_mru_window): make this
function take a timestamp and use it for meta_window_focus or
XSetInputFocus, (meta_workspace_activate_with_focus): make this
function take a timestamp and pass it along to meta_window_focus
and meta_workspace_focus_default_window,
(meta_workspace_activate): make this function take a timestamp and
pass it to meta_workspace_activate_with_focus),
(meta_workspace_focus_default_window): make this function take a
timestamp, warn if its 0 but try to handle that case sanely, and
pass the timestamp on to meta_window_focus or
meta_workspace_focus_mru_window or XSetInputFocus
2004-09-22 Elijah Newren <newren@math.utah.edu> 2004-09-22 Elijah Newren <newren@math.utah.edu>
* src/keybindings.c (process_workspace_switch_grab): Focus the * src/keybindings.c (process_workspace_switch_grab): Focus the

View File

@ -237,7 +237,8 @@ meta_core_user_raise (Display *xdisplay,
void void
meta_core_user_lower_and_unfocus (Display *xdisplay, meta_core_user_lower_and_unfocus (Display *xdisplay,
Window frame_xwindow) Window frame_xwindow,
Time timestamp)
{ {
MetaDisplay *display; MetaDisplay *display;
MetaWindow *window; MetaWindow *window;
@ -277,7 +278,9 @@ meta_core_user_lower_and_unfocus (Display *xdisplay,
/* focus on the (new) topmost window */ /* focus on the (new) topmost window */
if (window->has_focus) if (window->has_focus)
meta_workspace_focus_default_window (window->screen->active_workspace, window); meta_workspace_focus_default_window (window->screen->active_workspace,
window,
timestamp);
} }
} }

View File

@ -61,7 +61,8 @@ void meta_core_user_resize (Display *xdisplay,
void meta_core_user_raise (Display *xdisplay, void meta_core_user_raise (Display *xdisplay,
Window frame_xwindow); Window frame_xwindow);
void meta_core_user_lower_and_unfocus (Display *xdisplay, void meta_core_user_lower_and_unfocus (Display *xdisplay,
Window frame_xwindow); Window frame_xwindow,
Time timestamp);
void meta_core_user_focus (Display *xdisplay, void meta_core_user_focus (Display *xdisplay,
Window frame_xwindow, Window frame_xwindow,

View File

@ -1099,6 +1099,35 @@ meta_display_get_current_time (MetaDisplay *display)
return display->current_time; return display->current_time;
} }
/* Get a timestamp, even if it means a roundtrip */
guint32
meta_display_get_current_time_roundtrip (MetaDisplay *display)
{
guint32 timestamp;
timestamp = meta_display_get_current_time (display);
if (timestamp == CurrentTime)
{
XEvent property_event;
/* Using the property XA_PRIMARY because it's safe; nothing
* would use it as a property. The type doesn't matter.
*/
XChangeProperty (display->xdisplay,
display->leader_window,
XA_PRIMARY, XA_STRING, 8,
PropModeAppend, NULL, 0);
XWindowEvent (display->xdisplay,
display->leader_window,
PropertyChangeMask,
&property_event);
timestamp = property_event.xproperty.time;
}
return timestamp;
}
static void static void
add_ignored_serial (MetaDisplay *display, add_ignored_serial (MetaDisplay *display,
unsigned long serial) unsigned long serial)
@ -2100,7 +2129,7 @@ event_callback (XEvent *event,
space); space);
if (workspace) if (workspace)
meta_workspace_activate (workspace); meta_workspace_activate (workspace, meta_display_get_current_time_roundtrip (display));
else else
meta_verbose ("Don't know about workspace %d\n", space); meta_verbose ("Don't know about workspace %d\n", space);
} }
@ -2129,7 +2158,7 @@ event_callback (XEvent *event,
else else
{ {
meta_screen_unshow_desktop (screen); meta_screen_unshow_desktop (screen);
meta_workspace_focus_default_window (screen->active_workspace, NULL); meta_workspace_focus_default_window (screen->active_workspace, NULL, meta_display_get_current_time_roundtrip (display));
} }
} }
else if (event->xclient.message_type == else if (event->xclient.message_type ==

View File

@ -422,7 +422,8 @@ void meta_display_increment_event_serial (MetaDisplay *display);
void meta_display_update_active_window_hint (MetaDisplay *display); void meta_display_update_active_window_hint (MetaDisplay *display);
guint32 meta_display_get_current_time (MetaDisplay *display); guint32 meta_display_get_current_time (MetaDisplay *display);
guint32 meta_display_get_current_time_roundtrip (MetaDisplay *display);
/* utility goo */ /* utility goo */
const char* meta_event_mode_to_string (int m); const char* meta_event_mode_to_string (int m);

View File

@ -1372,7 +1372,9 @@ meta_frames_button_press_event (GtkWidget *widget,
} }
else if (event->button == 2) else if (event->button == 2)
{ {
meta_core_user_lower_and_unfocus (gdk_display, frame->xwindow); meta_core_user_lower_and_unfocus (gdk_display,
frame->xwindow,
event->time);
} }
else if (event->button == 3) else if (event->button == 3)
{ {

View File

@ -2474,7 +2474,7 @@ handle_activate_workspace (MetaDisplay *display,
if (workspace) if (workspace)
{ {
meta_workspace_activate (workspace); meta_workspace_activate (workspace, event->xkey.time);
} }
else else
{ {
@ -2680,7 +2680,9 @@ process_workspace_switch_grab (MetaDisplay *display,
meta_topic (META_DEBUG_KEYBINDINGS, meta_topic (META_DEBUG_KEYBINDINGS,
"Focusing default window on target workspace\n"); "Focusing default window on target workspace\n");
meta_workspace_focus_default_window (target_workspace, NULL); meta_workspace_focus_default_window (target_workspace,
NULL,
event->xkey.time);
return TRUE; /* we already ended the grab */ return TRUE; /* we already ended the grab */
} }
@ -2749,7 +2751,7 @@ process_workspace_switch_grab (MetaDisplay *display,
meta_topic (META_DEBUG_KEYBINDINGS, meta_topic (META_DEBUG_KEYBINDINGS,
"Activating target workspace\n"); "Activating target workspace\n");
meta_workspace_activate (target_workspace); meta_workspace_activate (target_workspace, event->xkey.time);
return TRUE; /* we already ended the grab */ return TRUE; /* we already ended the grab */
} }
@ -2760,7 +2762,7 @@ process_workspace_switch_grab (MetaDisplay *display,
"Ending workspace tabbing & focusing default window; uninteresting key pressed\n"); "Ending workspace tabbing & focusing default window; uninteresting key pressed\n");
workspace = workspace =
(MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup); (MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup);
meta_workspace_focus_default_window (workspace, NULL); meta_workspace_focus_default_window (workspace, NULL, event->xkey.time);
return FALSE; return FALSE;
} }
@ -2774,7 +2776,9 @@ handle_toggle_desktop (MetaDisplay *display,
if (screen->showing_desktop) if (screen->showing_desktop)
{ {
meta_screen_unshow_desktop (screen); meta_screen_unshow_desktop (screen);
meta_workspace_focus_default_window (screen->active_workspace, NULL); meta_workspace_focus_default_window (screen->active_workspace,
NULL,
event->xkey.time);
} }
else else
meta_screen_show_desktop (screen); meta_screen_show_desktop (screen);
@ -3210,7 +3214,7 @@ do_handle_move_to_workspace (MetaDisplay *display,
/* Activate second, so the window is never unmapped */ /* Activate second, so the window is never unmapped */
meta_window_change_workspace (window, workspace); meta_window_change_workspace (window, workspace);
if (flip) if (flip)
meta_workspace_activate_with_focus (workspace, window); meta_workspace_activate_with_focus (workspace, window, event->xkey.time);
} }
else else
{ {
@ -3374,7 +3378,7 @@ handle_workspace_switch (MetaDisplay *display,
meta_display_end_grab_op (display, event->xkey.time); meta_display_end_grab_op (display, event->xkey.time);
} }
meta_workspace_activate (next); meta_workspace_activate (next, event->xkey.time);
if (grabbed_before_release) if (grabbed_before_release)
{ {

View File

@ -604,7 +604,7 @@ meta_screen_new (MetaDisplay *display,
/* Screens must have at least one workspace at all times, /* Screens must have at least one workspace at all times,
* so create that required workspace. * so create that required workspace.
*/ */
meta_workspace_activate (meta_workspace_new (screen)); meta_workspace_activate (meta_workspace_new (screen), timestamp);
update_num_workspaces (screen); update_num_workspaces (screen);
set_workspace_names (screen); set_workspace_names (screen);
@ -641,7 +641,7 @@ meta_screen_new (MetaDisplay *display,
current_workspace); current_workspace);
if (space != NULL) if (space != NULL)
meta_workspace_activate (space); meta_workspace_activate (space, timestamp);
} }
meta_compositor_manage_screen (screen->display->compositor, meta_compositor_manage_screen (screen->display->compositor,
@ -1077,7 +1077,7 @@ update_num_workspaces (MetaScreen *screen)
} }
if (need_change_space) if (need_change_space)
meta_workspace_activate (last_remaining); meta_workspace_activate (last_remaining, meta_display_get_current_time_roundtrip (screen->display));
/* Should now be safe to free the workspaces */ /* Should now be safe to free the workspaces */
tmp = extras; tmp = extras;

View File

@ -931,7 +931,7 @@ meta_window_free (MetaWindow *window)
meta_topic (META_DEBUG_FOCUS, meta_topic (META_DEBUG_FOCUS,
"Focusing default window since we're unmanaging %s\n", "Focusing default window since we're unmanaging %s\n",
window->desc); window->desc);
meta_workspace_focus_default_window (window->screen->active_workspace, window); meta_workspace_focus_default_window (window->screen->active_workspace, window, meta_display_get_current_time_roundtrip (window->display));
} }
else if (window->display->expected_focus_window == window) else if (window->display->expected_focus_window == window)
{ {
@ -939,7 +939,7 @@ meta_window_free (MetaWindow *window)
"Focusing default window since expected focus window freed %s\n", "Focusing default window since expected focus window freed %s\n",
window->desc); window->desc);
window->display->expected_focus_window = NULL; window->display->expected_focus_window = NULL;
meta_workspace_focus_default_window (window->screen->active_workspace, window); meta_workspace_focus_default_window (window->screen->active_workspace, window, meta_display_get_current_time_roundtrip (window->display));
} }
else else
{ {
@ -1487,25 +1487,6 @@ idle_calc_showing (gpointer data)
tmp = tmp->next; tmp = tmp->next;
} }
/* for all displays used in the queue, set a sentinel property on
* the root window so that we can ignore EnterNotify events that
* occur before the window maps occur. This avoids a race
* condition.
*/
tmp = should_show;
while (tmp != NULL)
{
MetaWindow *window = tmp->data;
if (g_slist_find (displays, window->display) == NULL)
{
displays = g_slist_prepend (displays, window->display);
meta_display_increment_focus_sentinel (window->display);
}
tmp = tmp->next;
}
g_slist_free (copy); g_slist_free (copy);
g_slist_free (unplaced); g_slist_free (unplaced);
@ -1898,7 +1879,7 @@ meta_window_minimize (MetaWindow *window)
meta_topic (META_DEBUG_FOCUS, meta_topic (META_DEBUG_FOCUS,
"Focusing default window due to minimization of focus window %s\n", "Focusing default window due to minimization of focus window %s\n",
window->desc); window->desc);
meta_workspace_focus_default_window (window->screen->active_workspace, window); meta_workspace_focus_default_window (window->screen->active_workspace, window, meta_display_get_current_time_roundtrip (window->display));
} }
else else
{ {
@ -4021,7 +4002,7 @@ meta_window_client_message (MetaWindow *window,
meta_topic (META_DEBUG_FOCUS, meta_topic (META_DEBUG_FOCUS,
"Focusing default window because of minimization of former focus window %s, which was due to a wm_change_state client message\n", "Focusing default window because of minimization of former focus window %s, which was due to a wm_change_state client message\n",
window->desc); window->desc);
meta_workspace_focus_default_window (window->screen->active_workspace, window); meta_workspace_focus_default_window (window->screen->active_workspace, window, meta_display_get_current_time_roundtrip (window->display));
} }
} }

View File

@ -30,7 +30,8 @@
void meta_workspace_queue_calc_showing (MetaWorkspace *workspace); void meta_workspace_queue_calc_showing (MetaWorkspace *workspace);
static void set_active_space_hint (MetaScreen *screen); static void set_active_space_hint (MetaScreen *screen);
static void meta_workspace_focus_mru_window (MetaWorkspace *workspace, static void meta_workspace_focus_mru_window (MetaWorkspace *workspace,
MetaWindow *not_this_one); MetaWindow *not_this_one,
Time timestamp);
static void static void
maybe_add_to_list (MetaScreen *screen, MetaWindow *window, gpointer data) maybe_add_to_list (MetaScreen *screen, MetaWindow *window, gpointer data)
@ -270,7 +271,8 @@ meta_workspace_queue_calc_showing (MetaWorkspace *workspace)
void void
meta_workspace_activate_with_focus (MetaWorkspace *workspace, meta_workspace_activate_with_focus (MetaWorkspace *workspace,
MetaWindow *focus_this) MetaWindow *focus_this,
Time timestamp)
{ {
MetaWorkspace *old; MetaWorkspace *old;
MetaWindow *move_window; MetaWindow *move_window;
@ -322,8 +324,7 @@ meta_workspace_activate_with_focus (MetaWorkspace *workspace,
if (focus_this) if (focus_this)
{ {
meta_window_focus (focus_this, meta_window_focus (focus_this, timestamp);
meta_display_get_current_time (focus_this->display));
meta_window_raise (focus_this); meta_window_raise (focus_this);
} }
else if (move_window) else if (move_window)
@ -333,15 +334,15 @@ meta_workspace_activate_with_focus (MetaWorkspace *workspace,
else else
{ {
meta_topic (META_DEBUG_FOCUS, "Focusing default window on new workspace\n"); meta_topic (META_DEBUG_FOCUS, "Focusing default window on new workspace\n");
meta_workspace_focus_default_window (workspace, NULL); meta_workspace_focus_default_window (workspace, NULL, timestamp);
} }
} }
void void
meta_workspace_activate (MetaWorkspace *workspace) meta_workspace_activate (MetaWorkspace *workspace,
Time timestamp)
{ {
meta_workspace_activate_with_focus (workspace, meta_workspace_activate_with_focus (workspace, NULL, timestamp);
NULL);
} }
int int
@ -790,10 +791,18 @@ meta_workspace_get_name (MetaWorkspace *workspace)
void void
meta_workspace_focus_default_window (MetaWorkspace *workspace, meta_workspace_focus_default_window (MetaWorkspace *workspace,
MetaWindow *not_this_one) MetaWindow *not_this_one,
Time timestamp)
{ {
if (timestamp == CurrentTime)
{
meta_warning ("CurrentTime used to choose focus window; "
"focus window may not be correct.\n");
}
if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK) if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK)
meta_workspace_focus_mru_window (workspace, not_this_one); meta_workspace_focus_mru_window (workspace, not_this_one, timestamp);
else else
{ {
MetaWindow * window; MetaWindow * window;
@ -802,13 +811,28 @@ meta_workspace_focus_default_window (MetaWorkspace *workspace,
window->type != META_WINDOW_DOCK && window->type != META_WINDOW_DOCK &&
window->type != META_WINDOW_DESKTOP) window->type != META_WINDOW_DESKTOP)
{ {
meta_topic (META_DEBUG_FOCUS, if (timestamp == CurrentTime)
"Focusing mouse window %s\n", window->desc); {
meta_window_focus (window, meta_display_get_current_time (workspace->screen->display)); /* We would like for this to never happen. However, if
* it does happen then we kludge since using CurrentTime
* can mean ugly race conditions--and we can avoid these
* by allowing EnterNotify events (which come with
* timestamps) to handle focus.
*/
meta_topic (META_DEBUG_FOCUS,
"Not focusing mouse window %s because EnterNotify events should handle that\n", window->desc);
}
else
{
meta_topic (META_DEBUG_FOCUS,
"Focusing mouse window %s\n", window->desc);
meta_window_focus (window, timestamp);
}
} }
else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_SLOPPY) else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_SLOPPY)
meta_workspace_focus_mru_window (workspace, not_this_one); meta_workspace_focus_mru_window (workspace, not_this_one, timestamp);
else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_MOUSE) else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_MOUSE)
{ {
meta_topic (META_DEBUG_FOCUS, meta_topic (META_DEBUG_FOCUS,
@ -817,7 +841,7 @@ meta_workspace_focus_default_window (MetaWorkspace *workspace,
XSetInputFocus (workspace->screen->display->xdisplay, XSetInputFocus (workspace->screen->display->xdisplay,
workspace->screen->display->no_focus_window, workspace->screen->display->no_focus_window,
RevertToPointerRoot, RevertToPointerRoot,
meta_display_get_current_time (workspace->screen->display)); timestamp);
} }
} }
} }
@ -825,7 +849,8 @@ meta_workspace_focus_default_window (MetaWorkspace *workspace,
/* Focus MRU window (or top window if failed) on active workspace */ /* Focus MRU window (or top window if failed) on active workspace */
void void
meta_workspace_focus_mru_window (MetaWorkspace *workspace, meta_workspace_focus_mru_window (MetaWorkspace *workspace,
MetaWindow *not_this_one) MetaWindow *not_this_one,
Time timestamp)
{ {
MetaWindow *window = NULL; MetaWindow *window = NULL;
GList *tmp; GList *tmp;
@ -857,8 +882,7 @@ meta_workspace_focus_mru_window (MetaWorkspace *workspace,
meta_topic (META_DEBUG_FOCUS, meta_topic (META_DEBUG_FOCUS,
"Focusing workspace MRU window %s\n", window->desc); "Focusing workspace MRU window %s\n", window->desc);
meta_window_focus (window, meta_window_focus (window, timestamp);
meta_display_get_current_time (workspace->screen->display));
/* Also raise the window if in click-to-focus */ /* Also raise the window if in click-to-focus */
if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK) if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK)
@ -870,6 +894,6 @@ meta_workspace_focus_mru_window (MetaWorkspace *workspace,
XSetInputFocus (workspace->screen->display->xdisplay, XSetInputFocus (workspace->screen->display->xdisplay,
workspace->screen->display->no_focus_window, workspace->screen->display->no_focus_window,
RevertToPointerRoot, RevertToPointerRoot,
meta_display_get_current_time (workspace->screen->display)); timestamp);
} }
} }

View File

@ -63,8 +63,10 @@ void meta_workspace_relocate_windows (MetaWorkspace *workspace,
gboolean meta_workspace_contains_window (MetaWorkspace *workspace, gboolean meta_workspace_contains_window (MetaWorkspace *workspace,
MetaWindow *window); MetaWindow *window);
void meta_workspace_activate_with_focus (MetaWorkspace *workspace, void meta_workspace_activate_with_focus (MetaWorkspace *workspace,
MetaWindow *focus_this); MetaWindow *focus_this,
void meta_workspace_activate (MetaWorkspace *workspace); Time timestamp);
void meta_workspace_activate (MetaWorkspace *workspace,
Time timestamp);
int meta_workspace_index (MetaWorkspace *workspace); int meta_workspace_index (MetaWorkspace *workspace);
GList* meta_workspace_list_windows (MetaWorkspace *workspace); GList* meta_workspace_list_windows (MetaWorkspace *workspace);
@ -78,7 +80,8 @@ void meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace,
MetaRectangle *area); MetaRectangle *area);
void meta_workspace_focus_default_window (MetaWorkspace *workspace, void meta_workspace_focus_default_window (MetaWorkspace *workspace,
MetaWindow *not_this_one); MetaWindow *not_this_one,
Time timestamp);
MetaWorkspace* meta_workspace_get_neighbor (MetaWorkspace *workspace, MetaWorkspace* meta_workspace_get_neighbor (MetaWorkspace *workspace,
MetaMotionDirection direction); MetaMotionDirection direction);