diff --git a/js/Makefile.am b/js/Makefile.am index 555be7b19..49de2ff2a 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -56,4 +56,5 @@ nobase_dist_js_DATA = \ ui/windowManager.js \ ui/workspace.js \ ui/workspacesView.js \ - ui/workspaceSwitcherPopup.js + ui/workspaceSwitcherPopup.js \ + ui/xdndHandler.js diff --git a/js/ui/main.js b/js/ui/main.js index 4a8f8ef2c..e08781bfc 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -37,6 +37,7 @@ const ShellDBus = imports.ui.shellDBus; const TelepathyClient = imports.ui.telepathyClient; const WindowManager = imports.ui.windowManager; const Magnifier = imports.ui.magnifier; +const XdndHandler = imports.ui.xdndHandler; const StatusIconDispatcher = imports.ui.statusIconDispatcher; const DEFAULT_BACKGROUND_COLOR = new Clutter.Color(); @@ -60,6 +61,7 @@ let modalCount = 0; let modalActorFocusStack = []; let uiGroup = null; let magnifier = null; +let xdndHandler = null; let statusIconDispatcher = null; let _errorLogStack = []; let _startDate; @@ -129,6 +131,7 @@ function start() { global.stage.add_actor(uiGroup); placesManager = new PlaceDisplay.PlacesManager(); + xdndHandler = new XdndHandler.XdndHandler(); overview = new Overview.Overview(); chrome = new Chrome.Chrome(); magnifier = new Magnifier.Magnifier(); diff --git a/js/ui/overview.js b/js/ui/overview.js index 56da40adb..bfabcb3d2 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -9,9 +9,11 @@ const St = imports.gi.St; const Shell = imports.gi.Shell; const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; +const Gdk = imports.gi.Gdk; const AppDisplay = imports.ui.appDisplay; const Dash = imports.ui.dash; +const DND = imports.ui.dnd; const DocDisplay = imports.ui.docDisplay; const GenericDisplay = imports.ui.genericDisplay; const Lightbox = imports.ui.lightbox; @@ -30,6 +32,8 @@ const ANIMATION_TIME = 0.25; const DASH_SPLIT_FRACTION = 0.1; +const DND_WINDOW_SWITCH_TIMEOUT = 1250; + function Source() { this._init(); } @@ -178,9 +182,72 @@ Overview.prototype = { this._coverPane.lower_bottom(); + // XDND + this._dragMonitor = { + dragMotion: Lang.bind(this, this._onDragMotion) + }; + + Main.xdndHandler.connect('drag-begin', Lang.bind(this, this._onDragBegin)); + Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd)); + + this._windowSwitchTimeoutId = 0; + this._windowSwitchTimestamp = 0; + this._lastActiveWorkspaceIndex = -1; + this._needsFakePointerEvent = false; + this.workspaces = null; }, + _onDragBegin: function() { + DND.addDragMonitor(this._dragMonitor); + // Remember the workspace we started from + this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index(); + }, + + _onDragEnd: function(time) { + // In case the drag was canceled while in the overview + // we have to go back to where we started and hide + // the overview + if (this._shownTemporarily) { + global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time); + this.hideTemporarily(); + } + + DND.removeMonitor(this._dragMonitor); + }, + + _fakePointerEvent: function() { + let display = Gdk.Display.get_default(); + let deviceManager = display.get_device_manager(); + let pointer = deviceManager.get_client_pointer(); + let [screen, pointerX, pointerY] = display.get_device_state(pointer); + + display.warp_device(pointer, screen, pointerX, pointerY); + }, + + _onDragMotion: function(dragEvent) { + if (this._windowSwitchTimeoutId != 0) { + Mainloop.source_remove(this._windowSwitchTimeoutId); + this._windowSwitchTimeoutId = 0; + this._needsFakePointerEvent = false; + } + + if (dragEvent.targetActor && + dragEvent.targetActor._delegate && + dragEvent.targetActor._delegate.metaWindow) { + this._windowSwitchTimestamp = global.get_current_time(); + this._windowSwitchTimeoutId = Mainloop.timeout_add(DND_WINDOW_SWITCH_TIMEOUT, + Lang.bind(this, function() { + this._needsFakePointerEvent = true; + Main.activateWindow(dragEvent.targetActor._delegate.metaWindow, + this._windowSwitchTimestamp); + this.hideTemporarily(); + })); + } + + return DND.DragMotionResult.CONTINUE; + }, + _getDesktopClone: function() { let windows = global.get_window_actors().filter(function(w) { return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP; @@ -531,6 +598,12 @@ Overview.prototype = { this._animateVisible(); this._syncInputMode(); + + // Fake a pointer event if requested + if (this._needsFakePointerEvent) { + this._fakePointerEvent(); + this._needsFakePointerEvent = false; + } } }; Signals.addSignalMethods(Overview.prototype); diff --git a/js/ui/panel.js b/js/ui/panel.js index 4439d3d4d..4fe60a95d 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -26,6 +26,8 @@ const PANEL_ICON_SIZE = 24; const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5; +const BUTTON_DND_ACTIVATION_TIMEOUT = 250; + const ANIMATED_ICON_UPDATE_TIMEOUT = 100; const SPINNER_UPDATE_TIMEOUT = 130; const SPINNER_SPEED = 0.02; @@ -728,7 +730,20 @@ Panel.prototype = { reactive: true, can_focus: true }); this.button.set_child(label); - + this.button._delegate = this.button; + this.button._xdndTimeOut = 0; + this.button.handleDragOver = Lang.bind(this, + function(source, actor, x, y, time) { + if (source == Main.xdndHandler) { + if (this.button._xdndTimeOut != 0) + Mainloop.source_remove(this.button._xdndTimeOut); + this.button._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT, + Lang.bind(this, + function() { + this._xdndShowOverview(actor); + })); + } + }); this._leftBox.add(this.button); // We use this flag to mark the case where the user has entered the @@ -766,6 +781,18 @@ Panel.prototype = { this._hotCorner.connect('leave-event', Lang.bind(this, this._onHotCornerLeft)); + this._hotCorner._delegate = this._hotCorner; + this._hotCorner.handleDragOver = Lang.bind(this, + function(source, actor, x, y, time) { + if (source == Main.xdndHandler) { + if(!Main.overview.visible && !Main.overview.animationInProgress) { + this.rippleAnimation(); + Main.overview.showTemporarily(); + Main.overview.beginItemDrag(actor); + } + } + }); + this._boxContainer.add_actor(this._hotCornerEnvirons); this._boxContainer.add_actor(this._hotCorner); @@ -821,6 +848,25 @@ Panel.prototype = { Main.chrome.addActor(this.actor, { visibleInOverview: true }); }, + _xdndShowOverview: function (actor) { + let [x, y, mask] = global.get_pointer(); + let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); + + if (pickedActor != this.button) { + Mainloop.source_remove(this.button._xdndTimeOut); + this.button._xdndTimeOut = 0; + return; + } + + if(!Main.overview.visible && !Main.overview.animationInProgress) { + Main.overview.showTemporarily(); + Main.overview.beginItemDrag(actor); + } + + Mainloop.source_remove(this.button._xdndTimeOut); + this.button._xdndTimeOut = 0; + }, + startStatusArea: function() { for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) { let role = STANDARD_TRAY_ICON_ORDER[i]; @@ -915,6 +961,17 @@ Panel.prototype = { Main.uiGroup.add_actor(ripple); }, + rippleAnimation: function() { + // Show three concentric ripples expanding outwards; the exact + // parameters were found by trial and error, so don't look + // for them to make perfect sense mathematically + + // delay time scale opacity => scale opacity + this._addRipple(0.0, 0.83, 0.25, 1.0, 1.5, 0.0); + this._addRipple(0.05, 1.0, 0.0, 0.7, 1.25, 0.0); + this._addRipple(0.35, 1.0, 0.0, 0.3, 1, 0.0); + }, + _onHotCornerEntered : function() { if (this._menus.grabbed) return false; @@ -923,14 +980,7 @@ Panel.prototype = { if (!Main.overview.animationInProgress) { this._hotCornerActivationTime = Date.now() / 1000; - // Show three concentric ripples expanding outwards; the exact - // parameters were found by trial and error, so don't look - // for them to make perfect sense mathematically - - // delay time scale opacity => scale opacity - this._addRipple(0.0, 0.83, 0.25, 1.0, 1.5, 0.0); - this._addRipple(0.05, 1.0, 0.0, 0.7, 1.25, 0.0); - this._addRipple(0.35, 1.0, 0.0, 0.3, 1, 0.0); + this.rippleAnimation(); Main.overview.toggle(); } } diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js index 678a9e087..747800391 100644 --- a/js/ui/workspacesView.js +++ b/js/ui/workspacesView.js @@ -644,6 +644,9 @@ WorkspacesView.prototype = { }, _onDragMotion: function(dragEvent) { + if (Main.overview.animationInProgress) + return DND.DragMotionResult.CONTINUE; + let primary = global.get_primary_monitor(); let activeWorkspaceIndex = global.screen.get_active_workspace_index(); diff --git a/js/ui/xdndHandler.js b/js/ui/xdndHandler.js new file mode 100644 index 000000000..02755591f --- /dev/null +++ b/js/ui/xdndHandler.js @@ -0,0 +1,130 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const Lang = imports.lang; +const Shell = imports.gi.Shell; +const Signals = imports.signals; +const Mainloop = imports.mainloop; +const DND = imports.ui.dnd; + +function XdndHandler() { + this._init(); +} + +XdndHandler.prototype = { + _init: function() { + // Used to display a clone of the cursor window when the + // window group is hidden (like it happens in the overview) + this._cursorWindowClone = null; + + // Used as a drag actor in case we don't have a cursor window clone + this._dummy = new Clutter.Rectangle({ width: 1, height: 1, opacity: 0 }); + global.stage.add_actor(this._dummy); + this._dummy.hide(); + + // Mutter delays the creation of the output window as long + // as possible to avoid flicker. In case a plugin wants to + // access it directly it has to connect to the stage's show + // signal. (see comment in compositor.c:meta_compositor_manage_screen) + global.stage.connect('show', function () { + global.init_xdnd(); + return false; + }); + + global.connect('xdnd-enter', Lang.bind(this, this._onEnter)); + global.connect('xdnd-position-changed', Lang.bind(this, this._onPositionChanged)); + global.connect('xdnd-leave', Lang.bind(this, this._onLeave)); + + this._windowGroupVisibilityHandlerId = 0; + }, + + // Called when the user cancels the drag (i.e release the button) + _onLeave: function() { + if (this._windowGroupVisibilityHandlerId != 0) { + Mainloop.source_remove(this._windowGroupVisibilityHandlerId); + this._windowGroupVisibilityHandlerId = 0; + } + this.emit('drag-end'); + }, + + _onEnter: function() { + this._windowGroupVisibilityHandlerId = + global.window_group.connect('notify::visible', + Lang.bind(this, this._onWindowGroupVisibilityChanged)); + + this.emit('drag-begin', global.get_current_time()); + }, + + _onWindowGroupVisibilityChanged: function() { + if (!global.window_group.visible) { + if (this._cursorWindowClone) + return; + + let windows = global.get_window_actors(); + let cursorWindow = windows[windows.length - 1]; + + // FIXME: more reliable way? + if (!cursorWindow.is_override_redirect()) + return; + + let constraint_x = new Clutter.BindConstraint({ coordinate : Clutter.BindCoordinate.X, + source: cursorWindow}); + let constraint_y = new Clutter.BindConstraint({ coordinate : Clutter.BindCoordinate.Y, + source: cursorWindow}); + + this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow }); + global.overlay_group.add_actor(this._cursorWindowClone); + Shell.util_set_hidden_from_pick(this._cursorWindowClone, true); + + // Make sure that the clone has the same position as the source + this._cursorWindowClone.add_constraint(constraint_x); + this._cursorWindowClone.add_constraint(constraint_y); + } else { + if (this._cursorWindowClone) + { + this._cursorWindowClone.destroy(); + this._cursorWindowClone = null; + } + } + }, + + _onPositionChanged: function(obj, x, y) { + let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y); + + // Make sure that the cursor window is on top + if (this._cursorWindowClone) + this._cursorWindowClone.raise_top(); + + let dragEvent = { + x: x, + y: y, + dragActor: this._cursorWindowClone ? this._cursorWindowClone : this._dummy, + source: this, + targetActor: pickedActor + }; + + for (let i = 0; i < DND.dragMonitors.length; i++) { + let motionFunc = DND.dragMonitors[i].dragMotion; + if (motionFunc) { + let result = motionFunc(dragEvent); + if (result != DND.DragMotionResult.CONTINUE) + return; + } + } + + while (pickedActor) { + if (pickedActor._delegate && pickedActor._delegate.handleDragOver) { + let result = pickedActor._delegate.handleDragOver(this, + dragEvent.dragActor, + x, + y, + global.get_current_time()); + if (result != DND.DragMotionResult.CONTINUE) + return; + } + pickedActor = pickedActor.get_parent(); + } + } +} + +Signals.addSignalMethods(XdndHandler.prototype); diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index ec23bf317..50e2c5cbf 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -113,6 +113,8 @@ struct _GnomeShellPlugin int glx_error_base; int glx_event_base; guint have_swap_event : 1; + + ShellGlobal *global; }; struct _GnomeShellPluginClass @@ -320,7 +322,6 @@ gnome_shell_plugin_start (MetaPlugin *plugin) int status; const char *shell_js; char **search_path; - ShellGlobal *global; const char *glx_extensions; bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); @@ -379,10 +380,10 @@ gnome_shell_plugin_start (MetaPlugin *plugin) gvc_muted_debug_log_handler, NULL); /* Initialize the global object here. */ - global = shell_global_get (); + shell_plugin->global = shell_global_get (); - _shell_global_set_plugin (global, META_PLUGIN(shell_plugin)); - _shell_global_set_gjs_context (global, shell_plugin->gjs_context); + _shell_global_set_plugin (shell_plugin->global, META_PLUGIN(shell_plugin)); + _shell_global_set_gjs_context (shell_plugin->global, shell_plugin->gjs_context); add_statistics (shell_plugin); @@ -511,9 +512,9 @@ static gboolean gnome_shell_plugin_xevent_filter (MetaPlugin *plugin, XEvent *xev) { -#ifdef GLX_INTEL_swap_event - GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin); + GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin); +#ifdef GLX_INTEL_swap_event if (shell_plugin->have_swap_event && xev->type == (shell_plugin->glx_event_base + GLX_BufferSwapComplete)) { @@ -545,6 +546,12 @@ gnome_shell_plugin_xevent_filter (MetaPlugin *plugin, && xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (clutter_stage_get_default ()))) return TRUE; + /* + * Pass the event to shell-global + */ + if (_shell_global_check_xdnd_event (shell_plugin->global, xev)) + return TRUE; + return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE; } diff --git a/src/shell-global-private.h b/src/shell-global-private.h index 201ace86d..e17514530 100644 --- a/src/shell-global-private.h +++ b/src/shell-global-private.h @@ -11,4 +11,6 @@ void _shell_global_set_plugin (ShellGlobal *global, void _shell_global_set_gjs_context (ShellGlobal *global, GjsContext *context); +gboolean _shell_global_check_xdnd_event (ShellGlobal *global, + XEvent *xev); #endif /* __SHELL_GLOBAL_PRIVATE_H__ */ diff --git a/src/shell-global.c b/src/shell-global.c index 2cf6ec689..4c3246f20 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -6,6 +6,7 @@ #include "shell-enum-types.h" #include "shell-perf-log.h" #include "shell-window-tracker.h" +#include "shell-marshal.h" #include "shell-wm.h" #include "st.h" @@ -70,6 +71,8 @@ struct _ShellGlobal { /* For sound notifications */ ca_context *sound_context; + + guint32 xdnd_timestamp; }; enum { @@ -92,8 +95,19 @@ enum { PROP_FOCUS_MANAGER, }; +/* Signals */ +enum +{ + XDND_POSITION_CHANGED, + XDND_LEAVE, + XDND_ENTER, + LAST_SIGNAL +}; + G_DEFINE_TYPE(ShellGlobal, shell_global, G_TYPE_OBJECT); +static guint shell_global_signals [LAST_SIGNAL] = { 0 }; + static void shell_global_set_property(GObject *object, guint prop_id, @@ -238,6 +252,36 @@ shell_global_class_init (ShellGlobalClass *klass) gobject_class->get_property = shell_global_get_property; gobject_class->set_property = shell_global_set_property; + /* Emitted from gnome-shell-plugin.c during event handling */ + shell_global_signals[XDND_POSITION_CHANGED] = + g_signal_new ("xdnd-position-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _shell_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + + /* Emitted from gnome-shell-plugin.c during event handling */ + shell_global_signals[XDND_LEAVE] = + g_signal_new ("xdnd-leave", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /* Emitted from gnome-shell-plugin.c during event handling */ + shell_global_signals[XDND_ENTER] = + g_signal_new ("xdnd-enter", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + g_object_class_install_property (gobject_class, PROP_OVERLAY_GROUP, g_param_spec_object ("overlay-group", @@ -1130,6 +1174,39 @@ grab_notify (GtkWidget *widget, gboolean was_grabbed, gpointer user_data) shell_global_set_stage_input_mode (global, global->input_mode); } +/** + * shell_global_init_xdnd: + * @global: the #ShellGlobal + * + * Enables tracking of Xdnd events + */ +void shell_global_init_xdnd (ShellGlobal *global) +{ + long xdnd_version = 5; + + MetaScreen *screen = shell_global_get_screen (global); + Window output_window = meta_get_overlay_window (screen); + + MetaDisplay *display = meta_screen_get_display (screen); + Display *xdisplay = meta_display_get_xdisplay (display); + + ClutterStage *stage = CLUTTER_STAGE(meta_plugin_get_stage (global->plugin)); + Window stage_win = clutter_x11_get_stage_window (stage); + + XChangeProperty (xdisplay, stage_win, gdk_x11_get_xatom_by_name ("XdndAware"), XA_ATOM, + 32, PropModeReplace, (const unsigned char *)&xdnd_version, 1); + + XChangeProperty (xdisplay, output_window, gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW, + 32, PropModeReplace, (const unsigned char *)&stage_win, 1); + + /* + * XdndProxy is additionally set on the proxy window as verification that the + * XdndProxy property on the target window isn't a left-over + */ + XChangeProperty (xdisplay, stage_win, gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW, + 32, PropModeReplace, (const unsigned char *)&stage_win, 1); +} + /** * shell_global_format_time_relative_pretty: * @global: @@ -1422,6 +1499,10 @@ shell_global_get_current_time (ShellGlobal *global) guint32 time; MetaDisplay *display; + /* In case we have a xdnd timestamp use it */ + if (global->xdnd_timestamp != 0) + return global->xdnd_timestamp; + /* meta_display_get_current_time() will return the correct time when handling an X or Gdk event, but will return CurrentTime from some Clutter event callbacks. @@ -1689,3 +1770,67 @@ shell_global_play_theme_sound (ShellGlobal *global, { ca_context_play (global->sound_context, 0, CA_PROP_EVENT_ID, name, NULL); } + +/* + * Process Xdnd events + * + * We pass the position and leave events to JS via a signal + * where the actual drag & drop handling happens. + * + * http://www.freedesktop.org/wiki/Specifications/XDND + */ +gboolean _shell_global_check_xdnd_event (ShellGlobal *global, + XEvent *xev) +{ + MetaScreen *screen = meta_plugin_get_screen (global->plugin); + Window output_window = meta_get_overlay_window (screen); + MetaDisplay *display = meta_screen_get_display (screen); + Display *xdisplay = meta_display_get_xdisplay (display); + + ClutterStage *stage = CLUTTER_STAGE (meta_plugin_get_stage (global->plugin)); + Window stage_win = clutter_x11_get_stage_window (stage); + + if (xev->xany.window != output_window && xev->xany.window != stage_win) + return FALSE; + + if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndPosition")) + { + XEvent xevent; + Window src = xev->xclient.data.l[0]; + + memset (&xevent, 0, sizeof(xevent)); + xevent.xany.type = ClientMessage; + xevent.xany.display = xdisplay; + xevent.xclient.window = src; + xevent.xclient.message_type = gdk_x11_get_xatom_by_name ("XdndStatus"); + xevent.xclient.format = 32; + xevent.xclient.data.l[0] = output_window; + /* flags: bit 0: will we accept the drop? bit 1: do we want more position messages */ + xevent.xclient.data.l[1] = 2; + xevent.xclient.data.l[4] = None; + + XSendEvent (xdisplay, src, False, 0, &xevent); + + /* Store the timestamp of the xdnd position event */ + global->xdnd_timestamp = xev->xclient.data.l[3]; + g_signal_emit_by_name (G_OBJECT (global), "xdnd-position-changed", + (int)(xev->xclient.data.l[2] >> 16), (int)(xev->xclient.data.l[2] & 0xFFFF)); + global->xdnd_timestamp = 0; + + return TRUE; + } + else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndLeave")) + { + g_signal_emit_by_name (G_OBJECT (global), "xdnd-leave"); + + return TRUE; + } + else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndEnter")) + { + g_signal_emit_by_name (G_OBJECT (global), "xdnd-enter"); + + return TRUE; + } + + return FALSE; +} diff --git a/src/shell-global.h b/src/shell-global.h index 91a82c454..4a40bcd53 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -132,6 +132,8 @@ void shell_global_run_at_leisure (ShellGlobal *global, void shell_global_play_theme_sound (ShellGlobal *global, const char *name); +void shell_global_init_xdnd (ShellGlobal *global); + G_END_DECLS #endif /* __SHELL_GLOBAL_H__ */ diff --git a/src/shell-marshal.list b/src/shell-marshal.list index 588693dc1..34d107884 100644 --- a/src/shell-marshal.list +++ b/src/shell-marshal.list @@ -4,3 +4,4 @@ VOID:BOXED VOID:BOXED,OBJECT VOID:OBJECT,OBJECT VOID:STRING,OBJECT,BOOLEAN +VOID:INT,INT