Invalid CONSOLE_SCREEN_BUFFER_INFOEX; GetConsoleScreenBufferInfoEx (kernel32) - c#

GetConsoleScreenBufferInfoEx invalid return value
I am trying to change the console's color palette. To get this done I first need to Get my ConsoleScreenBufferInfoEx and then Set it. The problem is that I can't even get a valid ConsoleScreenBufferInfoEx from the STD_OUTPUT_HANDLE.
The code below throws this error message:
System.ArgumentException: 'Value does not fall within the expected range.'
The handle is valid and yet I get this error. I have quadruple-checked every data type and related pinvoke entry - everything is looking good to me. There is no sample code for GetConsoleScreenBufferInfoEx and I haven't been able to find a working solution yet.
My sources:
pinvoke: ConsoleFunctions (kernel32)
msdocs: CONSOLE_SCREEN_BUFFER_INFOEX structure
msdocs: COLORREF
Example App (.NET Core 3.1):
For this code to work, the project's build properties must allow unsafe code.
[Properties -> Build -> Allow unsafe code]
using Microsoft.Win32.SafeHandles;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace ScreenBufferInfoExample
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern SafeFileHandle GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern unsafe bool GetConsoleScreenBufferInfoEx(SafeFileHandle hConsoleOutput,
out CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfo);
static void Main()
{
SafeFileHandle stdOut = GetStdHandle(-11);
if(stdOut.IsInvalid)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
CONSOLE_SCREEN_BUFFER_INFO_EX info = new CONSOLE_SCREEN_BUFFER_INFO_EX();
info.cbSize = (uint)Marshal.SizeOf(info);
if(!GetConsoleScreenBufferInfoEx(stdOut, out info)) {
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());// <- this gets thrown
// System.ArgumentException: 'Value does not fall within the expected range.'
}
Console.ReadKey(true);
}
}
[StructLayout(LayoutKind.Sequential)]
struct CONSOLE_SCREEN_BUFFER_INFO_EX
{
public uint cbSize;
public COORD dwSize;
public COORD dwCursorPosition;
public ushort wAttributes;
public SMALL_RECT srWindow;
public COORD dwMaximumWindowSize;
public ushort wPopupAttributes;
public bool bFullscreenSupported;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public COLORREF[] ColorTable;
}
[StructLayout(LayoutKind.Sequential)]
struct COORD
{
public short X;
public short Y;
}
[StructLayout(LayoutKind.Sequential)]
struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
[StructLayout(LayoutKind.Sequential)]
struct COLORREF
{
public uint ColorDWORD;
public COLORREF(int r, int g, int b)
: this(Color.FromArgb(r, g, b)) { }
public COLORREF(Color color)
{
ColorDWORD = (uint)color.R
+ (((uint)color.G) << 8)
+ (((uint)color.B) << 16);
}
public Color GetColor()
{
return Color.FromArgb((int)(0x000000FFU & ColorDWORD),
(int)(0x0000FF00U & ColorDWORD) >> 8,
(int)(0x00FF0000U & ColorDWORD) >> 16);
}
public void SetColor(Color color)
{
ColorDWORD = (uint)color.R
+ (((uint)color.G) << 8)
+ (((uint)color.B) << 16);
}
}
}

pinvoke.net is often useful, but not always right.
As you need to pass information in and out of the GetConsoleScreenBufferInfoEx method, the parameter cannot be out, but needs to be ref.
So the correct declaration would be
[DllImport("kernel32.dll", SetLastError = true)]
public static extern unsafe bool GetConsoleScreenBufferInfoEx(SafeFileHandle hConsoleOutput,
ref CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfo);
called as GetConsoleScreenBufferInfoEx(stdOut, ref info).
(I just corrected the page on pinvoke.net accordingly)

Related

Read an exact one struct field

Here is the code to get system information:
using System;
using System.Runtime.InteropServices;
public class win{
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO{
public ushort wProcessorArchitecture;
public ushort wReserved;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public IntPtr lpMaximumApplicationAddress;
public UIntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort wProcessorLevel;
public ushort wProcessorRevision;
};
[DllImport("kernel32.dll")]
public static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);
public static void Main(){
var sysInfo = new SYSTEM_INFO();
GetNativeSystemInfo(ref sysInfo);
var res = sysInfo.wProcessorArchitecture;
Console.WriteLine(res);
}
}
Output is 9.
Is there a way to read exact one struct field, without define all struct?
For example, get value of first field:
using System;
using System.Runtime.InteropServices;
public class win{
[DllImport("kernel32.dll")]
public static extern void GetNativeSystemInfo(IntPtr lpSystemInfo);
public static void Main(){
var sysInfo = Marshal.AllocHGlobal(96);
GetNativeSystemInfo(sysInfo);
var res = Marshal.ReadByte(sysInfo, 0);
Console.WriteLine(res);
}
}
Output is 9. But how to get value of, for example, third field? May be there is some marker inside buffer, that can tell me, that new field begins?
How to get value of, for example, third field?
Just set the offset after the first two fields:
using System;
using System.Runtime.InteropServices;
public class win {
[DllImport("kernel32.dll")]
public static extern void GetNativeSystemInfo(IntPtr lpSystemInfo);
public static void Main() {
var offset = 4;
var sysInfo = Marshal.AllocHGlobal(offset + 4);
GetNativeSystemInfo(sysInfo);
var res = Marshal.ReadInt32(sysInfo, offset);
Marshal.FreeHGlobal(sysInfo);
Console.WriteLine(res);
}
}
Output: 4096 ( dwPageSize )

WriteConsoleOutput() from Kernel32 not displaying text

I'm currently writing a small utility library to help improve performance when writing to the console, and I'm facing an issue where it fails to actually output any text. Below is my code:
public static class QuickDraw
{
private static short Width => (short)Console.WindowWidth;
private static short Height => (short)Console.WindowHeight;
private static SafeFileHandle Handle =>
Kernel32.CreateFile("$CONOUT", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
private static Kernel32.Coord Cursor =>
new Kernel32.Coord((short)Console.CursorLeft, (short)Console.CursorTop);
private static Kernel32.SmallRect WriteRegion =>
new Kernel32.SmallRect() { Left = 0, Top = 0, Right = Width, Bottom = Height };
private static Kernel32.Coord BufferSize =>
new Kernel32.Coord(Width, Height);
private static Kernel32.Coord BufferCoord =>
new Kernel32.Coord(0, 0);
public static void Write(char[] text, ConsoleColor fg, ConsoleColor bg)
{
Kernel32.CharInfo[] buffer = new Kernel32.CharInfo[Width * Height];
Kernel32.Coord cursor = Cursor;
for (int i = 0; i < text.Length; i++)
{
if (text[i] == '\n')
{
cursor.X = 0;
cursor.Y++;
}
else
{
int index = (cursor.Y * Width) + cursor.X;
// Set character
buffer[index].Char.AsciiChar = (byte)text[i];
// Set color
// (Crazy heckin bitwise crap, don't touch.)
buffer[index].Attributes = (short)((int)fg | ((int)bg | (2 << 4)));
// Increment cursor
cursor.X++;
}
// Make sure that cursor does not exceed bounds of window
if (cursor.X >= Width)
{
cursor.X = 0;
cursor.Y++;
}
if (cursor.Y >= Height)
{
cursor.Y = 0;
}
}
var writeRegion = WriteRegion;
Kernel32.WriteConsoleOutput(Handle, buffer, BufferSize, BufferCoord, ref writeRegion);
Console.SetCursorPosition(cursor.X, cursor.Y);
}
}
// Taken from https://stackoverflow.com/a/2754674/7937949
internal static class Kernel32
{
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile(
string fileName,
[MarshalAs(UnmanagedType.U4)] uint fileAccess,
[MarshalAs(UnmanagedType.U4)] uint fileShare,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] int flags,
IntPtr template);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
public short X;
public short Y;
public Coord(short x, short y)
{
X = x;
Y = y;
}
}
[StructLayout(LayoutKind.Explicit)]
public struct CharUnion
{
[FieldOffset(0)] public char UnicodeChar;
[FieldOffset(0)] public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
public struct CharInfo
{
[FieldOffset(2)] public CharUnion Char;
[FieldOffset(2)] public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
}
I've mostly taken the implementation from this post, adding a new class to simplify its use. I'm not entirely sure where my issue is, as my debugger is not working right, but I'm pretty sure it's in my implementation in QuickDraw.
When I try to use QuickDraw.Write(), the cursor moves to the end of whatever string it was trying to print, but nothing actually shows up. What am I doing wrong?
First of all, you didn't copy the code right. There are some mistakes.
in CharInfo change
[FieldOffset(2)] public CharUnion Char;
to
[FieldOffset(0)] public CharUnion Char;
Change $CONOUT to this CONOUT$
First set the Attribute and then the AsciiChar
And of course, if you want the correct foreground-/background color representation then you have to delete the 2 << 4. I don't even know why you put it there.

WaitForDebugEvent (kernel32.dll) bug or what?

i'm new and i need your help resolving this issue.
I'm trying to create a simple debugger to understand how debuggers works and how is loaded exes in memory. I have already written the code which works as well, but now there is the problem: when i try to call WaitForDebugEvent (a kernel32 function) to get the debug event it works, in fact the debug_event variable is written, but this function clears all the variables in my application. So it clear also:
this (current form)
EventArgs (arguments of my form load function)
object sender (the object who called the function)
So i can't continue executing my app because all the vars were deleted. I wouldn't think this is a kernel32 or a Visual Studio bug...
This is the code:
(Structs and imports got from pInvoke.net and MSDN)
[DllImport("kernel32.dll")]
static extern bool DebugActiveProcess(uint dwProcessId);
[DllImport("kernel32.dll", EntryPoint = "WaitForDebugEvent")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool WaitForDebugEvent([In] ref DEBUG_EVENT lpDebugEvent, uint dwMilliseconds);
[DllImport("kernel32.dll")]
static extern bool ContinueDebugEvent(uint dwProcessId, uint dwThreadId, uint dwContinueStatus);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DebugActiveProcessStop([In] int Pid);
public struct DEBUG_EVENT
{
public int dwDebugEventCode;
public int dwProcessId;
public int dwThreadId;
public struct u
{
public EXCEPTION_DEBUG_INFO Exception;
public CREATE_THREAD_DEBUG_INFO CreateThread;
public CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
public EXIT_THREAD_DEBUG_INFO ExitThread;
public EXIT_PROCESS_DEBUG_INFO ExitProcess;
public LOAD_DLL_DEBUG_INFO LoadDll;
public UNLOAD_DLL_DEBUG_INFO UnloadDll;
public OUTPUT_DEBUG_STRING_INFO DebugString;
public RIP_INFO RipInfo;
};
};
[StructLayout(LayoutKind.Sequential)]
public struct EXCEPTION_DEBUG_INFO
{
public EXCEPTION_RECORD ExceptionRecord;
public uint dwFirstChance;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXCEPTION_RECORD
{
public uint ExceptionCode;
public uint ExceptionFlags;
public IntPtr ExceptionRecord;
public IntPtr ExceptionAddress;
public uint NumberParameters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15, ArraySubType = UnmanagedType.U4)]
public uint[] ExceptionInformation;
}
public delegate uint PTHREAD_START_ROUTINE(IntPtr lpThreadParameter);
[StructLayout(LayoutKind.Sequential)]
public struct CREATE_THREAD_DEBUG_INFO
{
public IntPtr hThread;
public IntPtr lpThreadLocalBase;
public PTHREAD_START_ROUTINE lpStartAddress;
}
//public delegate uint PTHREAD_START_ROUTINE(IntPtr lpThreadParameter);
[StructLayout(LayoutKind.Sequential)]
public struct CREATE_PROCESS_DEBUG_INFO
{
public IntPtr hFile;
public IntPtr hProcess;
public IntPtr hThread;
public IntPtr lpBaseOfImage;
public uint dwDebugInfoFileOffset;
public uint nDebugInfoSize;
public IntPtr lpThreadLocalBase;
public PTHREAD_START_ROUTINE lpStartAddress;
public IntPtr lpImageName;
public ushort fUnicode;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXIT_THREAD_DEBUG_INFO
{
public uint dwExitCode;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXIT_PROCESS_DEBUG_INFO
{
public uint dwExitCode;
}
[StructLayout(LayoutKind.Sequential)]
public struct LOAD_DLL_DEBUG_INFO
{
public IntPtr hFile;
public IntPtr lpBaseOfDll;
public uint dwDebugInfoFileOffset;
public uint nDebugInfoSize;
public IntPtr lpImageName;
public ushort fUnicode;
}
[StructLayout(LayoutKind.Sequential)]
public struct UNLOAD_DLL_DEBUG_INFO
{
public IntPtr lpBaseOfDll;
}
[StructLayout(LayoutKind.Sequential)]
public struct OUTPUT_DEBUG_STRING_INFO
{
[MarshalAs(UnmanagedType.LPStr)]
public string lpDebugStringData;
public ushort fUnicode;
public ushort nDebugStringLength;
}
[StructLayout(LayoutKind.Sequential)]
public struct RIP_INFO
{
public uint dwError;
public uint dwType;
}
And the main loop of debugger:
private void Form1_Load(object sender, EventArgs e)
{
DebugActiveProcess((uint)Process.GetProcessesByName("notepad")[0].Id);
DEBUG_EVENT debug_event = new DEBUG_EVENT();
CONTEXT context = new CONTEXT();
context.ContextFlags = (uint)CONTEXT_FLAGS.CONTEXT_ALL;
while (true)
{
unchecked
{
if (WaitForDebugEvent(ref debug_event, (uint)double.PositiveInfinity))
{
...
ContinueDebugEvent((uint)debug_event.dwProcessId, (uint)debug_event.dwThreadId, (uint)0x10002);
}
}
}
}
What can i do? Thanks in advance...
EDIT:
I have rewritten the code in C++ to see if the problem there was there too. But there was no issue... so i think the problem lays only in C#. This is the code in C++:
IMPORTS:
#include <windows.h>
#include <tlhelp32.h>
#include <msclr\marshal_cppstd.h>
CODE:
private: System::Void DebuggerForm_Load(System::Object^ sender, System::EventArgs^ e) {
std::wstring processName = msclr::interop::marshal_as<std::wstring, String^>("myExe.exe");
DWORD id = getProcessId(processName);
if (id == 0) std::exit(0);
DebugActiveProcess(id);
DEBUG_EVENT debug_event = { 0 };
while (true)
{
if (WaitForDebugEvent(&debug_event, INFINITE)) {
// TODO
ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, DBG_CONTINUE);
}
}
}
DWORD getProcessId(const std::wstring& processName)
{
PROCESSENTRY32 processInfo;
processInfo.dwSize = sizeof(processInfo);
HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (processesSnapshot == INVALID_HANDLE_VALUE)
return 0;
Process32First(processesSnapshot, &processInfo);
if (!processName.compare(processInfo.szExeFile))
{
CloseHandle(processesSnapshot);
return processInfo.th32ProcessID;
}
while (Process32Next(processesSnapshot, &processInfo))
{
if (!processName.compare(processInfo.szExeFile))
{
CloseHandle(processesSnapshot);
return processInfo.th32ProcessID;
}
}
CloseHandle(processesSnapshot);
return 0;
}
First of all, You should enable the SE_DEBUG_NAME privilege on the targeted process:
(SE_DEBUG_NAME = "SeDebugPrivilege")
Use OpenProcessToken with TOKEN_ADJUST_PRIVILEGES and TOKEN_QUERY access flags.
Use LookupPrivilegeValue to retrieve the LUID of the SE_DEBUG_NAME privilege name.
Use AdjustTokenPrivileges to enable the SE_DEBUG_NAME privilege.
CloseHandle
For the 3rd step you need a TOKEN_PRIVILEGES structure, where the PrivilegesCount field must set to 1. Also You must set the first(zero index) item of the Privilege field (Luid: see 2nd step, Attributes: SE_PRIVILEGE_ENABLED)
The DEBUG_EVENT structure:
The u field of the structure is an union, it means that it only contains one of the listed structs. Try to use LayoutKind.Explicit and decorate every field with the attribute FieldOffset to define the correct offset inside the structure. Try this:
[StructLayout(LayoutKind.Explicit)]
public struct DEBUG_EVENT
{
[FieldOffset(0)]
public int dwDebugEventCode;
[FieldOffset(4)]
public int dwProcessId;
[FieldOffset(8)]
public int dwThreadId;
[FieldOffset(12)]
[StructLayout(LayoutKind.Explicit)]
public struct u {
[FieldOffset(0)]
public EXCEPTION_DEBUG_INFO Exception;
[FieldOffset(0)]
public CREATE_THREAD_DEBUG_INFO CreateThread;
[FieldOffset(0)]
public CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
[FieldOffset(0)]
public EXIT_THREAD_DEBUG_INFO ExitThread;
[FieldOffset(0)]
public EXIT_PROCESS_DEBUG_INFO ExitProcess;
[FieldOffset(0)]
public LOAD_DLL_DEBUG_INFO LoadDll;
[FieldOffset(0)]
public UNLOAD_DLL_DEBUG_INFO UnloadDll;
[FieldOffset(0)]
public OUTPUT_DEBUG_STRING_INFO DebugString;
[FieldOffset(0)]
public RIP_INFO RipInfo;
}
};

C# method's type signature is not pinvoke compatible

I want to use API from Omron V4KU, the documentation described like this :
Original c# code :
const string DllLocation = #"..\..\Libs\Omron\OMCR.dll";
[DllImport(DllLocation)]
public static extern LPCOMCR OMCR_OpenDevice(string lpcszDevice, LPCOMCR_OPTION lpcOption);
public void Start()
{
var lpcOption = new LPCOMCR_OPTION();
var result = OMCR_OpenDevice(null, lpcOption); // error method's type signature is not pinvoke compatible
}
[StructLayout(LayoutKind.Sequential)]
public struct LPCOMCR
{
public string lpcszDevice;
public IntPtr hDevice;
public uint lpcDevice;
}
[StructLayout(LayoutKind.Sequential)]
public struct LPCOMCR_OPTION
{
public uint dwReserved0;
public uint dwReserved1;
public uint dwReserved2;
public uint dwReserved3;
}
if I missed or wrong in writing code?
sorry, my english is bad. thanks for help.
Start by defining the union structure correctly:
// OMCR_OPTION.COM
[StructLayout(LayoutKind.Sequential)]
public struct OmcrCom
{
public IntPtr Reserved0;
public uint BaudRate;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
public IntPtr Reserved1;
public IntPtr Reserved2;
}
// OMCR_OPTION.USB
[StructLayout(LayoutKind.Sequential)]
public struct OmcrUsb
{
public uint Reserved0;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
}
// OMCR_OPTION (union of COM and USB)
[StructLayout(LayoutKind.Explicit)]
public struct OmcrOptions
{
[FieldOffset(0)]
public OmcrCom Com;
[FieldOffset(0)]
public OmcrUsb Usb;
}
// OMCR
[StructLayout(LayoutKind.Sequential)]
public struct OmcrDevice
{
public string Device;
public IntPtr DeviceHandle;
public IntPtr DevicePointer;
}
[DllImport(dllName: DllLocation, EntryPoint = "OMCR_OpenDevice"]
public static extern IntPtr OmcrOpenDevice(string type, ref OmcrOptions options);
And then call the method, something like:
var options = new OmcrOptions();
options.Com.BaudRate = 115200; // or whatever you need to set
var type = "COM"; // is this USB/COM? not sure
OmcrDevice device;
var devicePtr = OmcrOpenDevice(type, ref options);
if (devicePtr == IntPtr.Zero)
device = (OmcrDevice)Marshal.PtrToStructure(devicePtr, typeof(OmcrDevice));
Well, for one, the documentation is asking you to pass LPCOMCR_OPTION as a pointer - you're passing it as a value. Using ref should help. There's another problem, though, and that's the return value - again, you're trying to interpret it as a value, while the docs say it's a pointer. However, this is a lot trickier than the first error - as far as I'm aware, your only options are using a C++/CLI interop library, or expecting IntPtr as a return value. In any case, you need to handle proper deallocation of the memory you get this way.
You need to do it like this:
[StructLayout(LayoutKind.Sequential)]
public struct OMCR
{
[MarshalAs(UnmanagedType.LPStr)]
public string lpcszDevice;
public IntPtr hDevice;
public IntPtr lpcDevice;
}
[StructLayout(LayoutKind.Sequential)]
public struct OMCR_OPTION
{
public uint dwReserved0;
public uint dwReserved1;
public uint dwReserved2;
public uint dwReserved3;
}
[DllImport(DllLocation, CallingConvention = CallingConvention.???,
SetLastError = true)]
public static extern IntPtr OMCR_OpenDevice(string lpcszDevice,
ref OMCR_OPTION lpcOption);
You need to replace CallingConvention.??? with the appropriate calling convention. We cannot tell from the question what that is. You will have to find out by reading the header file.
The return value is a pointer to OMCR. You need to hold on to this pointer and pass it to OMCR_CloseDevice when you are finished with it.
In order to obtain an OMCR value you would do the following:
OMCR_OPTION Option = new OMCR_OPTION(); // not sure how to initialize this
IntPtr DevicePtr = OMCR_OpenDevice(DeviceType, ref Option);
if (DevicePtr == IntPtr.Zero)
throw new Win32Exception();
OMCR Device = (OMCR)Marshal.PtrToStructure(DevicePtr, typeof(OMCR));

Global Hotkey in Mono and Gtk#

I'm trying to get a global hotkey working in Linux using Mono. I found the signatures of XGrabKey and XUngrabKey, but I can't seem to get them working. Whenever I try to invoke XGrabKey, the application crashes with a SIGSEGV.
This is what I have so far:
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)
}
}
Does anyone have a working example of XGrabKey?
Thanks!
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.

Categories

Resources