diff --git a/Hotkey.cs b/Hotkey.cs new file mode 100644 index 0000000..96d3853 --- /dev/null +++ b/Hotkey.cs @@ -0,0 +1,302 @@ +using System; +using System.Windows.Forms; +using System.ComponentModel; +using System.Xml.Serialization; +using System.Runtime.InteropServices; + +namespace MovablePython +{ + public class Hotkey : IMessageFilter + { + #region Interop + + [DllImport("user32.dll", SetLastError = true)] + private static extern int RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, Keys vk); + + [DllImport("user32.dll", SetLastError=true)] + private static extern int UnregisterHotKey(IntPtr hWnd, int id); + + private const uint WM_HOTKEY = 0x312; + + private const uint MOD_ALT = 0x1; + private const uint MOD_CONTROL = 0x2; + private const uint MOD_SHIFT = 0x4; + private const uint MOD_WIN = 0x8; + + private const uint ERROR_HOTKEY_ALREADY_REGISTERED = 1409; + + #endregion + + private static int currentID; + private const int maximumID = 0xBFFF; + + private Keys keyCode; + private bool shift; + private bool control; + private bool alt; + private bool windows; + + [XmlIgnore] + private int id; + [XmlIgnore] + private bool registered; + [XmlIgnore] + private Control windowControl; + + public event HandledEventHandler Pressed; + + public Hotkey() : this(Keys.None, false, false, false, false) + { + // No work done here! + } + + public Hotkey(Keys keyCode, bool shift, bool control, bool alt, bool windows) + { + // Assign properties + this.KeyCode = keyCode; + this.Shift = shift; + this.Control = control; + this.Alt = alt; + this.Windows = windows; + + // Register us as a message filter + Application.AddMessageFilter(this); + } + + ~Hotkey() + { + // Unregister the hotkey if necessary + if (this.Registered) + { this.Unregister(); } + } + + public Hotkey Clone() + { + // Clone the whole object + return new Hotkey(this.keyCode, this.shift, this.control, this.alt, this.windows); + } + + public bool GetCanRegister(Control windowControl) + { + // Handle any exceptions: they mean "no, you can't register" :) + try + { + // Attempt to register + if (!this.Register(windowControl)) + { return false; } + + // Unregister and say we managed it + this.Unregister(); + return true; + } + catch (Win32Exception) + { return false; } + catch (NotSupportedException) + { return false; } + } + + public bool Register(Control windowControl) + { + // Check that we have not registered + if (this.registered) + { throw new NotSupportedException("You cannot register a hotkey that is already registered"); } + + // We can't register an empty hotkey + if (this.Empty) + { throw new NotSupportedException("You cannot register an empty hotkey"); } + + // Get an ID for the hotkey and increase current ID + this.id = Hotkey.currentID; + Hotkey.currentID = Hotkey.currentID + 1 % Hotkey.maximumID; + + // Translate modifier keys into unmanaged version + uint modifiers = (this.Alt ? Hotkey.MOD_ALT : 0) | (this.Control ? Hotkey.MOD_CONTROL : 0) | + (this.Shift ? Hotkey.MOD_SHIFT : 0) | (this.Windows ? Hotkey.MOD_WIN : 0); + + // Register the hotkey + if (Hotkey.RegisterHotKey(windowControl.Handle, this.id, modifiers, keyCode) == 0) + { + // Is the error that the hotkey is registered? + if (Marshal.GetLastWin32Error() == ERROR_HOTKEY_ALREADY_REGISTERED) + { return false; } + else + { throw new Win32Exception(); } + } + + // Save the control reference and register state + this.registered = true; + this.windowControl = windowControl; + + // We successfully registered + return true; + } + + public void Unregister() + { + // Check that we have registered + if (!this.registered) + { throw new NotSupportedException("You cannot unregister a hotkey that is not registered"); } + + // It's possible that the control itself has died: in that case, no need to unregister! + if (!this.windowControl.IsDisposed) + { + // Clean up after ourselves + if (Hotkey.UnregisterHotKey(this.windowControl.Handle, this.id) == 0) + { throw new Win32Exception(); } + } + + // Clear the control reference and register state + this.registered = false; + this.windowControl = null; + } + + private void Reregister() + { + // Only do something if the key is already registered + if (!this.registered) + { return; } + + // Save control reference + Control windowControl = this.windowControl; + + // Unregister and then reregister again + this.Unregister(); + this.Register(windowControl); + } + + public bool PreFilterMessage(ref Message message) + { + // Only process WM_HOTKEY messages + if (message.Msg != Hotkey.WM_HOTKEY) + { return false; } + + // Check that the ID is our key and we are registerd + if (this.registered && (message.WParam.ToInt32() == this.id)) + { + // Fire the event and pass on the event if our handlers didn't handle it + return this.OnPressed(); + } + else + { return false; } + } + + private bool OnPressed() + { + // Fire the event if we can + HandledEventArgs handledEventArgs = new HandledEventArgs(false); + if (this.Pressed != null) + { this.Pressed(this, handledEventArgs); } + + // Return whether we handled the event or not + return handledEventArgs.Handled; + } + + public override string ToString() + { + // We can be empty + if (this.Empty) + { return "(none)"; } + + // Build key name + string keyName = Enum.GetName(typeof(Keys), this.keyCode);; + switch (this.keyCode) + { + case Keys.D0: + case Keys.D1: + case Keys.D2: + case Keys.D3: + case Keys.D4: + case Keys.D5: + case Keys.D6: + case Keys.D7: + case Keys.D8: + case Keys.D9: + // Strip the first character + keyName = keyName.Substring(1); + break; + default: + // Leave everything alone + break; + } + + // Build modifiers + string modifiers = ""; + if (this.shift) + { modifiers += "Shift+"; } + if (this.control) + { modifiers += "Control+"; } + if (this.alt) + { modifiers += "Alt+"; } + if (this.windows) + { modifiers += "Windows+"; } + + // Return result + return modifiers + keyName; + } + + public bool Empty + { + get { return this.keyCode == Keys.None; } + } + + public bool Registered + { + get { return this.registered; } + } + + public Keys KeyCode + { + get { return this.keyCode; } + set + { + // Save and reregister + this.keyCode = value; + this.Reregister(); + } + } + + public bool Shift + { + get { return this.shift; } + set + { + // Save and reregister + this.shift = value; + this.Reregister(); + } + } + + public bool Control + { + get { return this.control; } + set + { + // Save and reregister + this.control = value; + this.Reregister(); + } + } + + public bool Alt + { + get { return this.alt; } + set + { + // Save and reregister + this.alt = value; + this.Reregister(); + } + } + + public bool Windows + { + get { return this.windows; } + set + { + // Save and reregister + this.windows = value; + this.Reregister(); + } + } + } +} diff --git a/Preview.cs b/Preview.cs index 28a7485..ce25712 100644 --- a/Preview.cs +++ b/Preview.cs @@ -6,6 +6,7 @@ using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Diagnostics; +using MovablePython; namespace PreviewToy { @@ -26,6 +27,7 @@ namespace PreviewToy private bool has_been_set_up = false; private bool thumbnail_has_been_set_up = false; private PreviewToyHandler spawner; + private Hotkey hotkey; private bool hide = false; @@ -41,6 +43,8 @@ namespace PreviewToy return this.Text; } + + public void MakeTopMost(bool topmost) { this.TopMost = topmost && !(this.hide); @@ -66,6 +70,8 @@ namespace PreviewToy this.old_position = this.Location; has_been_set_up = true; + + } public void preview_MouseHover(object sender, System.EventArgs e) @@ -84,6 +90,46 @@ namespace PreviewToy RefreshPreview(); } + protected override CreateParams CreateParams + { + get + { + var Params = base.CreateParams; + Params.ExStyle |= 0x80; + return Params; + } + } + + public void registerShortcut(string shortcut) + { + if (shortcut == "") + return; + var cvt = new KeysConverter(); + var key = (Keys)cvt.ConvertFrom(shortcut); + + Hotkey hotkey = new Hotkey(); + + if ((key & Keys.Shift) == Keys.Shift) + { + hotkey.Shift = true; + } + if ((key & Keys.Alt) == Keys.Alt) + { + hotkey.Alt = true; + } + if ((key & Keys.Control) == Keys.Control) + { + hotkey.Control = true; + } + + key = key & ~Keys.Shift & ~Keys.Alt & ~Keys.Control; + hotkey.KeyCode = key; + hotkey.Register(this); + hotkey.Pressed += delegate { bring_client_to_foreground(); spawner.preview_did_switch(); }; + + this.hotkey = hotkey; + } + public void doZoom() { if (is_zoomed) diff --git a/PreviewHandler.cs b/PreviewHandler.cs index ae3ceda..f2e1103 100644 --- a/PreviewHandler.cs +++ b/PreviewHandler.cs @@ -27,6 +27,7 @@ namespace PreviewToy private Dictionary> unique_layouts; private Dictionary flat_layout; + private Dictionary flat_layout_shortcuts; private Dictionary client_layout; private bool is_initialized; @@ -89,6 +90,7 @@ namespace PreviewToy unique_layouts = new Dictionary>(); flat_layout = new Dictionary(); + flat_layout_shortcuts = new Dictionary(); client_layout = new Dictionary(); ignoring_size_sync = new Stopwatch(); @@ -197,6 +199,8 @@ namespace PreviewToy else if (previews.ContainsKey(process.MainWindowHandle) && process.MainWindowTitle != previews[process.MainWindowHandle].Text) //or update the preview titles { previews[process.MainWindowHandle].SetLabel(process.MainWindowTitle); + string key = previews[process.MainWindowHandle].Text; + previews[process.MainWindowHandle].registerShortcut(flat_layout_shortcuts[key]); refresh_client_window_locations(process); } @@ -299,6 +303,12 @@ namespace PreviewToy foreach (var el in rootElement.Elements()) { flat_layout[ParseXElement(el)] = new Point(Convert.ToInt32(el.Element("x").Value), Convert.ToInt32(el.Element("y").Value)); + flat_layout_shortcuts[ParseXElement(el)] = ""; + + if (el.Element("shortcut") != null) + { + flat_layout_shortcuts[ParseXElement(el)] = el.Element("shortcut").Value; + } } } @@ -355,6 +365,7 @@ namespace PreviewToy XElement layout = MakeXElement(clientKV.Key); layout.Add(new XElement("x", clientKV.Value.X)); layout.Add(new XElement("y", clientKV.Value.Y)); + layout.Add(new XElement("shortcut", flat_layout_shortcuts[clientKV.Key])); el2.Add(layout); } diff --git a/PreviewOverlay.cs b/PreviewOverlay.cs index 03c152a..b2cd701 100644 --- a/PreviewOverlay.cs +++ b/PreviewOverlay.cs @@ -41,5 +41,15 @@ namespace PreviewToy } + protected override CreateParams CreateParams + { + get + { + var Params = base.CreateParams; + Params.ExStyle |= 0x80; + return Params; + } + } + } } diff --git a/preview toy.csproj b/preview toy.csproj index c6cbcc1..c03ed96 100644 --- a/preview toy.csproj +++ b/preview toy.csproj @@ -95,6 +95,7 @@ + Form