Long long time ago (in a galaxy far away), I used to program some fun tools to assist me (as having a disability) or just support my lazyness :)
Tools that do things like "wait 30 seconds and then press play on my media player" or "save a list of all song names from winamp that streaming a live m3u based radio". it was almost 20 years ago, using C# and window messages api (not wanting to relay on mouse clicks and strict window size&position). I would've found the window's handle and the "control" handle and interact with it.
The question is: Can I still do it today in the age of Windows 10?
If so, how?
I would appreciate a starting point.
Let's say I want to press play on my bs.player after x seconds, or close an error message that comes up every 10 seconds (well, its not cause' my windows is healthy ..but theoretically).
Thank you :)
Yes you can, if you talk about Windows API.
You need to declare the external WinAPI's signatures as static extern using the DllImport attribute.
For example to know if the screen saver is active or if an app runs in full screen:
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private const int SPI_GETSCREENSAVERRUNNING = 0x0072;
[DllImport("user32.dll", SetLastError = true)]
static extern bool SystemParametersInfo(int action, int param, ref int retval, int updini);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
static private extern bool GetWindowRect(HandleRef hWnd, [In, Out] ref RECT rect);
[DllImport("user32.dll")]
static private extern IntPtr GetForegroundWindow();
static private bool IsForegroundFullScreen()
{
return IsForegroundFullScreen(null);
}
static private bool IsForegroundFullScreen(Screen screen)
{
if ( screen == null ) screen = Screen.PrimaryScreen;
RECT rect = new RECT();
GetWindowRect(new HandleRef(null, GetForegroundWindow()), ref rect);
return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top)
.Contains(screen.Bounds);
}
private bool IsScreensaverActive()
{
int active = 1;
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref active, 0);
return active != 0;
}
private bool IsForegroundFullScreenOrScreensaver()
{
return IsForegroundFullScreen() || IsScreensaverActive();
}
Calling Win32 DLLs in C#
c# dllimport with pointers
Related
I am trying to make a makeshift onscreen keyboard for Windows 10 and need the background to be transparent, to make it more convenient for the user (the keys are already transparent). I, however, have no idea how to make the background transparent.
Any help would be greatly appreciated.
I believe that I am essentially looking for an updated version of the code in this thread show below:
using System;
using System.Runtime.InteropServices;
using UnityEngine;
public class TransparentWindow : MonoBehaviour
{
[SerializeField]
private Material m_Material;
private struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll")]
static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", EntryPoint = "SetLayeredWindowAttributes")]
static extern int SetLayeredWindowAttributes(IntPtr hwnd, int crKey, byte bAlpha, int dwFlags);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
private static extern int SetWindowPos(IntPtr hwnd, int hwndInsertAfter, int x, int y, int cx, int cy, int uFlags);
[DllImport("Dwmapi.dll")]
private static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
const int GWL_STYLE = -16;
const uint WS_POPUP = 0x80000000;
const uint WS_VISIBLE = 0x10000000;
const int HWND_TOPMOST = -1;
void Start()
{
// You really don't want to enable this in the editor, but it works there..
int fWidth = Screen.width;
int fHeight = Screen.height;
var margins = new MARGINS() { cxLeftWidth = -1 };
var hwnd = GetActiveWindow();
SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
// Transparent windows with click through
SetWindowLong(hwnd, -20, 524288 | 32);//GWL_EXSTYLE=-20; WS_EX_LAYERED=524288=&h80000, WS_EX_TRANSPARENT=32=0x00000020L
SetLayeredWindowAttributes(hwnd, 0, 255, 2);// Transparency=51=20%, LWA_ALPHA=2
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, fWidth, fHeight, 32 | 64); //SWP_FRAMECHANGED = 0x0020 (32); //SWP_SHOWWINDOW = 0x0040 (64)
DwmExtendFrameIntoClientArea(hwnd, ref margins);
}
void OnRenderImage(RenderTexture from, RenderTexture to)
{
Graphics.Blit(from, to, m_Material);
}
}
The code given did not work, so I assume that it is outdated. I have no idea how to update it myself, since it is a bit out of my skill set. When I upload the code to Unity, it just says that there are errors in the code and that it is not a valid script. When I open the script, however, no errors appear.
I expect to be able to have a relatively good view of whatever is behind my keyboard, like my desktop, but I actually just see a black plane.
Update:
So apparently the error message was caused by my script not having the same name as my class. I spent over 4 hours yesterday trying to fix that error message, and this naming incident was the cause :(. Thanks Ruzihm. Anyway, now that the error message is gone, when I run or build the program, my transparent window material just comes up: a dark pink. I then changed my Unity version back to 2018.2.16f1, with no success. Then I removed the #if !Unity Editor line to get the transparency to work perfectly in the editor, but not when I build it. Note, click through does work when I build it and when I run it in the editor.
As discovered in the comments, the problem was fixed when the camera's clear flags were set to solid color and the pink transparent window material was replaced a with white transparent material.
I'm trying to move the window of the program I am building inside of unity. I'm getting it's handle via interating through all processes in Process.GetProcesses(). Then, I'm calling SetWindowPos, but nothing happens. Here's my code.
internal static void CheckHandle()
{
Process[] ps = Process.GetProcesses();
foreach (Process p in ps)
{
try
{
if (string.Equals(p.ProcessName, "TestBuild0001"))
{
_correctHandle = true;
_handle = p.Handle;
}
}
catch
{
//no catch, simply exited process
}
}
}
internal static void SetPosition()
{
if (!_correctHandle)
CheckHandle();
if (_correctHandle)
{
NewGUI.SetWarning("Window set!",5,50,900,300,50);
SetWindowPos(_handle, 0, 0, 0, 0, 0, 0x0001);
}
}
NewGUI.SetWarning just displays a label and shows up properly. _correctHandle is a simple bool and SetWindowPos is put in as
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
private static extern bool SetWindowPos(IntPtr hwnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
I've tried moving quite a few things to get it to work but am out of ideas. Trying to get foregroundwindow brings back a entirely incorrect handle, findwindow for name brings back 0, and quite a few other things don't seem to work. Anyone know what my error could be/
p.Handle is the process' handle, not the window handle. You want p.MainWindowHandle.
It's also possible that the process you're trying to attach to has a hidden message-only main window. To figure that out, you'd need to use a tool like Spy++ to look at the structure of the windows.
If all else fails Spy++ will also give you a window class, which you can use with FindWindowEx to locate the window. (I do the same trick with Sticky Notes windows in this answer)
The GetActiveWindow idea from this thread : Any way to bring Unity3d to the foreground? got me the right window ID, but changing resolutions still caused me issues since unity was setting the window location to halfway between both monitors after my SetWindowPos is called. Here's my solution for other people needing something like this.
void FixedUpdate()
{
if (Handle == IntPtr.Zero)
{
Handle = GetActiveWindow();
uint uP;
GetWindowThreadProcessId(Handle, out uP);
System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById((int)uP);
if (string.Equals(p.ProcessName, "Unity"))
AllowReset = false;
else if (string.Equals(p.ProcessName, "TestBuild00001"))
AllowReset = true;
else
Handle = IntPtr.Zero;
}
}
void Update()
{
if (ScreenSet && AllowReset && GuiValidReset)
{
GuiValidReset = false;
ScreenSet = false;
SetPosition();
}
}
void OnGUI()
{
if (ScreenSet && AllowReset && !GuiValidReset)
GuiValidReset = true;
}
internal static void SetPosition()
{
SetWindowPos(Handle, 0, 0, 0, 0, 0, 0x0001);
}
Dll imports of
[DllImport("User32.dll")]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
internal static extern IntPtr GetActiveWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hwnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
Did update and ongui check to assure that a full redraw of unity would happen before positioning the window, since calling it at the same time a resolution change caused the window to reset to midscreen. ScreenSet is set to true right after my resolution is set.
I am try to get form2 positioned relative to form1. I've tried many things an nothing seems to work right. I wanted to try:
http://www.pinvoke.net/default.aspx/user32/MoveWindow.html
As a newbie to windows programming especially C# I'm looking at the syntax/example and I find it difficult to know what to put in for the parameters. I did get a different simpler p/invoke to work:
using System.Runtime.InteropServices;
...
public partial class Form1 : Form
{
public Form1()
{ InitializeComponent(); }
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateDirectory(string lpPathName,
IntPtr lpSecurityAttributes);
private void Form1_Load(object sender, EventArgs e) { }
private void button1_Click(object sender, EventArgs e)
{ CreateDirectory(#"c:\test4", IntPtr.Zero); }
}
...
I'm taking a guess IntPtr is "saying" I'm pointing at the first node - but only a guess...
The C# signature for MoveWindow:
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
there's comments on this as well on the site. "IntPtr hWnd" - I need to get that associated with Form2 (?) , do I repaint? I'm trying to show I've looked at it and tried to figure it out - I know we are getting it from the system's dll's...the x-y I got but getting it "with" Form2 I'm lost. Help appreciated.
In general you wouldn't need PInvoke for something as simple as this.
As long as you have a reference to form2 from form1 then you can easily do this by listening to the LocationChanged event of form1. When form1 moves then you can move form2 by doing the following:
var location = this.Location;
location.Offset(xoffset, yoffset);
form2.Location = location;
That would normally be enough to make sure form2 is placed somewhere relatively to form1 and that its position is updated when form1 is moved. You may have to set an initial position of form2 if the LocationChanged event is not called when the form is first created.
Something like this should work. Tested too. You can alter this to fit exactly what you wanted to do, which shouldn't be an issue at all.
// Win32 RECT
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
// GetWindowRect gets the win32 RECT by a window handle.
[DllImport("user32.dll", SetLastError=true)]
static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
// MoveWindow moves a window or changes its size based on a window handle.
[DllImport("user32.dll", SetLastError = true)]
static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
// MoveForms moves one form to another using the win api.
void MoveForms(Form fromForm, Form toForm)
{
IntPtr hWnd_from = fromForm.Handle; // fromForm window handle
IntPtr hWnd_to = toForm.Handle; // toForm window handle
RECT rect_from, rect_to; // RECT holders for fromForm and toForm
if (GetWindowRect(hWnd_from, out rect_from) &&
GetWindowRect(hWnd_to, out rect_to)) // if it gets the win32 RECT for both the fromForm and toForm do the following ...
{
int x_to = rect_to.Left; // toForm's X position
int y_to = rect_to.Top; // toForm's Y position
int width_from = rect_from.Right - rect_from.Left; // fromForm's width
int height_from = rect_from.Bottom - rect_from.Top; // fromForm's height
// Moves fromForm to toForm using the x_to, y_to for X/Y and width_from, height_from for W/H.
MoveWindow(hWnd_from, x_to, y_to, width_from, height_from, true);
}
}
I've an application which has a system tray icon. While uninstalling I'm killing the process if its running. So, as am not gracefully stopping the app, the icon remains in the system tray and will remove only if we hover the mouse on it. I wrote a code that would run the cursor along the tray and get the cursor back in its initial position. This is what I have done:
[DllImport("user32.dll")]
static extern IntPtr FindWindow(string className, string windowName);
[DllImport("user32.dll")]
static extern IntPtr FindWindowEx(IntPtr parent, IntPtr child, string className, string windowName);
[DllImport("user32.dll")]
static extern bool GetWindowRect(HandleRef handle, out RECT rct);
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
void RefreshTray()
{
IntPtr taskbar_Handle = FindWindow("Shell_Traywnd", "");
IntPtr tray_Handle = FindWindowEx(taskbar_Handle, IntPtr.Zero, "TrayNotifyWnd", "");
RECT rct;
if (!(GetWindowRect(new HandleRef(null, tray_Handle), out rct)))
{
}
System.Drawing.Point init = Control.MousePosition;
for (int i = rct.Left; i < rct.Right-20; i++)
{
Cursor.Position = new System.Drawing.Point(i, (rct.Bottom + rct.Top) / 2);
}
Cursor.Position = init;
}
This works good in all the cases except when the option "do not show notification icons" is enabled. Is there some way I could refresh the tray in this case?
EDIT
As the comments suggested I changed my approach. Instead of killing the tray application, I established a communication between my application service (yeah, forgot to mention, I have a service too running along with the application) and tray application. While uninstalling, I stop the service, from the service stop method I would send a socket message of a particular format to the tray application and ask it to close and I would set the notify icon visibility to false. This would leave the Tray Application running in background so I am using "taskkill" to remove the application. It worked fine in Win7 and Vista, but is not working properly in Win XP. But I have not written any environment specific code. Any possible clue?
That's similar to what I use.
A simple floating Keyboard I added to a touch gallery interface. The user wanted to also have my keyboard as a standalone application on their desktop. So I did this, created a tray app for it. Now - what if its open and they launch my gallery?
They would have two keyboards.
Sure - the user could end the first - but its easier to just end it. There are no repercussions from me killing it, so I do. But the tray Icon remains, as its waiting for an event. To get around this, I refresh the Tray area.
Please note - This would only work on an English Locale Installation. To get this to work on another language, change "User Promoted Notification Area", and "Notification Area" to the translated / equivalent string.
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass,
string lpszWindow);
[DllImport("user32.dll")]
public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
public static void RefreshTrayArea()
{
IntPtr systemTrayContainerHandle = FindWindow("Shell_TrayWnd", null);
IntPtr systemTrayHandle = FindWindowEx(systemTrayContainerHandle, IntPtr.Zero, "TrayNotifyWnd", null);
IntPtr sysPagerHandle = FindWindowEx(systemTrayHandle, IntPtr.Zero, "SysPager", null);
IntPtr notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "Notification Area");
if (notificationAreaHandle == IntPtr.Zero)
{
notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32",
"User Promoted Notification Area");
IntPtr notifyIconOverflowWindowHandle = FindWindow("NotifyIconOverflowWindow", null);
IntPtr overflowNotificationAreaHandle = FindWindowEx(notifyIconOverflowWindowHandle, IntPtr.Zero,
"ToolbarWindow32", "Overflow Notification Area");
RefreshTrayArea(overflowNotificationAreaHandle);
}
RefreshTrayArea(notificationAreaHandle);
}
private static void RefreshTrayArea(IntPtr windowHandle)
{
const uint wmMousemove = 0x0200;
RECT rect;
GetClientRect(windowHandle, out rect);
for (var x = 0; x < rect.right; x += 5)
for (var y = 0; y < rect.bottom; y += 5)
SendMessage(windowHandle, wmMousemove, 0, (y << 16) + x);
}
Shouldn't be difficult to close the current instance using something like pipes, or TCP if you don't feel like doing that and aren't running .NET4.0.
As everyone is implying, the issue is that by killing your process it doesn't get a chance to unregister its tray icon instance, so it sticks around until Windows attempts to send an event to it (the next time you move the mouse over it) at which point Windows will remove it.
Depending on what installer you are using, this could be quite easy or more difficult. Most popular installer frameworks allow for plugins, and a few of them support Pipes, many more support TCP requests. Alternatively, write up a small executable that your installer can run before it begins the uninstall process, which communicates with your primary app and sends a close message.
As a final note, if you can use .NET4.0 then I'd suggest looking at the built in System.IO.Pipes namespace and the included classes.
Use this tool
http://www.codeproject.com/Articles/19620/LP-TrayIconBuster
It iterates through ToolBarButtons in TrayNotifyWnd & NotifyIconOverflowWindow and removes those with null file names.
I found this (http://maruf-dotnetdeveloper.blogspot.com/2012/08/c-refreshing-system-tray-icon.html) solution worked for me.
Is there a way to open an application to a saved set of dimensions and positions (on Windows) via a script? I'd also like to, of course, save the dimensions and positions of open applications -- the other side of this script. Any suggestions? If a script can't get this done on a Windows machine is there a way with C#/.NET?
You can do this using a User32.dll call to SetWindowPos.
For example:
[DllImport("User32.dll")]
public static extern IntPtr FindWindow(string className, string windowName);
[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr windowHandle, IntPtr parentWindowHandle, int x, int y, int width, int height, PositionFlags positionFlags);
public static readonly IntPtr HWND_TOP = new IntPtr(0);
[Flags]
public enum PositionFlags : uint
{
ShowWindow = 0x40
}
static void Main(string[] args)
{
var windowHandle = FindWindow(null, "Untitled - Notepad");
SetWindowPos(windowHandle, HWND_TOP, 0, 0, 640, 480, PositionFlags.ShowWindow);
}
This will find the window with the title "Untitled - Notepad", move it to 0, 0, and resize it to 640x480. I have added the bare minimal number of PositionFlags and HWND flags, look at the link I provided if you require more and add them in the same way :)
Oh, and to read the dimensions out, take a look at GetWindowRect. Here's an example of how to use this from c#: Example.
Take a look at Application Settings.