How to log actions in compact framework? - c#

I'm working on a windows mobile project using compact framework.
One thing I have to do is log when users perform actions, this can mean any action from pressing a button to using the barcode scanner. The time it happened also needs to be logged.
My plan is to override all controls to include logging functionality built into them but this might not be the right way to go about it, seems like a very tedious thing to do..
Is there a better way?

I would go with IL Weaving. Here is a library that I would recommend: http://www.sharpcrafters.com/aop.net/msil-injection What it does is that you mark your class with an attribute and you can intercept all function calls. In this interception you would put in your logging logic.

I'd say it depends greatly on the definition of "action". I'd be highly inclined to see if the (undocumented) QASetWindowsJournalHook API would work. It's probably going to grab most of what you want, with not a lot of code required. A native example of usage can be found on Codeproject here.
SetWindowsHook with WH_JOURNALRECORD might also be worth a look. Yeah, I know it's "unsupported" but it works just fine, and it's unlikely to be removed from a device you've got fielded (plus it's been in the OS for at least 10 years).
Some P/Invoke declarations, all derived from pwinuser.h, for them both are as follows:
[StructLayout(LayoutKind.Sequential)]
public struct JournalHookStruct
{
public int message { get; set; }
public int paramL { get; set; }
public int paramH { get; set; }
public int time { get; set; }
public IntPtr hwnd { get; set; }
}
internal enum HookType
{
JournalRecord = 0,
JournalPlayback = 1,
KeyboardLowLevel = 20
}
internal enum HookCode
{
Action = 0,
GetNext = 1,
Skip = 2,
NoRemove = 3,
SystemModalOn = 4,
SystemModalOff = 5
}
public const int HC_ACTION = 0;
public const int LLKHF_EXTENDED = 0x1;
public const int LLKHF_INJECTED = 0x10;
public const int LLKHF_ALTDOWN = 0x20;
public const int LLKHF_UP = 0x80;
public const int VK_TAB = 0x9;
public const int VK_CONTROL = 0x11;
public const int VK_ESCAPE = 0x1B;
public const int VK_DELETE = 0x2E;
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(HookType idHook, HookProc lpfn, IntPtr hMod, int
[DllImport("coredll.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("coredll.dll", SetLastError = true)]
public static extern int CallNextHookEx(IntPtr hhk, HookCode nCode, IntPtr wParam, IntPtr
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr QASetWindowsJournalHook(HookType nFilterType, HookProc pfnFilterProc, ref JournalHookStruct pfnEventMsg);

Would writing these messages to a log file not solve your problem?
#if PocketPC
private static string _appPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
#else
private static string _appPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Application.CompanyName);
#endif
public const int KILOBYTE = 1024;
public static string ErrorFile { get { return _appPath + #"\error.log"; } }
public static void Log(string message)
{
if (String.IsNullOrEmpty(message)) return;
using (FileStream stream = File.Open(ErrorFile, FileMode.Append, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(stream, Encoding.UTF8, KILOBYTE))
{
sw.WriteLine(string.Format("{0:MM/dd/yyyy HH:mm:ss} - {1}", DateTime.Now, message));
}
}
}
You could have issues though if you have threading going on and multiple routines try to write at the same time. In that case, you could add additional logic to lock the routine while it is in use.
That's how I do it, anyway.
By the #if regions, you can see this is also used by my Windows PC applications.

Related

C# read and save propertys of printer devmode

I have a simple application to copy the properties of the printer devmode and restore them after a manual change.
I read through topics the whole day and can't understand why my code isn't working. I compared the output of two different printer settings against each other and can confirm that they are different, so my guess is that something with restoring these settings isn't working.
I also tried thisĀ applicationĀ but found some strange behavior. When I export the devmode data, change the properties manually and load the save again, the data only shows changed if I call the printer property dialog directly with the button in the application itself. If I go to windows settings and navigate to the printer properties myself, the data isn't changed.
Here is the code, got it from this thread
public static class PrinterSettingsExtensions
{
public static byte[] GetDevModeData(this PrinterSettings settings)
{
//Contract.Requires(settings != null);
byte[] devModeData;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
// cer since hDevMode is not a SafeHandle
}
finally
{
var hDevMode = settings.GetHdevmode();
try
{
IntPtr pDevMode = NativeMethods.GlobalLock(hDevMode);
try
{
var devMode = (NativeMethods.DEVMODE)Marshal.PtrToStructure(
pDevMode, typeof(NativeMethods.DEVMODE));
var devModeSize = devMode.dmSize + devMode.dmDriverExtra;
devModeData = new byte[devModeSize];
Marshal.Copy(pDevMode, devModeData, 0, devModeSize);
}
finally
{
NativeMethods.GlobalUnlock(hDevMode);
}
}
finally
{
Marshal.FreeHGlobal(hDevMode);
}
}
return devModeData;
}
public static void SetDevModeData(this PrinterSettings settings, byte[] data)
{
//Contract.Requires(settings != null);
//Contract.Requires(data != null);
//Contract.Requires(data.Length >= Marshal.SizeOf(typeof(NativeMethods.DEVMODE)));
RuntimeHelpers.PrepareConstrainedRegions();
try
{
// cer since AllocHGlobal does not return SafeHandle
}
finally
{
var pDevMode = Marshal.AllocHGlobal(data.Length);
try
{
// we don't have to worry about GlobalLock since AllocHGlobal only uses LMEM_FIXED
Marshal.Copy(data, 0, pDevMode, data.Length);
var devMode = (NativeMethods.DEVMODE)Marshal.PtrToStructure(
pDevMode, typeof(NativeMethods.DEVMODE));
// The printer name must match the original printer, otherwise an AV will be thrown
settings.PrinterName = devMode.dmDeviceName;
// SetHDevmode creates a copy of the devmode, so we don't have to keep ours around
settings.SetHdevmode(pDevMode);
}
finally
{
Marshal.FreeHGlobal(pDevMode);
}
}
}
}
static class NativeMethods
{
private const string Kernel32 = "kernel32.dll";
[DllImport(Kernel32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GlobalLock(IntPtr handle);
[DllImport(Kernel32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern bool GlobalUnlock(IntPtr handle);
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Auto)]
public struct DEVMODE
{
private const int CCHDEVICENAME = 32;
private const int CCHFORMNAME = 32;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public int dmPositionX;
public int dmPositionY;
public int dmDisplayOrientation;
public int dmDisplayFixedOutput;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
public string dmFormName;
public short dmLogPixels;
public int dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
}
}
I'm using .Net Core 6 and Windows 11 22000.795 if this helps.
DevMode is what I am working on at the moment. Using .net 4.8.1 unfortunately but I am using Heap functions and I am pulling the default settings using p/Invoke.
My goal is to modify settings and then stream the changed settings to another API. So, I'll look into this and see what I can find.
Several steps could be giving you problems with settings. Mostly around the printer/document/settings/defaults/drivers themselves. Once you make your settings and pass need to make sure your end test is picking up the changes. But, as you suggest, most likely an invalid format due to each driver being unique.
Any progress?
Appears for the data to show it has been changed, you need to flag the fields that have been modified in public int dmFields; (Says it is defined as a long).
dmFields
Specifies bit flags identifying which of the following DEVMODEW members are in use. For example, the DM_ORIENTATION flag is set when the dmOrientation member contains valid data. The DM_XXX flags are defined in wingdi.h.

Long Running Timer increasing Private Bytes in C#

I created an application which runs in background and make use of 4 Timers for different purposes. On elapsed event of timers task is accomplished but Memory Private Bytes are growing.
I read about Timer classes and it is said that Timers must be disposed after once task is completed but the issue is timer must run in background to accomplish task.
class myservice
{
public void Start()
{
Timer tActiveWin = new Timer();
tActiveWin.Interval = TimeSpan.FromSeconds(2).TotalMilliseconds;
tActiveWin.Elapsed += TActiveWin_Elapsed;
tActiveWin.AutoReset = true;
tActiveWin.Enabled = true;
}
private void TActiveWin_Elapsed(object sender, ElapsedEventArgs e)
{
var win = new WindowEvents().GetActiveWindow();
Console.WriteLine(win.activewindowtitle);
}
class WindowEvents
{
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
public JsonAppEvents.Activewindow GetActiveWindow()
{
JsonAppEvents.Activewindow activeWin = new JsonAppEvents.Activewindow();
IntPtr hWnd = GetForegroundWindow();
int processID = 0;
int threadID = GetWindowThreadProcessId(hWnd, out processID);
using (Process p = Process.GetProcessById(processID))
{
StringBuilder text = new StringBuilder(256);
if (GetWindowText(hWnd, text, 256) > 0)
{
text.ToString();
}
activeWin.activewindowfullpath = p.MainModule.FileName;
activeWin.activewindowtitle = p.MainWindowTitle;
activeWin.time = p.StartTime.ToString("ddd, dd MMM yyyy HH:mm:ss");
activeWin.activewindowdescription = p.MainModule.ModuleName;
p.Dispose();
hWnd = IntPtr.Zero;
processID = 0;
threadID = 0;
text.Clear();
text = null;
}
return activeWindow.Result;
}
class JsonAppEvents
{
public class Activewindow
{
public string activewindowfullpath { get; set; }
public string activewindowdescription { get; set; }
public string time { get; set; }
public string activewindowtitle { get; set; }
}
}
I also did searches on Garbage Collection working and since in C# we can not control when memory will be claimed, I am stuck with more findings on it. Any clue on it will help.
Unlike the SafeFileHandle, unsafe handles aren't disposed automatically. So
use the CloseHandle() Win32 API to discard unsafe handles. Size of a handle depends on the target architecture - it is either 32 or 64 bits. So declare the function and use it as follows:
[DllImport("kernel32.dll", SetLastError = true)]
protected static extern bool CloseHandle([In] IntPtr Handle);
.
.
.
IntPtr hWnd = GetForegroundWindow();
.
.
.
CloseHandle(hWnd);

Automatically a debug process from C# code and read register values

I'm looking for a way to read the edx registry at a certain address like asked in this question: Read eax register
Although my solution needs to be in C# and I tried to make it, what I got at this moment is:
public static IntPtr GetEdx(IntPtr address, Process process)
{
const uint DBG_EXCEPTION_NOT_HANDLED = 0x80010001;
const uint EXCEPTION_SINGLE_STEP = 0x80000004;
const int DBG_CONTINUE = 0x00010002; // Seems to work better than DBG_EXCEPTION_NOT_HANDLED
//DebugSetProcessKillOnExit(0);
DEBUG_EVENT evt = new DEBUG_EVENT();
// Attach to the process we provided the thread as an argument
if (!DebugActiveProcess(process.Id))
throw new Win32Exception();
CONTEXT context = new CONTEXT();
foreach (ProcessThread thread in process.Threads)
{
uint iThreadId = (uint)thread.Id;
IntPtr hThread =
OpenThread(
ThreadAccessFlags.SUSPEND_RESUME | ThreadAccessFlags.SET_CONTEXT |
ThreadAccessFlags.GET_CONTEXT, false, iThreadId);
// Suspent the thread
if (SuspendThread(hThread) == -1) throw new ApplicationException("Cannot suspend thread.");
context = new CONTEXT
{
ContextFlags = (uint)CONTEXT_FLAGS.CONTEXT_DEBUG_REGISTERS |
(uint)CONTEXT_FLAGS.CONTEXT_INTEGER
};
// Get the context
if (!GetThreadContext(hThread, ref context))
throw new Win32Exception();
// Change the context
context.Dr0 = (uint)address;
context.Dr7 = 0x00000001;
// Set the changed context back
if (!SetThreadContext(hThread, ref context))
throw new Win32Exception();
// Check if setting the context give any errors
var error = Marshal.GetLastWin32Error();
if (error != 0)
{
throw new ApplicationException("Error is setting context.");
}
// Resume the thread
if (ResumeThread(hThread) == -1) throw new ApplicationException("Cannot resume thread.");
}
while (true)
{
if (!WaitForDebugEvent(out evt, -1))
throw new Win32Exception();
// Multiple if's for easier debugging at this moment
if (evt.dwDebugEventCode == (uint)DebugEventType.EXCEPTION_DEBUG_EVENT)
{
if (evt.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
{
if (evt.Exception.ExceptionRecord.ExceptionAddress == address)
{
context = new CONTEXT
{
ContextFlags = (uint)CONTEXT_FLAGS.CONTEXT_DEBUG_REGISTERS |
(uint)CONTEXT_FLAGS.CONTEXT_INTEGER
};
GetThreadContext((IntPtr)evt.dwThreadId, ref context);
return (IntPtr)context.Ebx; // ebx get
}
}
}
ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, DBG_CONTINUE);//DBG_EXCEPTION_NOT_HANDLED);
}
}
With a whole lot of Kernel32 methods:
[DllImport("kernel32.dll")]
static extern int ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll")]
static extern uint SuspendThread(IntPtr hThread);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenThread(ThreadAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("Kernel32.dll", SetLastError = true)]
static extern bool DebugActiveProcess(int dwProcessId);
[DllImport("Kernel32.dll", SetLastError = true)]
static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);
[DllImport("Kernel32.dll", SetLastError = true)]
static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, uint dwContinueStatus);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool IsDebuggerPresent();
[DllImport("kernel32.dll")]
private static extern bool GetThreadContext(IntPtr hThread, ref CONTEXT lpContext);
[DllImport("kernel32.dll")]
public static extern bool SetThreadContext(IntPtr hThread, ref CONTEXT lpContext);
[StructLayout(LayoutKind.Sequential)]
public unsafe struct DEBUG_EVENT
{
public readonly uint dwDebugEventCode;
public readonly int dwProcessId;
public readonly int dwThreadId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 86, ArraySubType = UnmanagedType.U1)]
private readonly byte[] debugInfo;
public EXCEPTION_DEBUG_INFO Exception
{
get
{
if (debugInfo == null)
return new EXCEPTION_DEBUG_INFO();
fixed (byte* ptr = debugInfo)
{
return *(EXCEPTION_DEBUG_INFO*)ptr;
}
}
}
public LOAD_DLL_DEBUG_INFO LoadDll
{
get
{
if (debugInfo == null)
return new LOAD_DLL_DEBUG_INFO();
fixed (byte* ptr = debugInfo)
{
return *(LOAD_DLL_DEBUG_INFO*)ptr;
}
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct LOAD_DLL_DEBUG_INFO
{
public readonly IntPtr hFile;
public readonly IntPtr lpBaseOfDll;
public readonly uint dwDebugInfoFileOffset;
public readonly uint nDebugInfoSize;
public readonly IntPtr lpImageName;
public readonly ushort fUnicode;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXCEPTION_DEBUG_INFO
{
public EXCEPTION_RECORD ExceptionRecord;
public readonly uint dwFirstChance;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXCEPTION_RECORD
{
public readonly uint ExceptionCode;
public readonly uint ExceptionFlags;
public readonly IntPtr ExceptionRecord;
public readonly IntPtr ExceptionAddress;
public readonly uint NumberParameters;
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15, ArraySubType = UnmanagedType.U4)]
//public readonly uint[] ExceptionInformation;
public unsafe fixed uint ExceptionInformation[15];
}
public enum DebugEventType : int
{
CREATE_PROCESS_DEBUG_EVENT = 3, //Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
CREATE_THREAD_DEBUG_EVENT = 2, //Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
EXCEPTION_DEBUG_EVENT = 1, //Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
EXIT_PROCESS_DEBUG_EVENT = 5, //Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
EXIT_THREAD_DEBUG_EVENT = 4, //Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
LOAD_DLL_DEBUG_EVENT = 6, //Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
OUTPUT_DEBUG_STRING_EVENT = 8, //Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
RIP_EVENT = 9, //Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
UNLOAD_DLL_DEBUG_EVENT = 7, //Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
}
[StructLayout(LayoutKind.Sequential)]
public struct CONTEXT
{
public uint ContextFlags;
public uint Dr0;
public uint Dr1;
public uint Dr2;
public uint Dr3;
public uint Dr6;
public uint Dr7;
public FLOATING_SAVE_AREA FloatSave;
public uint SegGs;
public uint SegFs;
public uint SegEs;
public uint SegDs;
public uint Edi;
public uint Esi;
public uint Ebx;
public uint Edx;
public uint Ecx;
public uint Eax;
public uint Ebp;
public uint Eip;
public uint SegCs;
public uint EFlags;
public uint Esp;
public uint SegSs;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
public byte[] ExtendedRegisters;
}
public enum CONTEXT_FLAGS : uint
{
CONTEXT_i386 = 0x10000,
CONTEXT_i486 = 0x10000,
CONTEXT_CONTROL = CONTEXT_i386 | 0x01,
CONTEXT_INTEGER = CONTEXT_i386 | 0x02,
CONTEXT_SEGMENTS = CONTEXT_i386 | 0x04,
CONTEXT_FLOATING_POINT = CONTEXT_i386 | 0x08,
CONTEXT_DEBUG_REGISTERS = CONTEXT_i386 | 0x10,
CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x20,
CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS,
CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS
}
[Flags]
public enum ThreadAccessFlags : int
{
TERMINATE = 0x0001,
SUSPEND_RESUME = 0x0002,
GET_CONTEXT = 0x0008,
SET_CONTEXT = 0x0010,
SET_INFORMATION = 0x0020,
QUERY_INFORMATION = 0x0040,
SET_THREAD_TOKEN = 0x0080,
IMPERSONATE = 0x0100,
DIRECT_IMPERSONATION = 0x0200
}
[StructLayout(LayoutKind.Sequential)]
public struct FLOATING_SAVE_AREA
{
public uint ControlWord;
public uint StatusWord;
public uint TagWord;
public uint ErrorOffset;
public uint ErrorSelector;
public uint DataOffset;
public uint DataSelector;
// missing some stuff
public uint Cr0NpxState;
}
[DllImport("kernel32.dll")]
private static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(IntPtr hProcess, int lpBaseAddress, byte[] buffer, int size,
int lpNumberOfBytesRead);
But for some reason it never hits the evt.Exception.ExceptionRecord.ExceptionAddress == address
I'm pretty new to memory reading and have a hard time figuring out what is wrong with the code above.
EDIT: Also if I uncomment the context.Dr7 = 0x00000001; the application that I'm trying to read crashes.
The theory:
You want to attach to a process, put a breakpoint in it, register an event in the debugger application and wait for that event. You need to put the breakpoint at the address you give as parameter, and by reading the context you should be able to see the content of EDX. This seems reasonable, like a human developer would do it.
The implementation:
Looking at your implementation, the method you try to use to put the breakpoint at the address seems suspicious. Reading here I understand that trying to set the context on a running thread might give unpredictable results. You may also lack the sufficient permissions. Assuming you have the permissions, try stopping the threads before setting the context.
The other problem I predict is that the thread you want to stop and debug has a context that needs to be altered as less as possible. I think that you should first stop the thread, read it's context, change the Dr0 flag (assuming that's where you set the breakpoint) and then set the context with all the other register information unaltered.
Without that, I think that you basically change the execution of the program and I have a strong feeling that some of those registers are read only.
These are 2 things you need to consider. Hope it helps.
Debugging:
If that does not work, you need to add a function that uses GetLastError() to see why the functions fail (I'm suspecting SetThreadContext() will be the one causing problems in the first time).
You must also check that the Context structure is defined correctly and all the members have the same order has the ones defined here. The structure has to be aligned by the C# code exactly as in the unmanaged code.
Also please check if you are running on a 64bit OS. The context of a thread in a 64bit OS is different than for 32bit. Registers are extended for 64b and so on. If you use a 64bit OS, the context structure needs to be redefined. Same goes if you use an ARM machine.
Your answer seems to be hiding in one of the comments you transplanted with the other code... The functions to set and get thread context require a handle to a thread (probably opened with OpenThread using appropriate permissions, including at least get/set context). Instead, you're apparently passing a casted process ID. You should consider checking the return value for success, it would've probably helped identify your problem.

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.

How do you place a file in recycle bin instead of delete?

Programmatic solution of course...
http://www.daveamenta.com/2008-05/c-delete-a-file-to-the-recycle-bin/
From above:
using Microsoft.VisualBasic;
string path = #"c:\myfile.txt";
FileIO.FileSystem.DeleteDirectory(path,
FileIO.UIOption.OnlyErrorDialogs,
RecycleOption.SendToRecycleBin);
You need to delve into unmanaged code. Here's a static class that I've been using:
public static class Recycle
{
private const int FO_DELETE = 3;
private const int FOF_ALLOWUNDO = 0x40;
private const int FOF_NOCONFIRMATION = 0x0010;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
public struct SHFILEOPSTRUCT
{
public IntPtr hwnd;
[MarshalAs(UnmanagedType.U4)]
public int wFunc;
public string pFrom;
public string pTo;
public short fFlags;
[MarshalAs(UnmanagedType.Bool)]
public bool fAnyOperationsAborted;
public IntPtr hNameMappings;
public string lpszProgressTitle;
}
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
static extern int SHFileOperation(ref SHFILEOPSTRUCT FileOp);
public static void DeleteFileOperation(string filePath)
{
SHFILEOPSTRUCT fileop = new SHFILEOPSTRUCT();
fileop.wFunc = FO_DELETE;
fileop.pFrom = filePath + '\0' + '\0';
fileop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
SHFileOperation(ref fileop);
}
}
Addendum:
Tsk tsk # Jeff for "using Microsoft.VisualBasic" in C# code.
Tsk tsk # MS for putting all the goodies in VisualBasic namespace.
The best way I have found is to use the VB function FileSystem.DeleteFile.
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(file.FullName,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
It requires adding Microsoft.VisualBasic as a reference, but this is part of the .NET framework and so isn't an extra dependency.
Alternate solutions require a P/Invoke to SHFileOperation, as well as defining all the various structures/constants. Including Microsoft.VisualBasic is much neater by comparison.

Categories

Resources