using Gtk;

static Gtk.Menu menu;

/*
Compile with
valac --pkg gtk+-3.0 --pkg gdk-x11-3.0 --pkg x11 gtk-popup-issue.vala

This really quick examples shows that the gtk menu does not popup if another
xevent follows after the popup.
*/

int main (string[] args) {
    Gtk.init (ref args);

    var window = new Window ();
    window.title = "Cause Popup issue";
    window.border_width = 10;
    window.window_position = WindowPosition.CENTER;
    window.set_default_size (350, 70);
    window.destroy.connect (Gtk.main_quit);

    var keybinding_entry = new Gtk.Entry();
    keybinding_entry.text = "<Ctrl><Alt>V";

    var button = new Button.with_label ("Bind hotkey");
    button.clicked.connect (() => {
	bind(keybinding_entry.text);
        button.label = "Now press " + keybinding_entry.text;
    });

    var hbox = new Box (Orientation.HORIZONTAL, 20);
    hbox.add (keybinding_entry);
    hbox.add (button);
    window.add (hbox);
    window.show_all ();

    menu = new Gtk.Menu();
    var item = new Gtk.MenuItem.with_label("Test");
    menu.append(item);
    menu.show_all();

    Gdk.Window rootwin = Gdk.get_default_root_window();
    if(rootwin != null) {
        rootwin.add_filter(event_filter);
    }

    Gtk.main ();
    return 0;
}

Gdk.FilterReturn event_filter(Gdk.XEvent gdk_xevent, Gdk.Event gdk_event) {
    X.Event* xevent = (X.Event*) gdk_xevent;
    if(xevent->type == X.EventType.KeyPress) {
        warning("Press event received");
        menu.popup(null, null, null, 0, gdk_event.get_time());
        warning("Popup should be shown now");
        return Gdk.FilterReturn.REMOVE;
    } else if (xevent->type == X.EventType.KeyRelease) {
        warning("Key release event received");
    }

    return Gdk.FilterReturn.CONTINUE;
}

void bind(string accelerator) {
    uint[] lock_modifiers = {
        0,
        Gdk.ModifierType.MOD2_MASK, // NUM_LOCK
        Gdk.ModifierType.LOCK_MASK, // CAPS_LOCK
        Gdk.ModifierType.MOD5_MASK, // SCROLL_LOCK
        Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK,
        Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.MOD5_MASK,
        Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK,
        Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK
    };

    // convert accelerator
    uint keysym;
    Gdk.ModifierType modifiers;
    Gtk.accelerator_parse(accelerator, out keysym, out modifiers);

    unowned X.Display display = Gdk.X11.get_default_xdisplay();
    int keycode = display.keysym_to_keycode(keysym);
    if(keycode != 0) {
        X.Window root_window = Gdk.X11.get_default_root_xwindow();
        // trap XErrors to avoid closing of application
        // even when grabing of key fails
        Gdk.error_trap_push();

        // grab key finally
        // also grab all keys which are combined with a lock key such NumLock
        foreach(uint lock_modifier in lock_modifiers) {
            display.grab_key(keycode, modifiers|lock_modifier, root_window, false,
                X.GrabMode.Async, X.GrabMode.Async);
        }

        // wait until all X request have been processed
        Gdk.flush();
    }
}
