Im working on unity game tool, where you need to drag and drop file from explorer, process there, and drag and drop it back to explorer, notepad, paint or something else.
Problem: I cant drop file in explorer, or somewhere else, but I can "copy" file to empty space on left sidebar or scrollbar.
Can't copy filee and folders space: https://i.stack.imgur.com/J4DBU.png
Can "copy" to empty space: https://i.stack.imgur.com/ZdaYD.png
Can "copy" to scrollbar: https://i.stack.imgur.com/2rWDd.png
private void StartDrag()
{
var data = new DataObject();
var ls = new System.Collections.Specialized.StringCollection();
ls.Add("C:/ExeMask.log");
data.SetFileDropList(ls);
OleInitialize(null);
int[] array = new int[1];
DoDragDrop(data, new DropSource(), (int)DragDropEffects.Copy, array);
OleUninitialize();
}
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int OleInitialize(Action lpVoid);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern void OleUninitialize();
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int DoDragDrop(System.Runtime.InteropServices.ComTypes.IDataObject dataObject, IOleDropSource dropSource, int allowedEffects, int[] finalEffect);
[ComImport]
[Guid("00000121-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleDropSource
{
[PreserveSig]
int OleQueryContinueDrag(int fEscapePressed, [In][MarshalAs(UnmanagedType.U4)] int grfKeyState);
[PreserveSig]
int OleGiveFeedback([In][MarshalAs(UnmanagedType.U4)] int dwEffect);
}
public class DropSource : IOleDropSource
{
public DropSource()
{
}
public int OleQueryContinueDrag(int fEscapePressed, [In, MarshalAs(UnmanagedType.U4)] int grfKeyState)
{
QueryContinueDragEventArgs qcdevent = null;
bool escapePressed = fEscapePressed != 0;
DragAction cancel = DragAction.Continue;
if (escapePressed)
{
cancel = DragAction.Cancel;
}
else if ((((grfKeyState & 1) == 0) && ((grfKeyState & 2) == 0)) && ((grfKeyState & 0x10) == 0))
{
cancel = DragAction.Drop;
}
qcdevent = new QueryContinueDragEventArgs(grfKeyState, escapePressed, cancel);
//this.peer.OnQueryContinueDrag(qcdevent);
switch (qcdevent.Action)
{
case DragAction.Drop:
return 0x40100;
case DragAction.Cancel:
return 0x40101;
}
return 0;
}
public int OleGiveFeedback([In, MarshalAs(UnmanagedType.U4)] int dwEffect)
{
GiveFeedbackEventArgs gfbevent = new GiveFeedbackEventArgs((DragDropEffects)dwEffect, true);
//this.peer.OnGiveFeedback(gfbevent);
if (gfbevent.UseDefaultCursors)
{
return 0x40102;
}
return 0;
}
}
What I tried:
Invoke and dont invoke OleInitialize and OleUninitialize
Import exactly the same System.Windows.Forms.dll that WinForms used into unity (Failed to import due to no CIL found in unity)
new Control().DoDragDrop - same result (but I used not exactly the same dll)
Run app as admin.
Maybe there is some lib, that can start drag and drop without own gui - just invoke method and boom! drag and drop initiated?
I saw c++ solutions, but Im pretty bad with it.
Related
I am working on a console executable that may run in multi monitor environment.
It may be started by double clicking on the Exe file name inside Windows Explorer.
I want to move the console to the same monitor as the Windows Explorer window that started it. Is there any way to do it?
I was able to get parent process and get MainWindowHandle.
But it will not give me the correct window. It will give me the first Explorer window. See code below. SetupConsoleWindow is the main function.
internal static class NativeMethods
{
internal const int SWP_NOSIZE = 0x0001;
[DllImport("kernel32.dll")]
internal static extern bool AllocConsole();
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
internal static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetConsoleWindow();
}
public static Process GetParentProcess()
{
var currentProcessName = Process.GetProcessById(Process.GetCurrentProcess().Id).ProcessName;
var processesByName = Process.GetProcessesByName(currentProcessName);
for (var index = 0; index < processesByName.Length; index++)
{
string processIndexdName = index == 0 ? currentProcessName : currentProcessName + "#" + index;
var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
if ((int)processId.NextValue() == Process.GetCurrentProcess().Id)
break;
}
var parentPerformanceCounter = new PerformanceCounter("Process", "Creating Process ID", currentProcessName);
Process parentProcess = Process.GetProcessById((int)parentPerformanceCounter.NextValue());
return parentProcess;
}
public static void SetupConsoleWindow()
{
NativeMethods.AllocConsole();
var parentProcess = GetParentProcess();
if (parentProcess != null)
{
IntPtr consoleWindowHandle = NativeMethods.GetConsoleWindow();
Screen parentScreen = Screen.FromHandle(parentProcess.MainWindowHandle);
Rectangle monitor = parentScreen.WorkingArea;
NativeMethods.SetWindowPos(consoleWindowHandle, 0, monitor.Left, monitor.Top, 0, 0, NativeMethods.SWP_NOSIZE);
}
}
So what you want to do is launch your program on the same monitor as the Windows Explorer window that started it. Doing this the way you currently intend to may be technically possible, but it would be cumbersome and flaky. It would involve enumerating all the open windows, figuring out which ones are Explorer windows, then for each of those which folder is being viewed and then which file is currently selected. You would then compare the name of that file with your program's name and, if multiple such windows are found, choose the most top-level one. You would have to hope that the file did not somehow get deselected between the double click and your main().
This process is well documented here. For reference, here is what the code to get the currently selected item looks like in C++:
#include <shlobj.h>
#include <exdisp.h>
TCHAR g_szPath[MAX_PATH];
TCHAR g_szItem[MAX_PATH];
void CALLBACK RecalcText(HWND hwnd, UINT, UINT_PTR, DWORD)
{
HWND hwndFind = GetForegroundWindow();
g_szPath[0] = TEXT('\0');
g_szItem[0] = TEXT('\0');
IShellWindows *psw;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
IID_IShellWindows, (void**)&psw))) {
VARIANT v;
V_VT(&v) = VT_I4;
IDispatch *pdisp;
BOOL fFound = FALSE;
for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
V_I4(&v)++) {
IWebBrowserApp *pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
hwndWBA == hwndFind) {
fFound = TRUE;
IServiceProvider *psp;
if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
IShellBrowser *psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
IID_IShellBrowser, (void**)&psb))) {
IShellView *psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
IFolderView *pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
(void**)&pfv))) {
IPersistFolder2 *ppf2;
if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
(void**)&ppf2))) {
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
}
int iFocus;
if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
LPITEMIDLIST pidlItem;
if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
IShellFolder *psf;
if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
(void**)&psf))) {
STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
SHGDN_INFOLDER,
&str))) {
StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
}
psf->Release();
}
CoTaskMemFree(pidlItem);
}
}
CoTaskMemFree(pidlFolder);
}
ppf2->Release();
}
pfv->Release();
}
psv->Release();
}
psb->Release();
}
psp->Release();
}
}
pwba->Release();
}
pdisp->Release();
}
psw->Release();
}
InvalidateRect(hwnd, NULL, TRUE);
}
However, you may be making this more complicated than it needs to be. Consider this instead:
Get the current mouse coordinates with Cursor.Position (or GetCursorPos)
Get the monitor on which the mouse cursor is present
Move your window to that monitor
I would like to load the icons from multiple files dragged onto a listview and some of the information of those files, but the process is going very slow.
In my test, a list of 381 files dragged (some are not exe so those are skipped in my code), takes over 2 minutes to load the icon from the file, and add them to the listview.
Following is condensed version of my code :
public Form1()
{
InitializeComponent();
listView1.DragEnter += ListView1_DragEnter;
listView1.DragDrop += ListView1_DragDrop;
listView1.LargeImageList = new ImageList() { ImageSize = new Size( 64, 64) };
listView1.SmallImageList = new ImageList();
listView1.AllowDrop = true;
}
private void ListView1_DragDrop(object sender, DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] values = (string[])e.Data.GetData(DataFormats.FileDrop);
Convert.IconExtractor i = new Convert.IconExtractor();
foreach (var v in values)
{
var info = new FileInfo(v);
if(info.Extension.ToLower() == ".exe")
{
ListViewItem item = new ListViewItem();
listView1.LargeImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Large));
listView1.SmallImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Small));
item.Text = Path.GetFileNameWithoutExtension(info.FullName);
item.ImageIndex = listView1.SmallImageList.Images.Count -1;
item.Tag = info.FullName;
listView1.Items.Add(item);
}
}
listView1.Refresh();
}
}
private void ListView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.All;
}
}
The method used for extraction is pasted here for convenience :
public enum IconSize
{
Small,
Large
}
public class IconExtractor
{
//----------------------------------------------------------------------------
//
// Description: Extracts the icon associated with any file on your system.
// Author: WidgetMan http://softwidgets.com
//
// Remarks...
//
// Class requires the IconSize enumeration that is implemented in this
// same file. For best results, draw an icon from within a control's Paint
// event via the e.Graphics.DrawIcon method.
//
//----------------------------------------------------------------------------
private const int SHGFI_ICON = 0x100;
private const int SHGFI_SMALLICON = 0x1;
private const int SHGFI_LARGEICON = 0x0;
private struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public int dwAttributes;
[VBFixedString(260), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[VBFixedString(80), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[DllImport("shell32", EntryPoint = "SHGetFileInfoA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int ByValcbFileInfo, int uFlags);
public IconExtractor()
{
}
public System.Drawing.Icon Extract(string File, IconSize Size)
{
SHFILEINFO aSHFileInfo = default(SHFILEINFO);
int cbFileInfo = 0;
int uflags = 0;
System.Drawing.Icon Icon = default(System.Drawing.Icon);
switch (Size)
{
case IconSize.Large:
uflags = SHGFI_ICON | SHGFI_LARGEICON;
break;
default:
uflags = SHGFI_ICON | SHGFI_SMALLICON;
break;
}
cbFileInfo = Marshal.SizeOf(aSHFileInfo);
SHGetFileInfo(File, 0, ref aSHFileInfo, cbFileInfo, uflags);
Icon = System.Drawing.Icon.FromHandle(aSHFileInfo.hIcon);
return Icon;
}
public System.Drawing.Icon Extract(string File)
{
return this.Extract(File, IconSize.Small);
}
}
}
What can I do to make this process quick. The concept is to make a quick launcher for multiple applications.
Also of note, while this process is happening, the drag-collection icon is still 'hung' on windows explorer until the drag & drop task has completed fully (looped through all the files).
Here is a rough draft to give a visual of the application:
(yes, i know the icons extracted look like crap as well, but I think that is a separate issue from the slow issue I am having)
My advice is not to load all 381 icons in one step, particularly if the items are not visible. Load them on demand as items are scrolled into view, of if the view is large enough, load them in the background using your threading/task/concurrency technology of choice.
That's what Windows does.
To speed up loading, you may want to use the System Image List which would most likely benefit from caching. Here's some code for getting the large icon. Just change size to be SHGFI_ICON for your use.
public static Icon GetLargeIcon(string FileName, bool jumbo, bool useFileAttributes=false)
{
var shinfo = new SHFILEINFO();
uint flags;
flags = SHGFI_SYSICONINDEX;
if (useFileAttributes)
{
flags |= SHGFI_USEFILEATTRIBUTES;
}
var res = SHGetFileInfo(FileName, FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint) Marshal.SizeOf(shinfo), flags);
if (res == IntPtr.Zero)
{
throw (new FileNotFoundException());
}
var iconIndex = shinfo.iIcon;
// Get the System IImageList object from the Shell:
var iidImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
IImageList iml;
var size = jumbo
? SHIL_JUMBO
: SHIL_EXTRALARGE;
var hres = SHGetImageList(size, iidImageList, out iml);
if (hres != 0)
{
throw (new Exception("Error SHGetImageList"));
}
IntPtr hIcon;
const int ILD_TRANSPARENT = 1;
hres = iml.GetIcon(iconIndex, ILD_TRANSPARENT, out hIcon);
var icon = Icon.FromHandle(hIcon);
icon = icon.Clone() as Icon;
var bm = icon.ToBitmap();
DestroyIcon(hIcon);
return icon;
}
I've created some global Hot keys for my application and I want them to work only if my application is active. (It should not work if my application is not the active form).
So how can I check if my C# winform application is the active form among all the other windows applications?
I tried
if(this.Focused)
//Do somthing
But it's not working
Try this:
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
public static bool Activates()
{
var x = GetForegroundWindow();
if (x == IntPtr.Zero) {
return false;
}
var y = Process.GetCurrentProcess().Id;
int i;
GetWindowThreadProcessId(x, out i);
return i == y;
}
You can also refer: C#: Detecting which application has focus
You can use Windows API function GetForegroundWindow and GetWindowText.
GetForegroundWindow :
The GetForegroundWindow function returns a handle to the window with which the user is currently working.
GetWindowText:
The GetWindowText function copies the text of the specified window's title bar (if it has one) into a buffer.
Add below code to declare API functions :
[ DllImport("user32.dll") ]
static extern int GetForegroundWindow();
[ DllImport("user32.dll") ]
static extern int GetWindowText(int hWnd, StringBuilder text, int count);
Start a timer :
private void timer1_Tick(object sender, System.EventArgs e)
{
GetActiveWindow();
}
Active window function :
private void GetActiveWindow()
{
const int nChars = 256;
int handle = 0;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if ( GetWindowText(handle, Buff, nChars) > 0 )
{
this.captionWindowLabel.Text = Buff.ToString();
this.IDWindowLabel.Text = handle.ToString();
}
}
I want to use SystemIcons.Warning but it is too big for my need. I want to resize it.
I have tried :
Icon sizedIcon = new Icon(SystemIcons.Warning, new Size(10,10));
But it does not work, icon remains the same.
Any suggestions?
The .NET Icon class is pretty crippled, it is stuck in the previous decade due to once-relevant support for Windows 98 and Windows 2000. On top of this, Windows does not support loading the system icons in any size other than the system's default icon size. Usually 32x32. Resizing it is going to inevitably look bad.
Do keep in mind that no icon is ever going to look good at 10x10. It is but a fleck on a modern monitor. You do get ahead a bit by starting with an icon size that's close to the desired final size, the less drastic the required resize, the higher the odds that it still looks reasonable. When you can, do favor sizes that are likely to be present in the icon. Like 16x16, 32x32, 48x48.
Anyhoo, this is fixable. Do keep in mind that this takes considerable hack-o-rama since Windows doesn't directly support this. What's required is reading the icon directly from the system DLL resources. And using a more modern winapi, LoadImage() instead of the one that .NET uses, LoadIcon(). Works on XP and up.
Add a new class to your project and paste this code:
using System;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
public class IconEx : IDisposable {
public enum SystemIcons {
Application = 100,
Asterisk = 104,
Error = 103,
Exclamation = 101,
Hand = 103,
Information = 104,
Question = 102,
Shield = 106,
Warning = 101,
WinLogo = 100
}
public IconEx(string path, Size size) {
IntPtr hIcon = LoadImage(IntPtr.Zero, path, IMAGE_ICON, size.Width, size.Height, LR_LOADFROMFILE);
if (hIcon == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
attach(hIcon);
}
public IconEx(SystemIcons sysicon, Size size) {
IntPtr hUser = GetModuleHandle("user32");
IntPtr hIcon = LoadImage(hUser, (IntPtr)sysicon, IMAGE_ICON, size.Width, size.Height, 0);
if (hIcon == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
attach(hIcon);
}
public Icon Icon {
get { return this.icon; }
}
public void Dispose() {
if (icon != null) icon.Dispose();
}
private Icon icon;
private void attach(IntPtr hIcon) {
// Invoke the private constructor so we can get the Icon object to own the handle
var ci = typeof(Icon).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
null, new Type[] { typeof(IntPtr), typeof(bool) }, null);
this.icon = (Icon)ci.Invoke(new object[] { hIcon, true});
}
private const int IMAGE_ICON = 1;
private const int LR_LOADFROMFILE = 0x10;
private const int LR_SHARED = 0x8000;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr GetModuleHandle(string name);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr LoadImage(IntPtr hinst, string lpszName, int uType,
int cxDesired, int cyDesired, int fuLoad);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr LoadImage(IntPtr hinst, IntPtr resId, int uType,
int cxDesired, int cyDesired, int fuLoad);
}
Sample usage:
using (var icon = new IconEx(IconEx.SystemIcons.Warning, new Size(10, 10))) {
e.Graphics.DrawIcon(icon.Icon, 0, 0);
}
It does look better than SystemIcons.Warning. It's squeaky clean when you use 16x16.
How can one determine, in code, how long the machine is locked?
Other ideas outside of C# are also welcome.
I like the windows service idea (and have accepted it) for simplicity and cleanliness, but unfortunately I don't think it will work for me in this particular case. I wanted to run this on my workstation at work rather than home (or in addition to home, I suppose), but it's locked down pretty hard courtesy of the DoD. That's part of the reason I'm rolling my own, actually.
I'll write it up anyway and see if it works. Thanks everyone!
I hadn't found this before, but from any application you can hookup a SessionSwitchEventHandler. Obviously your application will need to be running, but so long as it is:
Microsoft.Win32.SystemEvents.SessionSwitch += new Microsoft.Win32.SessionSwitchEventHandler(SystemEvents_SessionSwitch);
void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e)
{
if (e.Reason == SessionSwitchReason.SessionLock)
{
//I left my desk
}
else if (e.Reason == SessionSwitchReason.SessionUnlock)
{
//I returned to my desk
}
}
I would create a Windows Service (a visual studio 2005 project type) that handles the OnSessionChange event as shown below:
protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
if (changeDescription.Reason == SessionChangeReason.SessionLock)
{
//I left my desk
}
else if (changeDescription.Reason == SessionChangeReason.SessionUnlock)
{
//I returned to my desk
}
}
What and how you log the activity at that point is up to you, but a Windows Service provides quick and easy access to windows events like startup, shutdown, login/out, along with the lock and unlock events.
The solution below uses the Win32 API. OnSessionLock is called when the workstation is locked, and OnSessionUnlock is called when it is unlocked.
[DllImport("wtsapi32.dll")]
private static extern bool WTSRegisterSessionNotification(IntPtr hWnd,
int dwFlags);
[DllImport("wtsapi32.dll")]
private static extern bool WTSUnRegisterSessionNotification(IntPtr
hWnd);
private const int NotifyForThisSession = 0; // This session only
private const int SessionChangeMessage = 0x02B1;
private const int SessionLockParam = 0x7;
private const int SessionUnlockParam = 0x8;
protected override void WndProc(ref Message m)
{
// check for session change notifications
if (m.Msg == SessionChangeMessage)
{
if (m.WParam.ToInt32() == SessionLockParam)
OnSessionLock(); // Do something when locked
else if (m.WParam.ToInt32() == SessionUnlockParam)
OnSessionUnlock(); // Do something when unlocked
}
base.WndProc(ref m);
return;
}
void OnSessionLock()
{
Debug.WriteLine("Locked...");
}
void OnSessionUnlock()
{
Debug.WriteLine("Unlocked...");
}
private void Form1Load(object sender, EventArgs e)
{
WTSRegisterSessionNotification(this.Handle, NotifyForThisSession);
}
// and then when we are done, we should unregister for the notification
// WTSUnRegisterSessionNotification(this.Handle);
I know this is an old question but i have found a method to get the Lock State for a given session.
I found my answer here but it was in C++ so i translated as much as i can to C# to get the Lock State.
So here goes:
static class SessionInfo {
private const Int32 FALSE = 0;
private static readonly IntPtr WTS_CURRENT_SERVER = IntPtr.Zero;
private const Int32 WTS_SESSIONSTATE_LOCK = 0;
private const Int32 WTS_SESSIONSTATE_UNLOCK = 1;
private static bool _is_win7 = false;
static SessionInfo() {
var os_version = Environment.OSVersion;
_is_win7 = (os_version.Platform == PlatformID.Win32NT && os_version.Version.Major == 6 && os_version.Version.Minor == 1);
}
[DllImport("wtsapi32.dll")]
private static extern Int32 WTSQuerySessionInformation(
IntPtr hServer,
[MarshalAs(UnmanagedType.U4)] UInt32 SessionId,
[MarshalAs(UnmanagedType.U4)] WTS_INFO_CLASS WTSInfoClass,
out IntPtr ppBuffer,
[MarshalAs(UnmanagedType.U4)] out UInt32 pBytesReturned
);
[DllImport("wtsapi32.dll")]
private static extern void WTSFreeMemoryEx(
WTS_TYPE_CLASS WTSTypeClass,
IntPtr pMemory,
UInt32 NumberOfEntries
);
private enum WTS_INFO_CLASS {
WTSInitialProgram = 0,
WTSApplicationName = 1,
WTSWorkingDirectory = 2,
WTSOEMId = 3,
WTSSessionId = 4,
WTSUserName = 5,
WTSWinStationName = 6,
WTSDomainName = 7,
WTSConnectState = 8,
WTSClientBuildNumber = 9,
WTSClientName = 10,
WTSClientDirectory = 11,
WTSClientProductId = 12,
WTSClientHardwareId = 13,
WTSClientAddress = 14,
WTSClientDisplay = 15,
WTSClientProtocolType = 16,
WTSIdleTime = 17,
WTSLogonTime = 18,
WTSIncomingBytes = 19,
WTSOutgoingBytes = 20,
WTSIncomingFrames = 21,
WTSOutgoingFrames = 22,
WTSClientInfo = 23,
WTSSessionInfo = 24,
WTSSessionInfoEx = 25,
WTSConfigInfo = 26,
WTSValidationInfo = 27,
WTSSessionAddressV4 = 28,
WTSIsRemoteSession = 29
}
private enum WTS_TYPE_CLASS {
WTSTypeProcessInfoLevel0,
WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1
}
public enum WTS_CONNECTSTATE_CLASS {
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
public enum LockState {
Unknown,
Locked,
Unlocked
}
[StructLayout(LayoutKind.Sequential)]
private struct WTSINFOEX {
public UInt32 Level;
public UInt32 Reserved; /* I have observed the Data field is pushed down by 4 bytes so i have added this field as padding. */
public WTSINFOEX_LEVEL Data;
}
[StructLayout(LayoutKind.Sequential)]
private struct WTSINFOEX_LEVEL {
public WTSINFOEX_LEVEL1 WTSInfoExLevel1;
}
[StructLayout(LayoutKind.Sequential)]
private struct WTSINFOEX_LEVEL1 {
public UInt32 SessionId;
public WTS_CONNECTSTATE_CLASS SessionState;
public Int32 SessionFlags;
/* I can't figure out what the rest of the struct should look like but as i don't need anything past the SessionFlags i'm not going to. */
}
public static LockState GetSessionLockState(UInt32 session_id) {
IntPtr ppBuffer;
UInt32 pBytesReturned;
Int32 result = WTSQuerySessionInformation(
WTS_CURRENT_SERVER,
session_id,
WTS_INFO_CLASS.WTSSessionInfoEx,
out ppBuffer,
out pBytesReturned
);
if (result == FALSE)
return LockState.Unknown;
var session_info_ex = Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);
if (session_info_ex.Level != 1)
return LockState.Unknown;
var lock_state = session_info_ex.Data.WTSInfoExLevel1.SessionFlags;
WTSFreeMemoryEx(WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, ppBuffer, pBytesReturned);
if (_is_win7) {
/* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ee621019(v=vs.85).aspx
* Windows Server 2008 R2 and Windows 7: Due to a code defect, the usage of the WTS_SESSIONSTATE_LOCK
* and WTS_SESSIONSTATE_UNLOCK flags is reversed. That is, WTS_SESSIONSTATE_LOCK indicates that the
* session is unlocked, and WTS_SESSIONSTATE_UNLOCK indicates the session is locked.
* */
switch (lock_state) {
case WTS_SESSIONSTATE_LOCK:
return LockState.Unlocked;
case WTS_SESSIONSTATE_UNLOCK:
return LockState.Locked;
default:
return LockState.Unknown;
}
}
else {
switch (lock_state) {
case WTS_SESSIONSTATE_LOCK:
return LockState.Locked;
case WTS_SESSIONSTATE_UNLOCK:
return LockState.Unlocked;
default:
return LockState.Unknown;
}
}
}
}
Note: The above code was extracted from a much larger project so if i missed a bit sorry. I havn't got time to test the above code but plan to come back in a week or two to check everything. I only posted it now because i didn't want to forget to do it.
NOTE: This is not an answer, but a (contribution) to Timothy Carter answer, because my reputation doesn't allow me to comment so far.
Just in case somebody tried the code from Timothy Carter's answer and did not get it to work right away in a Windows service, there's one property that need to be set to true in the constructor of the service.
Just add the line in the constructor:
CanHandleSessionChangeEvent = true;
And be sure not to set this property after the service is started otherwise an InvalidOperationException will be thrown.
If you're interested in writing a windows-service to "find" these events, topshelf (the library/framework that makes writing windows services much easier) has a hook.
public interface IMyServiceContract
{
void Start();
void Stop();
void SessionChanged(Topshelf.SessionChangedArguments args);
}
public class MyService : IMyServiceContract
{
public void Start()
{
}
public void Stop()
{
}
public void SessionChanged(SessionChangedArguments e)
{
Console.WriteLine(e.ReasonCode);
}
}
and now the code to wire up the topshelf service to the interface/concrete above
Everything below is "typical" topshelf setup.... except for 2 lines which I marked as
/* THIS IS MAGIC LINE */
Those are what get the SessionChanged method to fire.
I tested this with windows 10 x64. I locked and unlocked my machine and I got the desired result.
IMyServiceContract myServiceObject = new MyService(); /* container.Resolve<IMyServiceContract>(); */
HostFactory.Run(x =>
{
x.Service<IMyServiceContract>(s =>
{
s.ConstructUsing(name => myServiceObject);
s.WhenStarted(sw => sw.Start());
s.WhenStopped(sw => sw.Stop());
s.WhenSessionChanged((csm, hc, chg) => csm.SessionChanged(chg)); /* THIS IS MAGIC LINE */
});
x.EnableSessionChanged(); /* THIS IS MAGIC LINE */
/* use command line variables for the below commented out properties */
/*
x.RunAsLocalService();
x.SetDescription("My Description");
x.SetDisplayName("My Display Name");
x.SetServiceName("My Service Name");
x.SetInstanceName("My Instance");
*/
x.StartManually(); // Start the service manually. This allows the identity to be tweaked before the service actually starts
/* the below map to the "Recover" tab on the properties of the Windows Service in Control Panel */
x.EnableServiceRecovery(r =>
{
r.OnCrashOnly();
r.RestartService(1); ////first
r.RestartService(1); ////second
r.RestartService(1); ////subsequents
r.SetResetPeriod(0);
});
x.DependsOnEventLog(); // Windows Event Log
x.UseLog4Net();
x.EnableShutdown();
x.OnException(ex =>
{
/* Log the exception */
/* not seen, I have a log4net logger here */
});
});
My packages.config to provide hints about versions:
<package id="log4net" version="2.0.5" targetFramework="net45" />
<package id="Topshelf" version="4.0.3" targetFramework="net461" />
<package id="Topshelf.Log4Net" version="4.0.3" targetFramework="net461" />
In Windows Task Scheduler, you could create tasks that trigger on workstation lock and on workstation unlock. Each task could write a flag and timestamp to a file to state if the workstation is locked or unlocked and when it happened.
I realize that this is not a programmatic way. It is simpler than writing a service. It won't miss an event because your program happens to not be running at the time of lock/unlock transition.
Below is the 100% working code to find if the PC is locked or not.
Before using this use the namespace System.Runtime.InteropServices.
[DllImport("user32", EntryPoint = "OpenDesktopA", CharSet = CharSet.Ansi,SetLastError = true, ExactSpelling = true)]
private static extern Int32 OpenDesktop(string lpszDesktop, Int32 dwFlags, bool fInherit, Int32 dwDesiredAccess);
[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern Int32 CloseDesktop(Int32 hDesktop);
[DllImport("user32", CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling = true)]
private static extern Int32 SwitchDesktop(Int32 hDesktop);
public static bool IsWorkstationLocked()
{
const int DESKTOP_SWITCHDESKTOP = 256;
int hwnd = -1;
int rtn = -1;
hwnd = OpenDesktop("Default", 0, false, DESKTOP_SWITCHDESKTOP);
if (hwnd != 0)
{
rtn = SwitchDesktop(hwnd);
if (rtn == 0)
{
// Locked
CloseDesktop(hwnd);
return true;
}
else
{
// Not locked
CloseDesktop(hwnd);
}
}
else
{
// Error: "Could not access the desktop..."
}
return false;
}