Window "On Desktop"

Pin window to desktop / Glue window to desktop / Always-on-bottom window

As @JonathanPotter pointed out, when hitting Windows + D or the Show Desktop button, the event WM_WINDOWPOSCHANGING gets fired, and the window gets moved to -32 000, -32 000 (its size also gets changed)

NOTE : a window without the style WS_MINIMIZEBOX seems not receiving WINDOWPOSCHANGING event when hitting Windows + D. Thus, no -32 000 coordinates detection in that case... Also noticed the same issue when using the ex style WS_EX_TOOLWINDOW (as this one gets rid of the minimize box, even if you set the style flag WS_MINIMIZEBOX).
Didn't find a solution for that case, so I'm sticking to an overlapped window.

To prevent this movement, just set the flags SWP_NOMOVE and SWP_NOSIZE on the WINDOWPOS structure passed in the lParam

So as a final result, to achieve the wanted behaviour (i.e always behind every other window but always in front of the desktop), the only needed code to add to the doc's sample is the following, placed in the window procedure WindowProc's switch statement :

EDIT : the best place to force the Z order with HWND_BOTTOM thus ensuring the window is always on bottom is also in the WM_WINDOWPOSCHANGING event. Indeed, calling SetWindowPos to force it in the WM_SIZE event when dragging the window over, as I was doing previously, causes some flickering on the window when resizing it, whereas no flickering occurs when setting directly the hwndInsertAfter property of the WINDOWPOS structure in WM_WINDOWPOSCHANGING.

case WM_WINDOWPOSCHANGING:
{
WINDOWPOS* pos = (WINDOWPOS*)lParam;
// Show desktop (Windows + D) results in the window moved to -32000, -32000 and size changed
if (pos->x == -32000) {
// Set the flags to prevent this and "survive" to the desktop toggle
pos->flags |= SWP_NOMOVE | SWP_NOSIZE;
}
// Also force the z order to ensure the window is always on bottom
pos->hwndInsertAfter = HWND_BOTTOM;
return 0;
}

Window on desktop

My answer is in terms of the Win32 API, not specific to WPF (and probably requiring P/Invoke from C#):

Rainlendar has two options:

  • "On Desktop", it becomes a child of the Explorer desktop window ("Program Manager"). You could achieve this with the SetParent API.
  • "On Bottom" is what you describe - its windows stay at the bottom of the Z-order, just in front of the desktop. It's easy enough to put them there to begin with (see SetWindowPos) - the trick is to stop them coming to the front when clicked. I would suggest handling the WM_WINDOWPOSCHANGING message.

What are the Windows Station and Desktop objects?

Windows Stations and Desktops are securable objects. Their primary function is to act as a security partition/barrier for various resources and features such as the clipboard, hooks, registered messages and the global atom table. Sessions were added at the top of this tree when the Terminal Services/Remote desktop feature was added to Windows. Sessions are also used by the fast user switching feature.

The hierarchy looks something like this:

+ Session for services (Session 0)
| + Window Station ("Service-xyz...")
| + Desktop
|
+ Session for user "Foo"
| + Window Station ("winsta0")
| + Desktop ("Default")
| | + Taskbar and applications
| + Desktop ("Winlogon")
| | + Logon/lock screen
| + Desktop ("Screen-saver", created on demand)
| + Secure screen saver
|
+ Session for user "Bar"
| + Window Station ("winsta0")
| + Desktop ("Default")
| | + Taskbar and applications
| + Desktop ("Winlogon")
| + Logon/lock screen

The interactive window station is named winsta0, and is the only
station that can receive mouse and keyboard input from the interactive
user.

In Vista and later, services run in their own session.

Because journal recording and hooks are per-desktop a normal application cannot listen to the keyboard input when you type in your logon password or accept a UAC prompt because Windows (winlogon.exe) switches to a different desktop under these conditions.

There are various tools tools that lets you explore the Window Station and Desktop objects. SysInternals also wrote a tool that uses/abuses Desktop objects to create virtual desktops.

Although it is from 2000, the "Programming Windows Security" book by Keith Brown might still be the best resource if you want to know more...

Further reading

  • 2007-07-24, Microsoft TechNet, Craig Marcho, Sessions, Desktops and Windows Stations (Archived here.)

AppleScript to raise a window in another space/desktop

I'm offering this as a workaround as it does work in a simple two normal window configuration where there is one window on each Desktop, and is executed from Desktop 1.

Example AppleScript code:

tell application "Google Chrome"
set minimized of window 2 to true
set minimized of window 2 to false
end tell

Note that this does cause seeing the window from Desktop 2 be minimized and restored as if on Desktop 1 but window 2 is then frontmost on Desktop 2 without having switched Spaces.

The was tested in a single display multiple Desktop scenario under macOS Catalina. Other versions of macOS and or Display configuration may present differing results.

Replacing the windows desktop

You need to be on top of desktop, below your apps, and hiding the taskbar.
Id first try to hide the taskbar via Hide TaskBar in WinForms Application

Secondly, set your window to bottom most via using HWND_BOTTOM.
There's some quirky behavior that's best to test across multiple OS's Setting a Windows form to be bottommost


EDIT - ok took a little fiddling because the taskbar keeps showing up. There will be a flicker when it loads, it then goes to the back. Some of this could be optimized, but I'll leave that to you - but here's a working demo. I've tested moving the taskbar and multiple desktops - all seems to work ok. You'll need to prevent the window from closing as well (if you want that behavior) but hooking into WM_CLOSE for ex. and ignoring it (still won't stop someone from using task manager, in that case you'd need something like a watcher process and both proceses watching each other to detect when the other is closed). I've compiled this from several SO posts and a bit of twiddling.

The window properties
Show in taskbar = false
WindowState = maximized
ControlBox = false
FormBorderStyle = none


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DesktopReplacement
{
public partial class Form1 : Form
{
private bool _enableOverride;
internal class NativeMethods
{
public const int WM_WINDOWPOSCHANGING = 0x46;
public const int WM_WINDOWPOSCHANGED = 0x47;
public const int GWL_HWNDPARENT = -8;
public const int SW_SHOW = 1;


[Flags()]
public enum SetWindowPosFlags
{
SWP_NOSIZE = 0x1,
SWP_NOMOVE = 0x2,
SWP_NOZORDER = 0x4,
SWP_NOREDRAW = 0x8,
SWP_NOACTIVATE = 0x10,
SWP_FRAMECHANGED = 0x20,
SWP_DRAWFRAME = SWP_FRAMECHANGED,
SWP_SHOWWINDOW = 0x40,
SWP_HIDEWINDOW = 0x80,
SWP_NOCOPYBITS = 0x100,
SWP_NOOWNERZORDER = 0x200,
SWP_NOREPOSITION = SWP_NOOWNERZORDER,
SWP_NOSENDCHANGING = 0x400,
SWP_DEFERERASE = 0x2000,
SWP_ASYNCWINDOWPOS = 0x4000,
}

public enum WindowZOrder
{
HWND_TOP = 0,
HWND_BOTTOM = 1,
HWND_TOPMOST = -1,
HWND_NOTOPMOST = -2,
}

[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hWnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public SetWindowPosFlags flags;

// Returns the WINDOWPOS structure pointed to by the lParam parameter
// of a WM_WINDOWPOSCHANGING or WM_WINDOWPOSCHANGED message.
public static WINDOWPOS FromMessage(Message msg)
{
// Marshal the lParam parameter to an WINDOWPOS structure,
// and return the new structure
return (WINDOWPOS)Marshal.PtrToStructure(msg.LParam, typeof(WINDOWPOS));
}

// Replaces the original WINDOWPOS structure pointed to by the lParam
// parameter of a WM_WINDOWPOSCHANGING or WM_WINDOWPSCHANGING message
// with this one, so that the native window will be able to see any
// changes that we have made to its values.
public void UpdateMessage(Message msg)
{
// Marshal this updated structure back to lParam so the native
// window can respond to our changes.
// The old structure that it points to should be deleted, too.
Marshal.StructureToPtr(this, msg.LParam, true);
}
}
}


public static class HWND
{
public static readonly IntPtr
NOTOPMOST = new IntPtr(-2),
BROADCAST = new IntPtr(0xffff),
TOPMOST = new IntPtr(-1),
TOP = new IntPtr(0),
BOTTOM = new IntPtr(1);
}


public static class SWP
{
public static readonly int
NOSIZE = 0x0001,
NOMOVE = 0x0002,
NOZORDER = 0x0004,
NOREDRAW = 0x0008,
NOACTIVATE = 0x0010,
DRAWFRAME = 0x0020,
FRAMECHANGED = 0x0020,
SHOWWINDOW = 0x0040,
HIDEWINDOW = 0x0080,
NOCOPYBITS = 0x0100,
NOOWNERZORDER = 0x0200,
NOREPOSITION = 0x0200,
NOSENDCHANGING = 0x0400,
DEFERERASE = 0x2000,
ASYNCWINDOWPOS = 0x4000;
}


[DllImport("user32.dll", SetLastError = true)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpWindowClass, string lpWindowName);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);

[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

[DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr hwnd, int command);



public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{

IntPtr hprog = FindWindowEx(
FindWindowEx(
FindWindow("Progman", "Program Manager"),
IntPtr.Zero, "SHELLDLL_DefView", ""
),
IntPtr.Zero, "SysListView32", "FolderView"
);

SetWindowLong(this.Handle, NativeMethods.GWL_HWNDPARENT, hprog);
}

protected override void WndProc(ref Message m)
{
if (_enableOverride)
{
if (m.Msg == NativeMethods.WM_WINDOWPOSCHANGING)
{

// Extract the WINDOWPOS structure corresponding to this message
NativeMethods.WINDOWPOS wndPos = NativeMethods.WINDOWPOS.FromMessage(m);


wndPos.flags = wndPos.flags | NativeMethods.SetWindowPosFlags.SWP_NOZORDER;
wndPos.UpdateMessage(m);

}
}

base.WndProc(ref m);
}


private void timer1_Tick(object sender, EventArgs e)
{
SetWindowPos(Handle, HWND.BOTTOM, 0, 0, 0, 0, SWP.SHOWWINDOW | SWP.NOMOVE | SWP.NOOWNERZORDER | SWP.NOSIZE | SWP.NOACTIVATE);

IntPtr task = FindWindow("Shell_TrayWnd", "");
ShowWindow(task, NativeMethods.SW_SHOW);

_enableOverride = true;
}

}
}

Since you are using WPF - give this a try. It needs some cleanup/formatting but you get the point :)


using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace WpfApplication1
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
public MainWindow()
{
this.SourceInitialized += MainWindow_SourceInitialized;
this.WindowStyle = WindowStyle.None;
this.Loaded += Window_Loaded;
this.WindowState = WindowState.Maximized;
InitializeComponent();


DispatcherTimer dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += dispatcherTimer_Tick;
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 500);
dispatcherTimer.Start();

}


private bool _enableOverride;
internal class NativeMethods
{
public const int WM_WINDOWPOSCHANGING = 0x46;
public const int WM_WINDOWPOSCHANGED = 0x47;
public const int GWL_HWNDPARENT = -8;
public const int SW_SHOW = 1;


[Flags]
public enum SetWindowPosFlags
{
SWP_NOSIZE = 0x1,
SWP_NOMOVE = 0x2,
SWP_NOZORDER = 0x4,
SWP_NOREDRAW = 0x8,
SWP_NOACTIVATE = 0x10,
SWP_FRAMECHANGED = 0x20,
SWP_DRAWFRAME = SWP_FRAMECHANGED,
SWP_SHOWWINDOW = 0x40,
SWP_HIDEWINDOW = 0x80,
SWP_NOCOPYBITS = 0x100,
SWP_NOOWNERZORDER = 0x200,
SWP_NOREPOSITION = SWP_NOOWNERZORDER,
SWP_NOSENDCHANGING = 0x400,
SWP_DEFERERASE = 0x2000,
SWP_ASYNCWINDOWPOS = 0x4000
}

public enum WindowZOrder
{
HWND_TOP = 0,
HWND_BOTTOM = 1,
HWND_TOPMOST = -1,
HWND_NOTOPMOST = -2
}

[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hWnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public SetWindowPosFlags flags;

// Returns the WINDOWPOS structure pointed to by the lParam parameter
// of a WM_WINDOWPOSCHANGING or WM_WINDOWPOSCHANGED message.
public static WINDOWPOS FromMessage(IntPtr lParam)
{
// Marshal the lParam parameter to an WINDOWPOS structure,
// and return the new structure
return (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
}

// Replaces the original WINDOWPOS structure pointed to by the lParam
// parameter of a WM_WINDOWPOSCHANGING or WM_WINDOWPSCHANGING message
// with this one, so that the native window will be able to see any
// changes that we have made to its values.
public void UpdateMessage(IntPtr lParam)
{
// Marshal this updated structure back to lParam so the native
// window can respond to our changes.
// The old structure that it points to should be deleted, too.
Marshal.StructureToPtr(this, lParam, true);
}
}
}


public static class HWND
{
public static readonly IntPtr
NOTOPMOST = new IntPtr(-2),
BROADCAST = new IntPtr(0xffff),
TOPMOST = new IntPtr(-1),
TOP = new IntPtr(0),
BOTTOM = new IntPtr(1);
}


public static class SWP
{
public static readonly int
NOSIZE = 0x0001,
NOMOVE = 0x0002,
NOZORDER = 0x0004,
NOREDRAW = 0x0008,
NOACTIVATE = 0x0010,
DRAWFRAME = 0x0020,
FRAMECHANGED = 0x0020,
SHOWWINDOW = 0x0040,
HIDEWINDOW = 0x0080,
NOCOPYBITS = 0x0100,
NOOWNERZORDER = 0x0200,
NOREPOSITION = 0x0200,
NOSENDCHANGING = 0x0400,
DEFERERASE = 0x2000,
ASYNCWINDOWPOS = 0x4000;
}


[DllImport("user32.dll", SetLastError = true)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpWindowClass, string lpWindowName);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);

[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

[DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr hwnd, int command);


private void dispatcherTimer_Tick(object sender, EventArgs e)
{
//ensure we don't overlap the taskbar.
SetWindowPos(new WindowInteropHelper(this).Handle, HWND.BOTTOM, 0, 0, 0, 0, SWP.SHOWWINDOW | SWP.NOMOVE | SWP.NOOWNERZORDER | SWP.NOSIZE | SWP.NOACTIVATE);

IntPtr task = FindWindow("Shell_TrayWnd", "");
ShowWindow(task, NativeMethods.SW_SHOW);

_enableOverride = true;
}


private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (_enableOverride)
{
if (msg == NativeMethods.WM_WINDOWPOSCHANGING)
{
Debug.WriteLine("WM_WINDOWPOSCHANGING");
// Extract the WINDOWPOS structure corresponding to this message
//lParam has the ptr to a WindowsPos structure if its our WM_WINDOWPOSCHANGING struct
NativeMethods.WINDOWPOS wndPos = NativeMethods.WINDOWPOS.FromMessage(lParam);

wndPos.flags = wndPos.flags | NativeMethods.SetWindowPosFlags.SWP_NOZORDER;
wndPos.UpdateMessage(lParam);
//handled = true;
}
}

return IntPtr.Zero;
}

private void MainWindow_SourceInitialized(object sender, EventArgs e)
{
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;

IntPtr hprog = FindWindowEx(
FindWindowEx(
FindWindow("Progman", "Program Manager"),
IntPtr.Zero, "SHELLDLL_DefView", ""
),
IntPtr.Zero, "SysListView32", "FolderView"
);

SetWindowLong(hWnd, NativeMethods.GWL_HWNDPARENT, hprog);
}
}
}



Related Topics



Leave a reply



Submit