SetSystemCursor() for multiple cursors behavior - c#

I am trying to change multiple cursors to Cross cursor. This is the code I am using for that matter:
[DllImport("user32.dll")]
static extern bool SetSystemCursor(IntPtr hcur, uint id);
[DllImport("user32.dll")]
static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern Int32 SystemParametersInfo(UInt32 uiAction,
UInt32 uiParam, String pvParam, UInt32 fWinIni);
//Normal cursor
private static uint OCR_NORMAL = 32512;
//The text selection (I-beam) cursor.
private static uint OCR_IBEAM = 32513;
//The cross-shaped cursor.
private static uint OCR_CROSS = 32515;
Then I use these two functions I made:
static public void ChangeCursors() {
SetSystemCursor(LoadCursor(IntPtr.Zero, (int)OCR_CROSS), OCR_NORMAL);
SetSystemCursor(LoadCursor(IntPtr.Zero, (int)OCR_CROSS), OCR_IBEAM);
}
static public void RevertCursors() {
SystemParametersInfo(0x0057, 0, null, 0);
}
If I just use SetSystemCursor(LoadCursor(IntPtr.Zero, (int)OCR_CROSS), OCR_NORMAL);, everything works fine. The Normal cursor gets replaced by Cross cursor.
My problem is when I try to change multiple cursors to Cross cursor. If I call ChangeCursors(), the expected result would be Normal cursor AND I-beam cursor gets replaced by Cross cursor. But the result is something really weird.
When the software starts depending on the current state of the cursor when the program started, the following strange things happen:
If cursor was Normal when the software started, it changes to Cross (that's good). Also, I-beam is replaced with Normal (that's bad, it should be Cross).
If cursor was I-beam when the software started, it stays I-beam(that's bad, because it should be Cross). Then, by hovering over to where previously the cursor should be Normal it is now Cross (that's good). Then, if I hover over to where the cursor was I-beam 1 second ago, it magically changes to Normal (that's weird) and stays that way.
So, my question is, how can I change 2 or more Cursors to Cross cursor, using SetSystemCursor() ?

Dont get confused about the weird behaviour. It's just the cursors getting swapped each time when you Assign.
At first
Normal == Normal
IBeam == IBeam
Cross == Cross
You Assign Normal = Cross
Normal == Cross
IBeam == IBeam
Cross == Normal
And now assign IBeam = Cross (Which is Normal now)
Normal == Cross
IBeam == Normal
Cross == IBeam
So for not letting it get swapped, you have to keep copies of all the cursors. I'll give you an example having Normal and IBeam changed to CROSS.
Program.cs
static class Program
{
[DllImport("user32.dll")]
static extern bool SetSystemCursor(IntPtr hcur, uint id);
[DllImport("user32.dll")]
static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32
uiParam, String pvParam, UInt32 fWinIni);
[DllImport("user32.dll")]
public static extern IntPtr CopyIcon(IntPtr pcur);
private static uint CROSS = 32515;
private static uint NORMAL = 32512;
private static uint IBEAM = 32513;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
uint[] Cursors = {NORMAL, IBEAM};
for (int i = 0; i < Cursors.Length; i++)
SetSystemCursor(CopyIcon(LoadCursor(IntPtr.Zero, (int)CROSS)), Cursors[i]);
Application.Run(new Form1());
SystemParametersInfo(0x0057, 0, null, 0);
}
}

SetSystemCursor replaces the system cursor given by the second argument (OCR_CROSS in this example) with the cursor in the first argument. So, you set the OCR_CROSS cursor first to Normal, then to IBeam, so in effect the cross-cursor is set to look like an IBeam.
The documentation also specifically says
The system destroys hcur [the first argument] by calling the DestroyCursor function. Therefore, hcur cannot be a cursor loaded using the LoadCursor function. To specify a cursor loaded from a resource, copy the cursor using the CopyCursor function, then pass the copy to SetSystemCursor.
Your code does this, so things could go wrong here, or at the least leak handles.
In-depth look
The SetSystemCursor is far more invasive then you might think. It actually swaps the global cursor-data of the specified cursor in the second argument by the cursor object in the first argument.
This has consequences. Say that you replaced IDC_WAIT by IDC_CROSS and then IDC_ARROW by IDC_WAIT (code in C):
HCURSOR hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_CROSS));
HCURSOR hCopyCursor = CopyCursor(hCursor);
SetSystemCursor(hCopyCursor, (DWORD)IDC_WAIT); // Replace Wait by Cross
HCURSOR hCursor2 = LoadCursor(0, MAKEINTRESOURCE(IDC_WAIT)); // Load whatever is in Wait and put it in Arrow
HCURSOR hCopyCursor2 = CopyCursor(hCursor2);
SetSystemCursor(hCopyCursor2, (DWORD)IDC_ARROW); // Replace Arrow by Wait.
Question: Is the Arrow cursor of the system a Cross or a Wait cursor?
Answer: It's a Cross cursor.
The reason this happens is because you actually swap the underlying cursor data, not just some reference, so LoadCursor reads the replaced cursor data.
This also shows why it gets very confusing when you don't make a copy of the cursor first: then the two cursors are getting swapped globally.

Related

Hijacking another processes form

So, I have been asked to figure out a way to make a program containing sensitive data more secure since we have staff that go afk and put potentially put data at risk.
I have loaded up Visual Studio for C# and found a nice way to get process of the fore mentioned application. Then grab the main window and attach a panel of my very own. This panel will basically now be used like a blind covering the application when its not in use.
Now, I have a program running in system tray waiting for the sensitive data to come on screen and my little panel hijacks the entire window and now nothing can be seen.
My problem now is how ever, that whilst my panel is attacked the main window of the application i am trying to lock out seems to just crash. I am guessing that is because my panel and the application belong to different processes.
Anyway I could do with some advise here.
Here is my panels class.
class LockingPanel : System.Windows.Forms.Panel
{
private IntPtr prn;
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
public void SetParent(IntPtr parent)
{
prn = parent;
SetParent(this.Handle, prn);
}
public IntPtr GetParent() {
return prn;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT Rect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
public void FillParent()
{
RECT rtc = new RECT();
GetWindowRect(prn, ref rtc);
this.Location = new System.Drawing.Point(0, 0);
this.Size = new System.Drawing.Size(rtc.Right, rtc.Bottom);
}
Anybody got a better idea on how I can go about this, or at least make it so that my panel inst going to crash the application.

WPF topmost option makes fullscreen app to escape

I used small window as a tooltip which shows "now playing" info in my application. Sure I set Topmost = "True" in window declaration and when new song starts to play I do tooltip.Show(). The problem is when I have fullscreen application in focus (game for example), .Show() call makes fullscreen window to became non-fullscreen, windowed, with border and top bar (as if I press Alt + Enter) and it looks like it loses focus also. So to restore fullscreen I need to click in window to focus it and press Alt-Enter manually.
ShowActivated set to "False" already.
So, I see three solutions:
1) Somehow make Topmost option to not cause to steal focus on .Show()
2) Use some workaround to make tooltip window "always on top" without Topmost option (I don't need to popup tooltip over fullscreen application since it can be (and probably will be) drawed via D3D or OpenGL)
3) Detect if system has fullscreen window in focus now and don't try to show tooltip.
I have no any clue how to fix behavior with any of this options, or maybe there is something more elegant?
So... in case anyone interested, here is my "research".
Solution 1) Failed. I found a few discussion about this behavior and some says it's a bug and the others it's a feature, but any of them assumes that it cannot be solved in managed code. I also tried attempt from Sheridan's answer with no luck.
Solution 2) Failed. I have some experience with P/Invoke but I generally fail if something goes wrong. In this particular case I tried to use
this.Handle = new WindowInteropHelper(this).Handle;
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
as it suggested across web. This code does not show window. So I tried to show it with
ShowWindow(this.Handle, 4); // I also tried SHOW_NORMAL and few other flags
but window still was not visible
It becames visible if I do this.Visibility = /*visible*/ or this.Show(), and I really see that window is topmost, but it does not solve the issue and fullscreen escapes. If anyone knows how .Show() works internally and how I can show WPF window with P/Invoke, please let me know.
Solution 3) succeed. I found working code with a little googling here and slightly shortened it:
internal class FullscreenCheck
{
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowRect(IntPtr hwnd, out RECT rc);
// I hope this handles never changes
private static IntPtr hndlDesktop = GetDesktopWindow();
private static IntPtr hndlShell = GetShellWindow();
public static bool IsFullscreen()
{
var hndlForeground = GetForegroundWindow();
if (hndlForeground == null || hndlForeground == IntPtr.Zero || hndlForeground == hndlDesktop || hndlForeground == hndlShell)
{
return false;
}
RECT appBounds;
GetWindowRect(hndlForeground, out appBounds);
var screenBounds = System.Windows.Forms.Screen.FromHandle(hndlForeground).Bounds;
return ((appBounds.Bottom - appBounds.Top) == screenBounds.Height && (appBounds.Right - appBounds.Left) == screenBounds.Width);
}
So the last step is just not call .Show() if IsFullscreen() == true
I'm not totally sure about this, but how about trying this?:
tooltip.Owner = referenceToParentWindow;
If you launch this from MainWindow.xaml.cs, then you would use this:
tooltip.Owner = this;
If you launch this from outside the MainWindow.xaml.cs file, then you could (maybe) use this:
tooltip.Owner = Application.Current.MainWindow;

How do I create a fully transparent winform in C# that is interactive?

I am trying to create a form in C# that is fully transparent, but will not allow clicks to go through it to the other windows below.
I have found two methods that were promising, but did not achieve the results I wanted.
The first is by setting the background color and transparency key to the same value. This gives me the transparent form, but clicking goes through.
this.BackColor = Color.Red;
this.TransparencyKey = Color.Red;
The other thing I tried is to set the opacity of the form to 1%. This creates the effects I wanted - almost. I get a 99% transparent form, but there is a slight color alteration to whatever is underneath the form. Since the app I am making is meant to be used in color-sensitive context (graphic design and such), even a tiny alteration of the colors is unacceptable. So I turn to you, dear SO. Can this be done?
I have found the solution, and I am sharing it with you guys as well.
The answer was quite simple: I set: this.TransparencyKey = Color.Lime;
And then I used a 1x1px Lime PNG as the background image. This also has the added benefit of not obscuring the form border and title bar. I will remove them later, but at the moment, it's useful to know where the form is located.
I found a solution to this completely by accident.
I wanted a click through transparent window and got it by using the answer in this SO question:
C# cursor highlighting/follower
That answer uses a transparency filter of LightGreen, but I thought that I might need to use that Color so I changed it to AliceBlue and click through stopped working. I switched back to LightGreen and it started working again.
Instead of trying to capture mouse actions on the transparent form, you can try just capturing mouse actions system-wide (clicks, moves) and handle them as you need.
This can be done in the following way (assuming the drawn-on form keeps maximized. If not, see the next paragraphs below):
Take screenshot of the current screen.
Create a form and use the screenshot as the background image.
Remove form title from the form, simply make it as a panel.
While the solution above solves what you want, you need to answer the question:
How will the user close the form he's drawing on?
If the form needs to be resized-moved - complicated version
However, if you want to resize this form (just noticed your edit with the new screenshots), then you need to cut the part of the taken screenshot and show it as the background of the form. But this goes farther then: you need to do it every time the form is resized or moved.
I personally would take the first (simpler) approach.
You can set hook to catch the mouse events.
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public class MouseStruct
{
public Point pt;
public int hwnd;
public int wHitTestCode;
public int dwExtraInfo;
}
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private int hHook;
public const int WH_MOUSE_LL = 14;
public static HookProc hProc;
public int SetHook()
{
hProc = new HookProc(MouseHookProc);
hHook = SetWindowsHookEx(WH_MOUSE_LL, hProc, IntPtr.Zero, 0);
return hHook;
}
public void UnHook()
{
UnhookWindowsHookEx(hHook);
}
//callback function, invoked when there is an mouse event
private int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
var MyMouseStruct = (MouseStruct)Marshal.PtrToStructure(lParam, typeof(MouseStruct));
if (nCode < 0)
{
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
else
{
switch wParam{
case (IntPtr)513:
//click
//do whatever you want
case (IntPtr)512:
//move
case (IntPtr)514:
//release
default:
}
Cursor.Position = MyMouseStruct.pt;
//stop the event from passed to other windows.
return 1;
}
}
More details at MSDN.

C# Retrieving Screen Update Rectangle

I am trying to create a program that makes lots of screenshots in succession. Instead of re-creating the screenshots over and over, I only want to find the changes between screens.
To do this I used the GetUpdateRect() method on a screen-level. Unfortunately it does not give me correct data. As I'm relatively new to C#, I'm sure I did something wrong :P
This code should log all the screen changes, but instead it returns [0,0,0,0]:
[DllImport("User32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("User32.dll")]
public static extern bool GetUpdateRect(IntPtr hWnd, out Rectangle lpRect, bool bErase);
static void Main()
{
Rectangle updateRect;
GetUpdateRect(GetDesktopWindow(), out updateRect, false);
while (true)
{
Thread.Sleep(100);
Console.WriteLine(updateRect);
}
}
All help is greatly appreciated! :D
Try using the code listed on Pinvoke.Net to import the RECT type instead of using System.Drawing.Rectangle (as #Alvin Wong suggests) and changing the signature of the GetUpdateRect() method to match.
HTH

How to get mouse position related to desktop in WPF?

Problem
When you search for such question using google you get a lot of hits but all solutions assume you have at least one window.
But my question is just like I phrased it -- not assumptions at all. I can have a window, but I could have zero windows (because I didn't even show one or I just closed the last one). So in short the solution cannot rely on any widget or window -- the only thing is known, is there is a desktop (and app running, but it does not have any windows).
So the question is -- how to get the mouse position?
Background
I would like to show windows centered to mouse position. There is no such mode in WPF (there are only center to owner, or center to screen) so I have to do it manually. The missing piece is mouse position.
Edits
Thank you all, so now I have the first part of the solution -- raw position. Now there is a problem how to convert the data for WPF. I found such topic:
WPF Pixels to desktop pixels
but again, it assumes having some window.
Then I googled more and I found solution:
http://jerryclin.wordpress.com/2007/11/13/creating-non-rectangular-windows-with-interop/
the code includes class for scaling up/down coordinates relying only on info about desktop. So joining those two pieces, I finally get the solution :-). Thanks again.
Getting the Screen Coordinates:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
private void WritePoint(object sender, RoutedEventArgs e)
{
POINT p;
if (GetCursorPos(out p))
{
System.Console.WriteLine(Convert.ToString(p.X) + ";" + Convert.ToString(p.Y));
}
}
Converting Pixels to WPF Units:
[DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
private Point ConvertPixelsToUnits(int x, int y)
{
// get the system DPI
IntPtr dDC = GetDC(IntPtr.Zero); // Get desktop DC
int dpi = GetDeviceCaps(dDC, 88);
bool rv = ReleaseDC(IntPtr.Zero, dDC);
// WPF's physical unit size is calculated by taking the
// "Device-Independant Unit Size" (always 1/96)
// and scaling it by the system DPI
double physicalUnitSize = (1d / 96d) * (double)dpi;
Point wpfUnits = new Point(physicalUnitSize * (double)x,
physicalUnitSize * (double)y);
return wpfUnits;
}
Putting both together:
private void WriteMouseCoordinatesInWPFUnits()
{
POINT p;
if (GetCursorPos(out p))
{
Point wpfPoint = ConvertPixelsToUnits(p.X, p.Y);
System.Console.WriteLine(Convert.ToString(wpfPoint.X) + ";" + Convert.ToString(wpfPoint.Y));
}
}
Two options:
Use System.Windows.Forms.Control.MousePosition, or p/invoke
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern bool GetCursorPos([In, Out] NativeMethods.POINT pt);
The first option already does the p/invoke for you. I'm not entirely sure it requires you have some UI splashed up, but I don't think so. Yes, its winforms and not wpf, but it really doesn't have anything to do with where its located at.
If you want to skip any dependencies on system.windows.forms.dll then check out more information about the second on pinvoke.net.
I stumbled over that thread while looking for a solution for the same problem. In the meantime, I found PointToScreen, which does not require any P/Invoke. The method is available on any Visual starting .NET 3.0 (and thus UIElement, Control, etc.) and an implementation would look like this:
protected void OnMouseLeave(object Sender, MouseEventArgs e) {
var relativePosition = e.GetPosition(this);
var screenPosition = this.PointToScreen(relativePosition);
}

Categories