Глобальная горячая клавиша в Mono и Gtk # - PullRequest
4 голосов
/ 28 февраля 2010

Я пытаюсь заставить глобальную горячую клавишу работать в Linux, используя Mono. Я нашел подписи XGrabKey и XUngrabKey, но я не могу заставить их работать. Всякий раз, когда я пытаюсь вызвать XGrabKey, приложение падает с SIGSEGV.

Это то, что я имею до сих пор:

using System;
using Gtk;
using System.Runtime.InteropServices;

namespace GTKTest
{
    class MainClass
    {
        const int GrabModeAsync = 1;

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

            MainWindow win = new MainWindow();
            win.Show();

            // Crashes here
            XGrabKey(
             win.Display.Handle,
             (int)Gdk.Key.A,
             (uint)KeyMasks.ShiftMask,
             win.Handle,
             true,
             GrabModeAsync,
             GrabModeAsync);

            Application.Run();

            XUngrabKey(
             win.Display.Handle,
             (int)Gdk.Key.A,
             (uint)KeyMasks.ShiftMask,
             win.Handle);
        }


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

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

    public enum KeyMasks
    {
        ShiftMask = (1 << 0),
        LockMask = (1 << 1),
        ControlMask = (1 << 2),
        Mod1Mask = (1 << 3),
        Mod2Mask = (1 << 4),
        Mod3Mask = (1 << 5),
        Mod4Mask = (1 << 6),
        Mod5Mask = (1 << 7)
    }
}

У кого-нибудь есть рабочий пример XGrabKey?

Спасибо!

Ответы [ 3 ]

7 голосов
/ 07 марта 2010

Ну, я наконец-то нашел работающее решение в управляемом коде. SIGSEGV происходил потому, что я путал дескрипторы неуправляемых объектов Gdk с дескрипторами их аналогов X11. Благодаря ответу Пола, я смог найти неуправляемый пример глобальных горячих клавиш и ознакомился с тем, как это работает. Затем я написал свою собственную неуправляемую тестовую программу, чтобы выяснить, что мне нужно делать, не сталкиваясь с какими-либо управляемыми идиосинкразиями. После этого я создал управляемое решение.

Вот управляемое решение:

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      

}

А вот и тестовая программа:

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!");
}

Я не уверен, как структура XKeyEvent будет вести себя в других системах с разными размерами для C int s и long s, поэтому покажет, будет ли это решение работать на всех системах.

Редактировать: Похоже, что это решение не будет независимым от архитектуры, как я опасался, из-за меняющейся природы базовых размеров типа C. libgtkhotkey выглядит многообещающе как способ избежать развертывания и компиляции пользовательских неуправляемых библиотек с вашими управляемыми сборками.

Примечание: Теперь вам нужно подробно определить BUILD_FOR_32_BIT_X11 или BUILD_FOR_64_BIT_X11 в зависимости от размера слова вашей ОС.

2 голосов
/ 26 марта 2012

Я новичок в этом сайте, и мне кажется, что я не могу оставить комментарий к предыдущему вопросу, поскольку у меня недостаточно репутации. (Извините, я даже не могу проголосовать за вас!)

Что касается вопроса о различающихся базовых размерах, я думаю, что это можно решить, используя IntPtr для длинных позиций. Это следует предложению в документации проекта Mono, см. http://www.mono -project.com / Interop_with_Native_Libraries # Longs . Типы C int и Bool должны отображаться в C # int.

Что касается оболочки GAPI, я попробовал, но не смог заставить ее работать. Если бы Зак мог опубликовать какую-либо информацию о том, как он это сделал, я был бы благодарен.

Также я не смог заставить программу-пример работать. Как и SDX2000, мне пришлось редактировать имена библиотек, и я добавил операторы. У меня была проблема с Application.Init (), который в конце концов я заменил для создания формы. Но все равно мой регистрационный вызов не удается с BadRequest. Если кто-то, у кого есть эта работа, может обновить код, чтобы сделать его более полным, я был бы благодарен.

1 голос
/ 28 февраля 2010

У Tomboy есть код, который знает, как это сделать, я бы взял код оттуда.

...