Skip to content

Commit 558a12d

Browse files
committed
Global hotkey support and always on top support
1 parent 8ae6bcf commit 558a12d

13 files changed

+732
-336
lines changed

Broker.cs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
using System.Threading.Tasks;
1010
using System.Windows;
1111
using System.Windows.Controls;
12+
using System.Windows.Input;
1213
using System.Xml;
1314

1415
using FreeSWITCH.Native;
1516
using FSClient.Controls;
17+
18+
using UnManaged;
1619
using Timer = System.Timers.Timer;
1720

1821
namespace FSClient {
@@ -136,6 +139,15 @@ private void init_us() {
136139
UseNumberOnlyInput = Properties.Settings.Default.UseNumberOnlyInput;
137140
CheckForUpdates = Properties.Settings.Default.CheckForUpdates;
138141
GUIStartup = Properties.Settings.Default.GuiStartup;
142+
AlwaysOnTopDuringCall = Properties.Settings.Default.AlwaysOnTopDuringCall;
143+
var hotkey_str = Properties.Settings.Default.GlobalHotKey;
144+
if (!String.IsNullOrWhiteSpace(hotkey_str)) {
145+
try {
146+
var new_hotkey = Newtonsoft.Json.JsonConvert.DeserializeObject<HotKeySetting>(hotkey_str);
147+
if (new_hotkey != null)
148+
SetHotKey(new_hotkey);
149+
} catch (Exception) { }
150+
}
139151

140152
if (Properties.Settings.Default.Sofia != null)
141153
sofia = Properties.Settings.Default.Sofia.GetSofia();
@@ -386,6 +398,8 @@ public void SaveSettings() {
386398
Properties.Settings.Default.ClearDTMFS = ClearDTMFS;
387399
Properties.Settings.Default.UPNPNAT = UPNPNAT;
388400
Properties.Settings.Default.DirectSipDial = DirectSipDial;
401+
Properties.Settings.Default.AlwaysOnTopDuringCall = AlwaysOnTopDuringCall;
402+
Properties.Settings.Default.GlobalHotKey = SerializeObject(global_hotkey, false);
389403
Properties.Settings.Default.UseNumberOnlyInput = UseNumberOnlyInput;
390404
Properties.Settings.Default.RecordingPath = recordings_folder;
391405
Properties.Settings.Default.Theme = theme;
@@ -399,6 +413,13 @@ public void SaveSettings() {
399413
MessageBox.Show("Error saving settings out: " + e.Message + "\n" + e.StackTrace);
400414
}
401415
}
416+
public static string SerializeObject(Object obj, bool indent = true) {
417+
var settings = new Newtonsoft.Json.JsonSerializerSettings() {
418+
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore,
419+
Converters = new[] { new Newtonsoft.Json.Converters.StringEnumConverter() }
420+
};
421+
return Newtonsoft.Json.JsonConvert.SerializeObject(obj, indent ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None, settings);
422+
}
402423
private void UpdateStatus() {
403424
int cur_active_calls = Call.live_call_count();
404425
bool is_call_active = (Call.active_call != null);
@@ -461,9 +482,17 @@ private void HandleCallWaiting(Timer timer, Call c) {
461482

462483
}
463484
public void BringToFront(bool no_keyboard_focus) {
464-
if (IncomingTopMost) {
485+
var opts = BRING_TO_FRONT_OPTS.OnlyIfTopMostSetting;
486+
if (!no_keyboard_focus)
487+
opts |= BRING_TO_FRONT_OPTS.KeyboardFocusIfSettingEnabled;
488+
BringToFront(opts);
489+
}
490+
[Flags]
491+
public enum BRING_TO_FRONT_OPTS { OnlyIfTopMostSetting, AlwaysKeyboardFocus, KeyboardFocusIfSettingEnabled }
492+
public void BringToFront(BRING_TO_FRONT_OPTS opts) {
493+
if (! opts.HasFlag(BRING_TO_FRONT_OPTS.OnlyIfTopMostSetting) || IncomingTopMost)
465494
MainWindow.get_instance().BringToFront();
466-
} else if (IncomingKeyboardFocus && !no_keyboard_focus)
495+
if (opts.HasFlag(BRING_TO_FRONT_OPTS.AlwaysKeyboardFocus) || (opts.HasFlag(BRING_TO_FRONT_OPTS.KeyboardFocusIfSettingEnabled) && IncomingKeyboardFocus) )
467496
Utils.SetForegroundWindow(MainWindow.get_instance());
468497

469498
}
@@ -532,6 +561,38 @@ private void VersionCheck() {
532561
} catch (Exception) { }
533562

534563
}
564+
565+
internal class HotKeySetting {
566+
public Key key { get; set; }
567+
public KeyModifier modifiers { get; set; }
568+
}
569+
public HotKeySetting global_hotkey { get; private set; } = new HotKeySetting { key = Key.None };
570+
public void SetHotKey(HotKeySetting setting) {
571+
try {
572+
if (setting.key == global_hotkey.key && setting.modifiers == global_hotkey.modifiers)
573+
return;
574+
global_hotkey = setting;
575+
if (setting.key == Key.None) {
576+
hot_key?.Unregister();
577+
return;
578+
}
579+
580+
if (hot_key == null)
581+
hot_key = new GlobalHotKey(setting.key, setting.modifiers, GlobalHotKeyPressed);
582+
else
583+
hot_key.UpdateHotKey(setting.key, setting.modifiers);
584+
}catch(Exception ex) {
585+
MessageBox.Show("Unable to set global hotkey due to: " + ex.Message);
586+
}
587+
}
588+
589+
private void GlobalHotKeyPressed(GlobalHotKey obj) {
590+
BringToFront(BRING_TO_FRONT_OPTS.AlwaysKeyboardFocus);
591+
}
592+
593+
private GlobalHotKey hot_key;
594+
595+
535596
public bool UseNumberOnlyInput {
536597
get { return _UseNumberOnlyInput; }
537598
set {
@@ -575,6 +636,18 @@ public bool DirectSipDial {
575636
private bool _DirectSipDial;
576637

577638

639+
public bool AlwaysOnTopDuringCall {
640+
get => _AlwaysOnTopDuringCall;
641+
642+
set {
643+
if (value == _AlwaysOnTopDuringCall)
644+
return;
645+
_AlwaysOnTopDuringCall = value;
646+
}
647+
}
648+
private bool _AlwaysOnTopDuringCall;
649+
650+
578651
public bool UPNPNAT {
579652
get { return _UPNPNAT; }
580653
set {

FSClient.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
<Reference Include="FreeSWITCH.Managed">
7171
<HintPath>External Items\$(Configuration)\FreeSWITCH.Managed.dll</HintPath>
7272
</Reference>
73+
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
74+
<HintPath>packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
75+
</Reference>
7376
<Reference Include="PresentationFramework.Aero" />
7477
<Reference Include="System" />
7578
<Reference Include="System.configuration" />
@@ -122,6 +125,7 @@
122125
<Compile Include="GenericEditor.xaml.cs">
123126
<DependentUpon>GenericEditor.xaml</DependentUpon>
124127
</Compile>
128+
<Compile Include="HotKey.cs" />
125129
<Compile Include="IContactPlugin.cs" />
126130
<Compile Include="IncomingCallNotification.xaml.cs">
127131
<DependentUpon>IncomingCallNotification.xaml</DependentUpon>

HotKey.cs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Net.Mime;
6+
using System.Runtime.InteropServices;
7+
using System.Text;
8+
using System.Windows;
9+
using System.Windows.Input;
10+
using System.Windows.Interop;
11+
12+
namespace UnManaged {
13+
public class GlobalHotKey : IDisposable {
14+
private static Dictionary<int, GlobalHotKey> _dictHotKeyToCalBackProc;
15+
16+
[DllImport("user32.dll")]
17+
private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);
18+
19+
[DllImport("user32.dll")]
20+
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
21+
22+
public const int WmHotKey = 0x0312;
23+
24+
private bool _disposed = false;
25+
26+
public Key Key { get; private set; }
27+
public KeyModifier KeyModifiers { get; private set; }
28+
public Action<GlobalHotKey> Action { get; set; }
29+
public int Id { get; set; }
30+
31+
// ******************************************************************
32+
public GlobalHotKey(Key k, KeyModifier keyModifiers, Action<GlobalHotKey> action, bool register = true) {
33+
Key = k;
34+
KeyModifiers = keyModifiers;
35+
Action = action;
36+
if (register)
37+
Register();
38+
39+
}
40+
private static object lock_obj = new object();
41+
public void UpdateHotKey(Key k, KeyModifier keyModifiers, bool register = true) {
42+
Key = k;
43+
KeyModifiers = keyModifiers;
44+
if (register)
45+
Register();
46+
}
47+
48+
// ******************************************************************
49+
public bool Register() {
50+
if (Id != 0)
51+
Unregister();
52+
int virtualKeyCode = KeyInterop.VirtualKeyFromKey(Key);
53+
Id = virtualKeyCode + ((int)KeyModifiers * 0x10000);
54+
bool result = RegisterHotKey(IntPtr.Zero, Id, (UInt32)KeyModifiers, (UInt32)virtualKeyCode);
55+
lock (lock_obj) {
56+
if (_dictHotKeyToCalBackProc == null) {
57+
_dictHotKeyToCalBackProc = new Dictionary<int, GlobalHotKey>();
58+
ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
59+
}
60+
}
61+
62+
_dictHotKeyToCalBackProc.Add(Id, this);
63+
if (!result)
64+
throw new Exception("Unable to register hot key");
65+
//Debug.Print(result.ToString() + ", " + Id + ", " + virtualKeyCode);
66+
return result;
67+
}
68+
69+
// ******************************************************************
70+
public void Unregister() {
71+
if (Id == 0)
72+
return;
73+
UnregisterHotKey(IntPtr.Zero, Id);
74+
_dictHotKeyToCalBackProc.Remove(Id);
75+
Id = 0;
76+
}
77+
78+
// ******************************************************************
79+
private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled) {
80+
if (!handled) {
81+
if (msg.message == WmHotKey) {
82+
GlobalHotKey hotKey;
83+
84+
if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey)) {
85+
if (hotKey.Action != null) {
86+
Application.Current.Dispatcher.BeginInvoke((Action)(()=>
87+
hotKey.Action.Invoke(hotKey)
88+
));
89+
}
90+
handled = true;
91+
}
92+
}
93+
}
94+
}
95+
96+
// ******************************************************************
97+
// Implement IDisposable.
98+
// Do not make this method virtual.
99+
// A derived class should not be able to override this method.
100+
public void Dispose() {
101+
Dispose(true);
102+
// This object will be cleaned up by the Dispose method.
103+
// Therefore, you should call GC.SupressFinalize to
104+
// take this object off the finalization queue
105+
// and prevent finalization code for this object
106+
// from executing a second time.
107+
GC.SuppressFinalize(this);
108+
}
109+
110+
// ******************************************************************
111+
// Dispose(bool disposing) executes in two distinct scenarios.
112+
// If disposing equals true, the method has been called directly
113+
// or indirectly by a user's code. Managed and unmanaged resources
114+
// can be _disposed.
115+
// If disposing equals false, the method has been called by the
116+
// runtime from inside the finalizer and you should not reference
117+
// other objects. Only unmanaged resources can be _disposed.
118+
protected virtual void Dispose(bool disposing) {
119+
// Check to see if Dispose has already been called.
120+
if (!this._disposed) {
121+
// If disposing equals true, dispose all managed
122+
// and unmanaged resources.
123+
if (disposing) {
124+
// Dispose managed resources.
125+
Unregister();
126+
}
127+
128+
// Note disposing has been done.
129+
_disposed = true;
130+
}
131+
}
132+
}
133+
134+
// ******************************************************************
135+
[Flags]
136+
public enum KeyModifier {
137+
None = 0x0000,
138+
Alt = 0x0001,
139+
Ctrl = 0x0002,
140+
NoRepeat = 0x4000,
141+
Shift = 0x0004,
142+
Win = 0x0008
143+
}
144+
145+
// ******************************************************************
146+
}

0 commit comments

Comments
 (0)