Global Hotkey in Mono and Gtk# - c#

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.

Related

Controlling GPIO in CP210x C#

I need to control the GPIO pins of a CP210x device using CP210xManufacturing.dll and CP210xRuntime.dll provided by Silicon Labs.
I manage to open and close the device and to get the part number. (In my case a CP2105).
I think I successfully read the latch, but I'm not sure.
I also call the write latch function, and no error is returned and neither do any pins toggle.
According to the provided utility (CP21xxCustomizationUtility.exe) it shows that both ports are in GPIO mode.
Here is my code:
using System;
using System.Runtime.InteropServices;
namespace CP210x
{
public class CP210x
{
[DllImport("CP210xManufacturing.dll")]
private static extern Int32 CP210x_GetNumDevices(ref Int32 numOfDevices);
public static Int32 GetNumDevices(ref Int32 numOfDevices)
{
return CP210x_GetNumDevices(ref numOfDevices);
}
[DllImport("CP210xManufacturing.dll")]
private static extern Int32 CP210x_Open(Int32 deviceNum, ref IntPtr handle);
public static Int32 Open(Int32 deviceNum, ref IntPtr handle)
{
return CP210x_Open(deviceNum, ref handle);
}
[DllImport("CP210xManufacturing.dll")]
private static extern Int32 CP210x_Close(IntPtr handle);
public static Int32 Close(IntPtr handle)
{
return CP210x_Close(handle);
}
[DllImport("CP210xManufacturing.dll")]
private static extern Int32 CP210x_GetPartNumber(IntPtr handle, Byte[] lpbPartNum);
public static Int32 GetPartNumber(IntPtr handle, Byte[] lpbPartNum)
{
return CP210x_GetPartNumber(handle, lpbPartNum);
}
[DllImport("CP210xRuntime.dll")]
private static extern Int32 CP210xRT_WriteLatch(IntPtr handle, UInt16 mask, UInt16 latch);
public static Int32 WriteLatch(IntPtr handle, UInt16 mask, UInt16 latch)
{
return CP210xRT_WriteLatch(handle, mask, latch);
}
[DllImport("CP210xRuntime.dll")]
private static extern Int32 CP210xRT_ReadLatch(IntPtr handle, UInt16[] lpLatch);
public static Int32 ReadLatch(IntPtr handle, UInt16[] lpLatch)
{
return CP210xRT_ReadLatch(handle, lpLatch);
}
}
}
and the function in my class calling the DLL methods:
private static void ResetTelit()
{
Int32 numOfDevices = 0;
Int32 retVal = CP210x.CP210x.GetNumDevices(ref numOfDevices);
IntPtr handle = IntPtr.Zero;
Byte[] prtNum = new Byte[1];
UInt16[] latch = new UInt16[8];
UInt16 mask = 0x01;
if (numOfDevices > 0)
{
retVal = CP210x.CP210x.Open(0, ref handle);
retVal = CP210x.CP210x.GetPartNumber(handle, prtNum);
if (prtNum[0] == 5)
{
retVal = CP210x.CP210x.ReadLatch(handle, latch);
for (Int32 idx = 0; idx < 16; idx++)
{
retVal = CP210x.CP210x.WriteLatch(handle, (UInt16)(mask << idx), 0x01);
}
}
retVal = CP210x.CP210x.Close(handle);
}
}
I do realise that the issue might be in the DLL wrapper, but I can't figure it out.
Refer to Windows Data Types. This assists in the DLL wrapper.
Refer to CP21xxCustomizationUtility. This contains the DLLs.
Google "silicon labs an169" to get the USBXpress® Programmer’s Guide.
So the question is what is wrong here? How do I toggle the GPIO pins?
The answer to the question is that the code is fine. The code works. The issue is the IC. The code works fine with the CP2103, but not so good with the CP2105. It seems like the CP2105 is setup differently.

How can I confirm script error dialog box into WebBrowser?

I need to confirm programatically (i.e, answer 'yes') to a script error dialog box into WebBrowser otherwise the page will stop working. I have no code to show because I have no idea how I could do this.
Here's a image from dialog box I'm talking about:
(take from this, btw)
According to the "How to handle script errors as a WebBrowser control host" MSKB article, you need to handle CGID_DocHostCommandHandler/OLECMDID_SHOWSCRIPTERROR command in the WebBrowser host.
With a bit of coding, here is how it can be done for the WinForms WebBrowser version, and it actually works:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class WebBrowserEx: WebBrowser
{
class WebBrowserSiteEx : WebBrowserSite, NativeMethods.IOleCommandTarget
{
public WebBrowserSiteEx(WebBrowser browser): base(browser)
{
}
public int QueryStatus(IntPtr pguidCmdGroup, uint cCmds, NativeMethods.OLECMD[] prgCmds, ref NativeMethods.OLECMDTEXT CmdText)
{
return NativeMethods.OLECMDERR_E_UNKNOWNGROUP;
}
public int Exec(IntPtr pguidCmdGroup, uint nCmdId, uint nCmdExecOpt, IntPtr pvaIn, IntPtr pvaOut)
{
if (pguidCmdGroup != IntPtr.Zero)
{
Guid guid = (Guid)Marshal.PtrToStructure(pguidCmdGroup, typeof(Guid));
if (guid == NativeMethods.CGID_DocHostCommandHandler)
{
if (nCmdId == NativeMethods.OLECMDID_SHOWSCRIPTERROR)
{
// if DOM needed: dynamic document = Marshal.GetObjectForNativeVariant(pvaIn);
// continue running scripts
if (pvaOut != IntPtr.Zero)
Marshal.GetNativeVariantForObject(true, pvaOut);
return NativeMethods.S_OK;
}
}
}
return NativeMethods.OLECMDERR_E_UNKNOWNGROUP;
}
}
protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
{
return new WebBrowserSiteEx(this);
}
}
public partial class Form1 : Form
{
WebBrowserEx _browser;
public Form1()
{
InitializeComponent();
_browser = new WebBrowserEx();
_browser.Dock = DockStyle.Fill;
this.Controls.Add(_browser);
}
private void Form1_Load(object sender, EventArgs e)
{
// testing
_browser.DocumentText = "<script>alert(bad.bad)</script>";
}
}
static class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct OLECMD
{
public uint cmdID;
public uint cmdf;
}
[StructLayout(LayoutKind.Sequential)]
public struct OLECMDTEXT
{
public UInt32 cmdtextf;
public UInt32 cwActual;
public UInt32 cwBuf;
public char rgwz;
}
public const int OLECMDERR_E_UNKNOWNGROUP = unchecked((int)0x80040102);
public const int OLECMDID_SHOWSCRIPTERROR = 40;
public static readonly Guid CGID_DocHostCommandHandler = new Guid("f38bc242-b950-11d1-8918-00c04fc2c836");
public const int S_OK = 0;
[ComImport, Guid("b722bccb-4e68-101b-a2bc-00aa00404770"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleCommandTarget
{
[PreserveSig]
int QueryStatus(
IntPtr pguidCmdGroup,
UInt32 cCmds,
[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] OLECMD[] prgCmds,
ref OLECMDTEXT CmdText);
[PreserveSig]
int Exec(
IntPtr pguidCmdGroup,
uint nCmdId,
uint nCmdExecOpt,
IntPtr pvaIn,
IntPtr pvaOut);
}
}
}
Updated to address the comment:
I need to capture the JavaScript error info in order to log it while
still not displaying it to the user. I looked at the document object
(the commented out line) and didn't see anything there. Is there an
easy way to capture this info?
Check the article I linked at the beginning. There are special properties errorLine, errorCharacter, errorCode, errorMessage, errorUrl available on document.parentWindow.event object.

How to log actions in compact framework?

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.

WPF Equivalent to SendInput?

Is there an equivalent to SendInput for WPF? I've looked into AutomationPeer classes but was not successfull.
I simply want to send a Keydown (the Enter Key). Simply raising the event (RaiseEvent) does not work in my scenario.
Here is what I have, which is working. I'd prefer to have a managed code alternative.
private void comboSelectionChanged(object sender, SelectionChangedEventArgs args)
{
((ComboBox)sender).Focus();
// send keydown
INPUT input = new INPUT();
input.type = INPUT_KEYBOARD;
input.union.keyboardInput.wVk = 0x0D;
input.union.keyboardInput.time = 0;
SendInput(1, ref input, Marshal.SizeOf(input));
}
[DllImport("user32.dll", SetLastError = true)]
private static extern int SendInput(int nInputs, ref INPUT mi, int cbSize);
[StructLayout(LayoutKind.Sequential)]
private struct INPUT
{
public int type;
public INPUTUNION union;
};
[StructLayout(LayoutKind.Explicit)]
private struct INPUTUNION
{
[FieldOffset(0)]
public MOUSEINPUT mouseInput;
[FieldOffset(0)]
public KEYBDINPUT keyboardInput;
};
[StructLayout(LayoutKind.Sequential)]
private struct MOUSEINPUT
{
public int dx;
public int dy;
public int mouseData;
public int dwFlags;
public int time;
public IntPtr dwExtraInfo;
};
[StructLayout(LayoutKind.Sequential)]
private struct KEYBDINPUT
{
public short wVk;
public short wScan;
public int dwFlags;
public int time;
public IntPtr dwExtraInfo;
};
private const int INPUT_MOUSE = 0;
private const int INPUT_KEYBOARD = 1;
You can emulate a keystroke like this:
public void SendKey(UIElement sourceElement, Key keyToSend)
{
KeyEventArgs args = new KeyEventArgs(InputManager.Current.PrimaryKeyboardDevice, PresentationSource.FromVisual(sourceElement), 0, keyToSend);
args.RoutedEvent = Keyboard.KeyDownEvent;
InputManager.Current.ProcessInput(args);
}
You could then call it like this:
SendKey(myComboBox, Key.Enter);
I suppose you can put this in a static class somewhere, or even make an extension method out of it. However, I would argue that in most cases there is a more elegant way to accomplish this.
I hope this helps.

Screen.AllScreen is not giving the correct monitor count

I am doing something like this in my program:
Int32 currentMonitorCount = Screen.AllScreens.Length;
if (currentMonitorCount < 2)
{
//Put app in single screen mode.
}
else
{
//Put app in dual screen mode.
}
It is VERY important my application recognizes how many monitors are currently connected.
However, after I plug/unplug the monitor a couple of times, Screen.AllScreens.Length always returns '2'.
My monitor knows it's not connected (it has entered 'power save' mode), and the control panel knows that it's not connected (it shows only one monitor).
So what am I missing? How do I figure out that there's only one monitor?
I had a look at the source (remember we can do that using the MS Symbol servers). AllScreens uses an unmanaged API to get the screens on the first access, then stores the result in a static variable for later use.
The consequence of this, is that if the number of monitors changes while your program is running; then Screen.AllScreens will not pick up the change.
The easiest way to get around this would probably be to call the unmanaged API directly.
(Or you could be evil, and use reflection to set the static screens field to null before asking. Don't do that).
Edit:
If you just need to know the count, check whether you can use System.Windows.Forms.SystemInformation.MonitorCount (as suggested in the comments) before going the P/Invoke route. This calls GetSystemMetrics directly, and it is probably correctly updated.
If you find you need to do it using P/Invoke, here is a complete example that demonstrates the usage of the unmanaged API from C#:
using System;
using System.Runtime.InteropServices;
class Program
{
public static void Main()
{
int monCount = 0;
Rect r = new Rect();
MonitorEnumProc callback = (IntPtr hDesktop, IntPtr hdc, ref Rect prect, int d) => ++monCount > 0;
if (EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, 0))
Console.WriteLine("You have {0} monitors", monCount);
else
Console.WriteLine("An error occured while enumerating monitors");
}
[DllImport("user32")]
private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lpRect, MonitorEnumProc callback, int dwData);
private delegate bool MonitorEnumProc(IntPtr hDesktop, IntPtr hdc, ref Rect pRect, int dwData);
[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
}
Building on the previous reply by driis, this is how I handled it. I should note that the following code lives in my Program.cs file.
First the links to external resources and data structures:
[DllImport("user32")]
private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lpRect, MonitorEnumProc callback, int dwData);
private delegate bool MonitorEnumProc(IntPtr hDesktop, IntPtr hdc, ref Rect pRect, int dwData);
[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
Now create a simple object to contain monitor information:
public class MonitorInfo
{
public bool IsPrimary = false;
public Rectangle Bounds = new Rectangle();
}
And a container to hold these objects:
public static List<MonitorInfo> ActualScreens = new List<MonitorInfo>();
and a method to refresh the container:
public static void RefreshActualScreens()
{
ActualScreens.Clear();
MonitorEnumProc callback = (IntPtr hDesktop, IntPtr hdc, ref Rect prect, int d) =>
{
ActualScreens.Add(new MonitorInfo()
{
Bounds = new Rectangle()
{
X = prect.left,
Y = prect.top,
Width = prect.right - prect.left,
Height = prect.bottom - prect.top,
},
IsPrimary = (prect.left == 0) && (prect.top == 0),
});
return true;
};
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, 0);
}
Then later on a Form, If I wanted to detect that a display had been added or removed ...
private const int WM_DISPLAYCHANGE = 0x007e;
protected override void WndProc(ref Message message)
{
base.WndProc(ref message);
if (message.Msg == WM_DISPLAYCHANGE)
{
Program.RefreshActualScreens();
// do something really interesting here
}
}
Might be a few typos in there, but that is the basic idea. Good luck!
I had a look at the code of the Screen class ( in here )
See line 120, Screen.AllScreens uses the field Screen.screens for cache.
In my solution, I use the reflection api to change the Screen class.
I clear Screens.screens before calling Screen.AllScreens.
// Code for clearing private field
typeof(Screen).GetField("screens", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).SetValue(null, null);

Categories

Resources