Skip to content

Instantly share code, notes, and snippets.

@anaisbetts
Created August 28, 2017 05:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anaisbetts/1183f3bd42a10c3161d845242cd3fe4b to your computer and use it in GitHub Desktop.
Save anaisbetts/1183f3bd42a10c3161d845242cd3fe4b to your computer and use it in GitHub Desktop.
Observe changes to top-level windows
var watchThatWindow = new ObservableWindow();
watchThatWindow.WindowCreated
.Select(hwnd => NativeMethods.GetWindowTitle(hwnd))
.Subscribe(x => Console.WriteLine($"Say hello to '{x}'!"));
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ReactiveUI;
namespace Peach
{
public class ObservableWindow
{
static int ShellWindowMessage;
static ObservableWindow()
{
ShellWindowMessage = NativeMethods.RegisterWindowMessageA("SHELLHOOK");
}
class MessageWindow : Form
{
public readonly Subject<Tuple<ShellProcCode, IntPtr>> Message = new Subject<Tuple<ShellProcCode, IntPtr>>();
static readonly IntPtr HwndMessage = (IntPtr)(-3);
protected override CreateParams CreateParams {
get {
var parameters = base.CreateParams;
parameters.Parent = HwndMessage;
return parameters;
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg != ShellWindowMessage) {
base.WndProc(ref m);
return;
}
Message.OnNext(Tuple.Create((ShellProcCode)m.WParam, m.LParam));
}
}
readonly IObservable<Tuple<ShellProcCode, IntPtr>> shellHookMessage;
readonly IObservable<IntPtr> moveSizeEnd;
readonly IObservable<IntPtr> locationChanged;
public ObservableWindow()
{
var shm = Observable.Create<Tuple<ShellProcCode, IntPtr>>(subj => {
var msgWnd = new MessageWindow();
if (!NativeMethods.RegisterShellHookWindow(msgWnd.Handle)) {
subj.OnError(new Win32Exception());
msgWnd.Dispose();
return Disposable.Empty;
}
var disp = msgWnd.Message.Subscribe(subj);
return Disposable.Create(() => {
disp.Dispose();
NativeMethods.DeregisterShellHookWindow(msgWnd.Handle);
msgWnd.Dispose();
});
});
var mse = observeAccessibilityEvent(WinEvent.EVENT_SYSTEM_MOVESIZEEND);
var locChange = observeAccessibilityEvent(WinEvent.EVENT_OBJECT_LOCATIONCHANGE);
shellHookMessage = shm.Publish().RefCount();
moveSizeEnd = mse.Publish().RefCount();
locationChanged = locChange.Publish().RefCount();
}
IObservable<IntPtr> observeAccessibilityEvent(WinEvent ev)
{
return Observable.Create<IntPtr>(subj => {
WinEventDelegate d = (hWinEventHook, dwEvent, hWnd, idObject, idChild, dwEventThread, dwmsEventTime) => {
subj.OnNext(hWnd);
};
var gch = GCHandle.Alloc(d);
var handle = NativeMethods.SetWinEventHook(ev, ev, IntPtr.Zero, d, 0, 0, WinEventHookFlags.WINEVENT_OUTOFCONTEXT);
if (handle == IntPtr.Zero) {
subj.OnError(new Win32Exception());
return Disposable.Empty;
}
return Disposable.Create(() => {
NativeMethods.UnhookWinEvent(handle);
gch.Free();
});
}).SubscribeOn(RxApp.MainThreadScheduler);
}
public IObservable<IntPtr> WindowActivated {
get => shellHookMessage.Where(x => x.Item1 == ShellProcCode.HSHELL_WINDOWACTIVATED).Select(x => x.Item2).ObserveOn(RxApp.MainThreadScheduler);
}
public IObservable<IntPtr> RudeAppActivated {
get => shellHookMessage.Where(x => x.Item1 == ShellProcCode.HSHELL_RUDEAPPACTIVATED).Select(x => x.Item2).ObserveOn(RxApp.MainThreadScheduler);
}
public IObservable<IntPtr> WindowReplaced {
get => shellHookMessage.Where(x => x.Item1 == ShellProcCode.HSHELL_WINDOWREPLACED).Select(x => x.Item2).ObserveOn(RxApp.MainThreadScheduler);
}
public IObservable<IntPtr> WindowCreated {
get => shellHookMessage.Where(x => x.Item1 == ShellProcCode.HSHELL_WINDOWCREATED).Select(x => x.Item2).ObserveOn(RxApp.MainThreadScheduler);
}
public IObservable<IntPtr> WindowDestroyed {
get => shellHookMessage.Where(x => x.Item1 == ShellProcCode.HSHELL_WINDOWDESTROYED).Select(x => x.Item2).ObserveOn(RxApp.MainThreadScheduler);
}
public IObservable<IntPtr> WindowMonitorChanged {
get => shellHookMessage.Where(x => x.Item1 == ShellProcCode.HSHELL_MONITORCHANGED).Select(x => x.Item2).ObserveOn(RxApp.MainThreadScheduler);
}
public IObservable<IntPtr> WindowGeometryChanged {
get => Observable.Merge(moveSizeEnd, locationChanged).ObserveOn(RxApp.MainThreadScheduler);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment