Global Hotkey in Mono and Gtk#

佐手、 提交于 2019-11-29 05:02:56
Zach Johnson

Well, I finally found a working solution in managed code. The SIGSEGV was happening because I was confusing the handles of the unmanaged Gdk objects with the handles of their X11 counterparts. Thanks to Paul's answer, I was able to find an unmanaged example of global hotkeys and familiarized myself with how it worked. Then I wrote my own unmanaged test program to find out what I needed to do without having to deal with any managed idiosyncrasies. After that was successful, I created a managed solution.

Here is the managed solution:

public class X11Hotkey
{
    private const int KeyPress = 2;
    private const int GrabModeAsync = 1;
    private Gdk.Key key;
    private Gdk.ModifierType modifiers;
    private int keycode;

    public X11Hotkey(Gdk.Key key, Gdk.ModifierType modifiers)
    {
        this.key = key;
        this.modifiers = modifiers;

        Gdk.Window rootWin = Gdk.Global.DefaultRootWindow;
        IntPtr xDisplay = GetXDisplay(rootWin);
        this.keycode = XKeysymToKeycode(xDisplay, (int)this.key);
        rootWin.AddFilter(new Gdk.FilterFunc(FilterFunction));
    }

    public event EventHandler Pressed;

    public void Register()
    {
        Gdk.Window rootWin = Gdk.Global.DefaultRootWindow;
        IntPtr xDisplay = GetXDisplay(rootWin);

        XGrabKey(
                 xDisplay,
                 this.keycode,
                 (uint)this.modifiers,
                 GetXWindow(rootWin),
                 false,
                 GrabModeAsync,
                 GrabModeAsync);     
    }

    public void Unregister()
    {
        Gdk.Window rootWin = Gdk.Global.DefaultRootWindow;
        IntPtr xDisplay = GetXDisplay(rootWin);

        XUngrabKey(
                 xDisplay,
                 this.keycode,
                 (uint)this.modifiers,
                 GetXWindow(rootWin));
    }

    private Gdk.FilterReturn FilterFunction(IntPtr xEvent, Gdk.Event evnt)
    {
        XKeyEvent xKeyEvent = (XKeyEvent)Marshal.PtrToStructure(
            xEvent, 
            typeof(XKeyEvent));

        if (xKeyEvent.type == KeyPress)
        {
            if (xKeyEvent.keycode == this.keycode 
                && xKeyEvent.state == (uint)this.modifiers)
            {
                this.OnPressed(EventArgs.Empty);
            }
        }

        return Gdk.FilterReturn.Continue;
    }

    protected virtual void OnPressed(EventArgs e)
    {
        EventHandler handler = this.Pressed;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    private static IntPtr GetXWindow(Gdk.Window window)
    {
        return gdk_x11_drawable_get_xid(window.Handle);
    }

    private static IntPtr GetXDisplay(Gdk.Window window)
    {
        return gdk_x11_drawable_get_xdisplay(
            gdk_x11_window_get_drawable_impl(window.Handle));
    }

    [DllImport("libgtk-x11-2.0")]
    private static extern IntPtr gdk_x11_drawable_get_xid(IntPtr gdkWindow); 

    [DllImport("libgtk-x11-2.0")]
    private static extern IntPtr gdk_x11_drawable_get_xdisplay(IntPtr gdkDrawable);

    [DllImport("libgtk-x11-2.0")]
    private static extern IntPtr gdk_x11_window_get_drawable_impl(IntPtr gdkWindow);

    [DllImport("libX11")]
    private static extern int XKeysymToKeycode(IntPtr display, int key);

    [DllImport("libX11")]
    private static extern int XGrabKey(
        IntPtr display, 
        int keycode, 
        uint modifiers, 
        IntPtr grab_window, 
        bool owner_events, 
        int pointer_mode, 
        int keyboard_mode);

    [DllImport("libX11")]
    private static extern int XUngrabKey(
        IntPtr display, 
        int keycode, 
        uint modifiers, 
        IntPtr grab_window);

#if BUILD_FOR_32_BIT_X11        

    [StructLayout(LayoutKind.Sequential)]
    internal struct XKeyEvent
    {
        public short type;
        public uint serial;
        public short send_event;
        public IntPtr display;
        public uint window;
        public uint root;
        public uint subwindow;
        public uint time;
        public int x, y;
        public int x_root, y_root;
        public uint state;
        public uint keycode;
        public short same_screen;
    }       
#elif BUILD_FOR_64_BIT_X11

    [StructLayout(LayoutKind.Sequential)]
    internal struct XKeyEvent
    {
        public int type;
        public ulong serial;
        public int send_event;
        public IntPtr display;
        public ulong window;
        public ulong root;
        public ulong subwindow;
        public ulong time;
        public int x, y;
        public int x_root, y_root;
        public uint state;
        public uint keycode;
        public int same_screen;
    }
#endif      

}

And here is the test program:

public static void Main (string[] args)
{
    Application.Init();

    X11Hotkey hotkey = new X11Hotkey(Gdk.Key.A, Gdk.ModifierType.ControlMask);
    hotkey.Pressed += HotkeyPressed;;
    hotkey.Register();

    Application.Run();

    hotkey.Unregister();
}

private static void HotkeyPressed(object sender, EventArgs e)
{
    Console.WriteLine("Hotkey Pressed!");
}

I'm not sure how the XKeyEvent structure will behave on other systems with different sizes for C ints and longs, so whether this solution will work on all systems remains to be seen.

Edit: It looks like this solution is not going to be architecture-independent as I feared, due to the varying nature of the underlying C type sizes. libgtkhotkey looks promising as way to avoid deploying and compiling custom unmanaged libraries with your managed assemblies.

Note: Now you need to explicity define BUILD_FOR_32_BIT_X11 or BUILD_FOR_64_BIT_X11 depending on the word-size of your OS.

I'm new to this site and it seems that I can't leave a comment on a previous question as I have insufficient reputation. (Sorry I can't even up-vote you!)

Relating to the issue of differing underlying sizes, I think this is solvable by using an IntPtr for the longs. This follows a suggestion in the Mono project documentation, see http://www.mono-project.com/Interop_with_Native_Libraries#Longs. The C types int and Bool should map to C# int.

Regarding the GAPI wrapper, I tried it, but couldn't get it working. If Zach could post any info on how he did it, I'd be grateful.

Also I couldn't get the sample program to work. Like SDX2000, I had to edit the library names, and I added using statements. I had a problem with Application.Init(), which in the end I swapped for creating a Form. But still my register call fails with BadRequest. If anyone who has got this working can update the code to make it more complete I'd be grateful.

Tomboy has some code that knows how to do this, I'd take the code from there.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!