├── Account.cs
├── App.xaml
├── App.xaml.cs
├── Broker.cs
├── Call.cs
├── Conference.cs
├── Controls
├── OurAutoCompleteBox.cs
├── PhonePadButton.xaml
└── PhonePadButton.xaml.cs
├── Converters.cs
├── DelayedFunction.cs
├── DpiUtil.cs
├── EventSocket.cs
├── External Items
├── JabraSDK.dll
├── Plantronics.Device.Common.dll
├── conf
│ └── freeswitch.xml
├── fs_cli.exe
├── libjabra.dll
├── link.pl
└── portaudio_notes.txt
├── FSClient.csproj
├── FSClient.sln
├── FSEvent.cs
├── Field.cs
├── GenericEditor.xaml
├── GenericEditor.xaml.cs
├── HotKey.cs
├── IContactPlugin.cs
├── IHeadsetPlugin.cs
├── IPlugin.cs
├── IncomingCallNotification.xaml
├── IncomingCallNotification.xaml.cs
├── InputBox.xaml
├── InputBox.xaml.cs
├── JabraHeadset
├── JabraHeadsetPlugin.csproj
├── JabraProvider.cs
├── Properties
│ └── AssemblyInfo.cs
└── app.config
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── ObservableClass.cs
├── Options.xaml
├── Options.xaml.cs
├── PlantronicsHeadset
├── PlantronicsHeadsetPlugin.csproj
├── PlantronicsProvider.cs
├── Properties
│ └── AssemblyInfo.cs
└── app.config
├── PluginManagerBase.cs
├── PluginOptionsWindow.xaml
├── PluginOptionsWindow.xaml.cs
├── PortAudio.cs
├── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
├── Resources.resx
├── Settings.Designer.cs
└── Settings.settings
├── README.md
├── Setup
├── Banner.bmp
├── Dialog.bmp
├── FSClient.wxs
├── FSCore.wxs
├── Product.wxs
└── Setup.wixproj
├── SimpleContactPluginBase.cs
├── SimpleXmlContactPlugin
├── Properties
│ └── AssemblyInfo.cs
├── SimpleXmlContactPlugin.cs
├── SimpleXmlContactPlugin.csproj
└── app.config
├── Sofia.cs
├── Themes
├── Base.xaml
├── Black.xaml
├── RoyalBlue.xaml
├── Steel.xaml
└── White.xaml
├── Utils.cs
├── Windows.cs
├── XmlUtils.cs
├── app.config
├── phone.ico
├── phone_dnd.ico
├── screenshots
├── FSClient_themes.png
└── FSclient_screen.png
└── warning.png
/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace FSClient {
4 | ///
5 | /// Interaction logic for App.xaml
6 | ///
7 | public partial class App : Application {
8 | public App() {
9 | DispatcherUnhandledException += App_DispatcherUnhandledException;
10 | System.Diagnostics.Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.High;
11 |
12 | }
13 |
14 | void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) {
15 | MessageBox.Show("Dispatcher exception of: " + e.Exception.Message, "Dispatcher Exception", MessageBoxButton.OK, MessageBoxImage.Error);
16 | e.Handled = true;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Controls/OurAutoCompleteBox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 |
8 | namespace FSClient.Controls {
9 | public class OurAutoCompleteBox : AutoCompleteBox {
10 | public enum OurAutoCompleteFilterMode {
11 | None,
12 | StartsWith,
13 | StartsWithCaseSensitive,
14 | StartsWithOrdinal,
15 | StartsWithOrdinalCaseSensitive,
16 | Contains,
17 | ContainsCaseSensitive,
18 | ContainsOrdinal,
19 | ContainsOrdinalCaseSensitive,
20 | Equals,
21 | EqualsCaseSensitive,
22 | EqualsOrdinal,
23 | EqualsOrdinalCaseSensitive,
24 | Custom,
25 | ContainsSplit
26 | }
27 | private string last_search_term;
28 | private string[] last_words;
29 | private bool MultiTextFilter(string search, string item) {
30 | if(search != last_search_term) {
31 | last_search_term = search;
32 | last_words = search.Split(' ');
33 | }
34 | return last_words.All(word => item.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) != -1);
35 | }
36 | public new OurAutoCompleteFilterMode FilterMode {
37 | get { return (OurAutoCompleteFilterMode)GetValue(FilterModeProperty); }
38 | set {
39 | SetValue(FilterModeProperty, value);
40 | }
41 | }
42 | public static new readonly DependencyProperty FilterModeProperty = DependencyProperty.Register("FilterMode", typeof(OurAutoCompleteFilterMode), typeof(OurAutoCompleteBox), new PropertyMetadata((object)OurAutoCompleteFilterMode.StartsWith, new PropertyChangedCallback(OurAutoCompleteBox.OnFilterModePropertyChanged)));
43 |
44 | private static void OnFilterModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
45 | OurAutoCompleteBox autoCompleteBox = d as OurAutoCompleteBox;
46 | AutoCompleteBox base_box = autoCompleteBox;
47 | var mode = (OurAutoCompleteFilterMode)e.NewValue;
48 | switch(mode) {
49 | case OurAutoCompleteFilterMode.ContainsSplit:
50 | base_box.FilterMode = AutoCompleteFilterMode.Custom;
51 | autoCompleteBox.TextFilter = autoCompleteBox.MultiTextFilter;
52 | break;
53 | default:
54 | base_box.FilterMode = (AutoCompleteFilterMode)mode;
55 | break;
56 | }
57 | }
58 | private TextBox the_textbox;
59 | private void find_textbox() {
60 | if(the_textbox != null)
61 | return;
62 | the_textbox = Template.FindName("Text", this) as TextBox;
63 | if(the_textbox != null)
64 | the_textbox.TabIndex = TabIndex;
65 | }
66 | public TextBox GetActualTextbox() {
67 | find_textbox();
68 | return the_textbox;
69 | }
70 | public bool TextBoxHasFocus() {
71 | find_textbox();
72 | if(the_textbox == null)
73 | return false;
74 | return the_textbox.IsFocused;
75 | }
76 | public void TextBoxFocus() {
77 | find_textbox();
78 | if(the_textbox != null)
79 | the_textbox.Focus();
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Controls/PhonePadButton.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
26 |
27 |
--------------------------------------------------------------------------------
/Controls/PhonePadButton.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Automation;
7 | using System.Windows.Automation.Peers;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 | using System.ComponentModel;
17 |
18 | namespace FSClient.Controls
19 | {
20 | ///
21 | /// Interaction logic for PhonePadButton.xaml
22 | ///
23 | public partial class PhonePadButton : UserControl
24 | {
25 | public PhonePadButton()
26 | {
27 | this.InitializeComponent();
28 | btn.Click += new RoutedEventHandler(btn_Click);
29 | }
30 |
31 | void btn_Click(object sender, RoutedEventArgs e)
32 | {
33 | if (Click != null)
34 | Click(this, e);
35 | }
36 | [Category("Behavior")]
37 | public event RoutedEventHandler Click;
38 |
39 | public string Number
40 | {
41 | get { return lblNumber.Text; }
42 | set { lblNumber.Text = value; AutomationProperties.SetName(btn, value); AutomationProperties.SetName(this, value); AutomationProperties.SetItemType(this,"Button"); }
43 | }
44 | public string Letters
45 | {
46 | get { return lblLetters.Text; }
47 | set { lblLetters.Text = value; }
48 | }
49 | protected AutomationControlType GetAutomationControlTypeCore() {
50 | return AutomationControlType.Button;
51 | }
52 |
53 | }
54 | }
--------------------------------------------------------------------------------
/Converters.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Windows;
7 | using System.Windows.Data;
8 | using System.Windows.Media;
9 |
10 | namespace FSClient {
11 | public class ConfStateConverter : IValueConverter{
12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture){
13 | if (value == null)
14 | return "";
15 | ConferenceUser.USER_STATE state = (ConferenceUser.USER_STATE)value;
16 | return StateConvert(state);
17 | }
18 | public static String StateConvert(ConferenceUser.USER_STATE state){
19 | String ret = "";
20 |
21 | if (ConferenceUser.StateTest(state,ConferenceUser.USER_STATE.DEAF))
22 | ret += "D ";
23 | if (ConferenceUser.StateTest(state, ConferenceUser.USER_STATE.FLOOR))
24 | ret += "F ";
25 | if (ConferenceUser.StateTest(state, ConferenceUser.USER_STATE.MUTE))
26 | ret += "M ";
27 | if (ConferenceUser.StateTest(state, ConferenceUser.USER_STATE.TALK))
28 | ret += "T ";
29 | return ret;
30 | }
31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
32 | throw new NotImplementedException();
33 | }
34 | }
35 | public class StateConverter : IValueConverter {
36 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
37 |
38 | if (value == null)
39 | return Colors.White.ToString();
40 | String ret = Colors.White.ToString();
41 | Call.CALL_STATE state = (Call.CALL_STATE)value;
42 | switch (state) {
43 | case Call.CALL_STATE.Answered:
44 | ret = "#FF4EFF00";
45 | break;
46 | case Call.CALL_STATE.Busy:
47 | ret = "#FFFFAF00";
48 | break;
49 | case Call.CALL_STATE.Ended:
50 | ret = "#FFAFAFAF";
51 | break;
52 | case Call.CALL_STATE.Failed:
53 | ret = "#FF000000";
54 | break;
55 | case Call.CALL_STATE.Hold:
56 | ret = "#FFF1FF00";
57 | break;
58 | case Call.CALL_STATE.Missed:
59 | ret = "#FFFF0000";
60 | break;
61 | case Call.CALL_STATE.Hold_Ringing:
62 | case Call.CALL_STATE.Ringing:
63 | ret = "#FF00B3FF";
64 | break;
65 | }
66 | return ret;
67 | }
68 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
69 | throw new NotImplementedException();
70 | }
71 | }
72 | public class AccountDefaultConverter : IValueConverter {
73 | public static SolidColorBrush default_account_color;
74 | public static SolidColorBrush normal_account_color;
75 |
76 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
77 | if (value == null)
78 | return normal_account_color;
79 | bool is_default = (bool)value;
80 | return is_default ? default_account_color : normal_account_color;
81 | }
82 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
83 | throw new NotImplementedException();
84 | }
85 | }
86 |
87 | public class EnglishDirectionConverter : IValueConverter {
88 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
89 | if (value == null)
90 | return "";
91 | return EnglishDirectionConverter.Convert((bool)value);
92 |
93 | }
94 | public static String Convert(bool is_outgoing) {
95 | return is_outgoing ? "Outgoing" : "Incoming";
96 | }
97 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
98 | throw new NotImplementedException();
99 | }
100 | }
101 | public class DirectionConverter : IValueConverter {
102 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
103 | if (value == null)
104 | return "";
105 | bool is_outgoing = (bool)value;
106 | return is_outgoing ? "<-" : "->";
107 | }
108 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
109 | throw new NotImplementedException();
110 | }
111 | }
112 | public class DurationTimeConverter : IValueConverter {
113 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
114 | if (value == null)
115 | return "";
116 | return Convert((TimeSpan)value);
117 | }
118 | public static string Convert(TimeSpan duration) {
119 | return duration.Minutes + ":" + duration.Seconds.ToString("00");
120 | }
121 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
122 | throw new NotImplementedException();
123 | }
124 | }
125 | public class ShortDateTimeConverter : IValueConverter {
126 | public static string Convert(DateTime time) {
127 | if (time == DateTime.MinValue)
128 | return "";
129 | return time.ToString("ddd, dd MMM yyyy HH:mm:ss tt");
130 | }
131 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
132 | if (value == null)
133 | return "";
134 | return Convert((DateTime)value);
135 |
136 | }
137 |
138 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
139 | throw new NotImplementedException();
140 | }
141 | }
142 | public class BoolToVisibilityConverter : IValueConverter {
143 | public static Visibility Convert(bool value) {
144 | return value ? Visibility.Visible : Visibility.Hidden;
145 | }
146 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
147 | if (value == null)
148 | return "";
149 | return Convert((bool)value);
150 |
151 | }
152 |
153 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
154 | throw new NotImplementedException();
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/DelayedFunction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Timers;
4 |
5 | namespace FSClient {
6 | public class DelayedFunction {
7 | private class DelayedItem {
8 | public String key;
9 | public Action action;
10 | public DateTime end_time;
11 | }
12 | private static List functions = new List();
13 | private static Timer timer;
14 | private static bool timer_enabled;
15 | public static void DelayedCall(String key, Action action, int ms) {
16 | if (timer == null) {
17 | timer = new Timer();
18 | timer.Interval = 200;
19 | timer.Elapsed += timer_Elapsed;
20 | }
21 | lock (functions) {
22 | if (!String.IsNullOrWhiteSpace(key)){
23 | for (int i = 0; i < functions.Count; i++){
24 | if (functions[i].key == key){
25 | functions.RemoveAt(i);
26 | break;
27 | }
28 | }
29 | }
30 | functions.Add(new DelayedItem { key = key, action = action, end_time = DateTime.Now.AddMilliseconds(ms) });
31 | if (!timer_enabled)
32 | timer_enabled = timer.Enabled = true;
33 | }
34 | }
35 |
36 | static void timer_Elapsed(object sender, ElapsedEventArgs e) {
37 | timer.Enabled = false;
38 | DateTime current = DateTime.Now;
39 | List to_perform = new List();
40 |
41 | lock (functions) {
42 | for (int i = 0; i < functions.Count; i++) {
43 | if (functions[i].end_time > current)
44 | continue;
45 | to_perform.Add(functions[i].action);
46 | functions.RemoveAt(i);
47 | i--;
48 | }
49 | }
50 | foreach (Action action in to_perform)
51 | action();
52 |
53 | lock (functions) {
54 | timer_enabled = timer.Enabled = (functions.Count != 0);
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/DpiUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | /*
4 | This class taken directly from keepass slimmed downed and slightly modified:
5 | https://github.com/dlech/KeePass2.x/blob/9b57541e6fcb49cb5f12029fb8e553295cf153c4/KeePass/UI/DpiUtil.cs
6 | KeePass Password Safe - The Open-Source Password Manager
7 | Copyright (C) 2003-2019 Dominik Reichl
8 | */
9 |
10 | namespace FSClient {
11 |
12 | public static class DpiUtil {
13 | internal const int LOGPIXELSX = 88;
14 | internal const int LOGPIXELSY = 90;
15 | [DllImport("User32.dll")]
16 | internal static extern IntPtr GetDC(IntPtr hWnd);
17 | [DllImport("Gdi32.dll")]
18 | internal static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
19 | [DllImport("User32.dll")]
20 | internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
21 |
22 | public static int ScaleIntX(double i) {
23 | EnsureInitialized();
24 | return (int)Math.Round((double)i * m_dScaleX);
25 | }
26 |
27 | public static int ScaleIntY(double i) {
28 | EnsureInitialized();
29 | return (int)Math.Round((double)i * m_dScaleY);
30 | }
31 | public static int DeScaleIntX(double i) {
32 | EnsureInitialized();
33 | return (int)Math.Round((double)i / m_dScaleX);
34 | }
35 |
36 | public static int DeScaleIntY(double i) {
37 | EnsureInitialized();
38 | return (int)Math.Round((double)i / m_dScaleY);
39 | }
40 | private static void EnsureInitialized() {
41 | if (m_bInitialized) return;
42 |
43 | try {
44 | IntPtr hDC = GetDC(IntPtr.Zero);
45 | if (hDC != IntPtr.Zero) {
46 | m_nDpiX = GetDeviceCaps(hDC,
47 | LOGPIXELSX);
48 | m_nDpiY = GetDeviceCaps(hDC,
49 | LOGPIXELSY);
50 | if ((m_nDpiX <= 0) || (m_nDpiY <= 0)) {
51 | System.Diagnostics.Debug.Assert(false);
52 | m_nDpiX = StdDpi;
53 | m_nDpiY = StdDpi;
54 | }
55 |
56 | if (ReleaseDC(IntPtr.Zero, hDC) != 1) {
57 | System.Diagnostics.Debug.Assert(false);
58 | }
59 | } else { System.Diagnostics.Debug.Assert(false); }
60 | } catch (Exception) { System.Diagnostics.Debug.Assert(false); }
61 |
62 | m_dScaleX = (double)m_nDpiX / (double)StdDpi;
63 | m_dScaleY = (double)m_nDpiY / (double)StdDpi;
64 |
65 | m_bInitialized = true;
66 | }
67 | private const int StdDpi = 96;
68 |
69 | private static bool m_bInitialized = false;
70 |
71 | private static int m_nDpiX = StdDpi;
72 | private static int m_nDpiY = StdDpi;
73 |
74 | private static double m_dScaleX = 1.0;
75 | public static double FactorX {
76 | get {
77 | EnsureInitialized();
78 | return m_dScaleX;
79 | }
80 | }
81 |
82 | private static double m_dScaleY = 1.0;
83 | public static double FactorY {
84 | get {
85 | EnsureInitialized();
86 | return m_dScaleY;
87 | }
88 | }
89 |
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/EventSocket.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Xml;
5 | using System.Xml.Serialization;
6 |
7 | namespace FSClient {
8 |
9 | [XmlRoot("settingsEventSocket")]
10 | public class SettingsEventSocket {
11 | public SettingsField[] fields { get; set; }
12 | public EventSocket GetEventSocket() {
13 | EventSocket socket = new EventSocket();
14 | foreach (SettingsField field in fields) {
15 | FieldValue val = FieldValue.GetByName(socket.values, field.name);
16 | if (val != null)
17 | val.value = field.value;
18 | }
19 | return socket;
20 | }
21 | public SettingsEventSocket() {
22 | }
23 | public SettingsEventSocket(EventSocket socket) {
24 | fields = (from fv in socket.values select new SettingsField(fv)).ToArray();
25 | }
26 | }
27 | public class EventSocket{
28 | public static Field[] fields = {
29 | new Field(Field.FIELD_TYPE.String, "Listen IP","listen-ip","listen-ip","127.0.0.1",""),
30 | new Field(Field.FIELD_TYPE.Int,"Listen Port","listen-port","listen-port","8022",""),
31 | new Field(Field.FIELD_TYPE.String,"Password","password","password","ClueCon",""),
32 | new Field(Field.FIELD_TYPE.String,"Inbound ACL","apply-inbound-acl","apply-inbound-acl","",""),
33 | new Field(Field.FIELD_TYPE.Bool,"Nat Map","nat-map","nat-map","false",""),
34 | new Field(Field.FIELD_TYPE.Combo,"Debug Mode","debug","debug","0","",new Field.FieldOption{display_value="true", value="1"},new Field.FieldOption{display_value="false", value="0"}),
35 | };
36 | private static string[] AllowedEmptyFields = new[] { "password", "listen-ip" };
37 | public FieldValue[] values = FieldValue.FieldValues(fields);
38 | public void gen_config(XmlNode config_node){
39 | XmlNode settings = XmlUtils.AddNodeNode(config_node, "settings");
40 | foreach (FieldValue value in values) {
41 | if (String.IsNullOrEmpty(value.field.xml_name))
42 | continue;
43 | if (String.IsNullOrWhiteSpace(value.value) && !AllowedEmptyFields.Contains(value.field.name))
44 | continue;
45 | Utils.add_xml_param(settings, value.field.xml_name, value.value);
46 | }
47 | }
48 | public void reload_config(){
49 | Utils.bgapi_exec("reload", "mod_event_socket");
50 |
51 | }
52 | public void edit() {
53 | GenericEditor editor = new GenericEditor();
54 | editor.Init("Editing Event Socket Settings", values);
55 | editor.ShowDialog();
56 | if (editor.DialogResult == true){
57 | MessageBoxResult mres = MessageBox.Show("Do you want to reload the event socket settings now, doing so will drop anyone connected to the event socket?", "Reload Module Warning", MessageBoxButton.YesNo);
58 | if (mres != MessageBoxResult.Yes)
59 | return;
60 | reload_config();
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/External Items/JabraSDK.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitchcapper/FSClient/e1cc332a216a2331e49d46c67a5b7fe526aba5cd/External Items/JabraSDK.dll
--------------------------------------------------------------------------------
/External Items/Plantronics.Device.Common.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitchcapper/FSClient/e1cc332a216a2331e49d46c67a5b7fe526aba5cd/External Items/Plantronics.Device.Common.dll
--------------------------------------------------------------------------------
/External Items/conf/freeswitch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/External Items/fs_cli.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitchcapper/FSClient/e1cc332a216a2331e49d46c67a5b7fe526aba5cd/External Items/fs_cli.exe
--------------------------------------------------------------------------------
/External Items/libjabra.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitchcapper/FSClient/e1cc332a216a2331e49d46c67a5b7fe526aba5cd/External Items/libjabra.dll
--------------------------------------------------------------------------------
/External Items/link.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | my $FS_BUILD_PATH = "c:\\software\\freeswitch\\Win32\\Debug";
4 | my $WORKING_DIR = "..\\bin\\Debug";
5 |
6 | my $USE_LN =0; #THIS SHOULD BE SET TO 0 (unless you have an ln equiv for windows)
7 | my $COPY_CONF = 1;
8 | my $VERBOSE = 0;
9 | my $try_pdb = 1;
10 | my $VERBOSE_SYS_RES = 0;
11 | die "Build directory does not exist: $FS_BUILD_PATH" if (! -e $FS_BUILD_PATH);
12 | die "Working directory does not exist: $WORKING_DIR" if (! -e $WORKING_DIR);
13 | my @MAIN_FILES = qw/FreeSwitch.dll libapr.dll libaprutil.dll libbroadvoice.dll libspandsp.dll libteletone.dll mod_managed.dll pthread.dll libeay32.dll ssleay32.dll FreeSWITCH.Managed.dll libpng16.dll/;
14 | my @modules = qw/mod_celt mod_commands mod_console mod_dialplan_xml mod_dptools mod_event_socket mod_ilbc mod_local_stream mod_logfile mod_loopback mod_PortAudio mod_siren mod_sndfile mod_sofia mod_opus mod_tone_stream mod_h26x mod_silk mod_spandsp mod_bv mod_iSAC mod_conference/;
15 |
16 | my $link_cmd = $USE_LN ? "ln -s -a" : "copy";
17 |
18 | sub exec_cmd($){
19 | my ($cmd) = @_;
20 | print $cmd . "\n" if ($VERBOSE);
21 | my $res = `$cmd 2>&1`;
22 | chomp($res);
23 | chomp($res);
24 | $res =~ s/\n/\n\t/g;
25 | print $res . "\n" if ($VERBOSE_SYS_RES);
26 | }
27 | sub link_file($$){
28 | my ($src,$dst) = @_;
29 | exec_cmd(qq~$link_cmd "$src" "$dst"~);
30 | print STDERR "Warning after copying/linking file: \"$dst\" did not exist src: $src\n" if (! -e $dst);
31 | }
32 | exec_cmd("mkdir " . $WORKING_DIR . "\\mod") if (-e $WORKING_DIR . "\\mod" == 0);
33 | foreach my $module (@modules){
34 | my $rel_path = "mod\\" . $module . ".dll";
35 | my $src_path = $FS_BUILD_PATH . "\\" . $rel_path;
36 | if (-e $src_path == 0){
37 | print STDERR "Warning: $src_path does not exist\n";
38 | next;
39 | }
40 | if ($try_pdb){
41 | my $pdb_file = $src_path;
42 | $pdb_file =~ s/\.dll$/.pdb/si;
43 | my $pdb_dest = "$WORKING_DIR\\$rel_path";
44 | $pdb_dest =~ s/\.dll$/.pdb/si;
45 | link_file($pdb_file,$pdb_dest) if (-e $pdb_file && (! $USE_LN || ! -e $pdb_dest));
46 | }
47 | next if (-e "$WORKING_DIR\\" . $rel_path && $USE_LN);
48 | link_file($src_path, "$WORKING_DIR\\$rel_path");
49 |
50 | }
51 |
52 | foreach my $file (@MAIN_FILES){
53 | my $src_path = $FS_BUILD_PATH . "\\" . $file;
54 | $src_path = $FS_BUILD_PATH . "\\mod\\" . $file if (-e $FS_BUILD_PATH . "\\mod\\" . $file); #mainly for putting mod_managed.dll into the root
55 | if (-e $src_path == 0){
56 | print STDERR "Warning: $src_path does not exist\n";
57 | next;
58 | }
59 | if ($try_pdb){
60 | my $pdb_file = $src_path;
61 | $pdb_file =~ s/\.dll$/.pdb/si;
62 | my $pdb_dest = "$WORKING_DIR\\$file";
63 | $pdb_dest =~ s/\.dll$/.pdb/si;
64 | link_file($pdb_file,$pdb_dest) if (-e $pdb_file && (! $USE_LN || ! -e $pdb_dest));
65 | }
66 | next if (-e "$WORKING_DIR\\" . $file && $USE_LN);
67 | link_file($src_path, "$WORKING_DIR\\$file");
68 | }
69 |
70 | if ($COPY_CONF){
71 | exec_cmd("mkdir " . $WORKING_DIR . "\\conf") if (-e $WORKING_DIR . "\\conf" == 0);
72 | link_file("conf\\freeswitch.xml","$WORKING_DIR\\conf\\freeswitch.xml");
73 | }
74 |
--------------------------------------------------------------------------------
/External Items/portaudio_notes.txt:
--------------------------------------------------------------------------------
1 | Portaudio changes:
2 | The major change in portaudio is the way streams are handled. Streams are only torn down at the end of all of the calls (rather than the end of any call). This increases the speed when switching between calls. Timers were also tweaked to make sure there are no more shared timers (caused some audio issues otherwise with multiple calls).
3 |
4 | These changes have not been tested in linux, however 99% of the changes are off by default (and must be enabled using vairables in the portaudio config) so it should be fairly stable. I also found the best results in windows using libportaudio compiled with DX support, however these changes are not DX specific.
5 |
6 | New Additions:
7 | added call held and call resumed events
8 | base support for an infinite number of streams
9 | Ability to keep streams around until all calls are completed rather than re-initing them between any call swtiches.
10 | Call ID variable standarized on "pa_call_id" for events (but left behind old for backwards compatability).
11 | Added ability to prevent ringing if there is currently an active call (variable "no-ring-during-call")
12 | Add option to not auto-unhold the next call once a call is ended (variable "no-auto-resume-call")
13 | Added ability to play audio directly to a specific stream ("pa playdev # [ringtest|] [seconds] [no_close]")
14 | Add ability to switch between streams during a call ("pa indev or pa outdev" only if variable "live-stream-switch" is enabled)
15 | Add ability to switch both streams at once for speed ("pa switchstream # #")
16 | Add ability to open a stream prior to using it for faster swtiching ("pa preparestream # #")
17 | Added the option to specify the number of seconds to play the audio for "pa play" and "pa playdev"
18 | Added option of no_close to not close streams after play or playdev, useful if playing multiple things in rapid succession
19 | Add closestreams command to allow for closing any currently open/prepared streams.
20 |
21 | Fixes:
22 | independent timers for everything eneded (should fix some audio glitches)
23 | No longer will ring the ring device during a call if the ring device is the same as the call device(will just screw up audio otherwise)
24 | Muting is global and no longer reset at the start of each call (this is a change in behavior)
25 | As you could have multiple play or playdevs running try to avoid destroying streams
26 |
--------------------------------------------------------------------------------
/FSClient.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32414.248
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSClient", "FSClient.csproj", "{A2D2A785-3B79-4042-9C3D-97B1CD9A804E}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlantronicsHeadsetPlugin", "PlantronicsHeadset\PlantronicsHeadsetPlugin.csproj", "{5E156019-7D68-402D-9109-7F7F1E55EA8A}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JabraHeadsetPlugin", "JabraHeadset\JabraHeadsetPlugin.csproj", "{084B8F8C-1E32-4545-B865-FA7274BD8054}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External Items", "External Items", "{E5A3FCB6-A888-4290-B076-864674F90508}"
13 | ProjectSection(SolutionItems) = preProject
14 | External Items\JabraTelephonyAPI.dll = External Items\JabraTelephonyAPI.dll
15 | External Items\Plantronics.Device.Common.dll = External Items\Plantronics.Device.Common.dll
16 | EndProjectSection
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "conf", "conf", "{BDB28E84-D1D5-4102-BD29-C638197EFD42}"
19 | ProjectSection(SolutionItems) = preProject
20 | External Items\conf\freeswitch.xml = External Items\conf\freeswitch.xml
21 | EndProjectSection
22 | EndProject
23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleXmlContactPlugin", "SimpleXmlContactPlugin\SimpleXmlContactPlugin.csproj", "{8DA31553-28E9-4A08-897E-7EE1E01356A3}"
24 | EndProject
25 | Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Setup", "Setup\Setup.wixproj", "{80D26E64-EECF-47F2-8132-804F087C27BA}"
26 | EndProject
27 | Global
28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
29 | Debug|Any CPU = Debug|Any CPU
30 | Debug|Mixed Platforms = Debug|Mixed Platforms
31 | Debug|x64 = Debug|x64
32 | Debug|x86 = Debug|x86
33 | Release|Any CPU = Release|Any CPU
34 | Release|Mixed Platforms = Release|Mixed Platforms
35 | Release|x64 = Release|x64
36 | Release|x86 = Release|x86
37 | EndGlobalSection
38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
39 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Debug|Any CPU.ActiveCfg = Debug|x86
40 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
41 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Debug|Mixed Platforms.Build.0 = Debug|x86
42 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Debug|x64.ActiveCfg = Debug|x64
43 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Debug|x64.Build.0 = Debug|x64
44 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Debug|x86.ActiveCfg = Debug|x86
45 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Debug|x86.Build.0 = Debug|x86
46 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Release|Any CPU.ActiveCfg = Release|x86
47 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Release|Mixed Platforms.ActiveCfg = Release|x86
48 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Release|Mixed Platforms.Build.0 = Release|x86
49 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Release|x64.ActiveCfg = Release|x64
50 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Release|x86.ActiveCfg = Release|x86
51 | {A2D2A785-3B79-4042-9C3D-97B1CD9A804E}.Release|x86.Build.0 = Release|x86
52 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
55 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
56 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Debug|x64.ActiveCfg = Debug|Any CPU
57 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Debug|x64.Build.0 = Debug|Any CPU
58 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Debug|x86.ActiveCfg = Debug|Any CPU
59 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Release|Any CPU.Build.0 = Release|Any CPU
61 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
62 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
63 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Release|x64.ActiveCfg = Release|Any CPU
64 | {5E156019-7D68-402D-9109-7F7F1E55EA8A}.Release|x86.ActiveCfg = Release|Any CPU
65 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|Any CPU.Build.0 = Debug|Any CPU
67 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
68 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
69 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|x64.ActiveCfg = Debug|Any CPU
70 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|x64.Build.0 = Debug|Any CPU
71 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|x86.ActiveCfg = Debug|Any CPU
72 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Debug|x86.Build.0 = Debug|Any CPU
73 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Release|Any CPU.ActiveCfg = Release|Any CPU
74 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Release|Any CPU.Build.0 = Release|Any CPU
75 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
76 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Release|Mixed Platforms.Build.0 = Release|Any CPU
77 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Release|x64.ActiveCfg = Release|Any CPU
78 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Release|x86.ActiveCfg = Release|Any CPU
79 | {084B8F8C-1E32-4545-B865-FA7274BD8054}.Release|x86.Build.0 = Release|Any CPU
80 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
82 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
83 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
84 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Debug|x64.ActiveCfg = Debug|Any CPU
85 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Debug|x64.Build.0 = Debug|Any CPU
86 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Debug|x86.ActiveCfg = Debug|Any CPU
87 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
88 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Release|Any CPU.Build.0 = Release|Any CPU
89 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
90 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
91 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Release|x64.ActiveCfg = Release|Any CPU
92 | {8DA31553-28E9-4A08-897E-7EE1E01356A3}.Release|x86.ActiveCfg = Release|Any CPU
93 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Debug|Any CPU.ActiveCfg = Debug|x86
94 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
95 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Debug|x64.ActiveCfg = Debug|x86
96 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Debug|x86.ActiveCfg = Debug|x86
97 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Debug|x86.Build.0 = Debug|x86
98 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Release|Any CPU.ActiveCfg = Release|x86
99 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Release|Mixed Platforms.ActiveCfg = Release|x86
100 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Release|x64.ActiveCfg = Release|x86
101 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Release|x86.ActiveCfg = Release|x86
102 | {80D26E64-EECF-47F2-8132-804F087C27BA}.Release|x86.Build.0 = Release|x86
103 | EndGlobalSection
104 | GlobalSection(SolutionProperties) = preSolution
105 | HideSolutionNode = FALSE
106 | EndGlobalSection
107 | GlobalSection(NestedProjects) = preSolution
108 | {BDB28E84-D1D5-4102-BD29-C638197EFD42} = {E5A3FCB6-A888-4290-B076-864674F90508}
109 | EndGlobalSection
110 | GlobalSection(ExtensibilityGlobals) = postSolution
111 | SolutionGuid = {CA73B252-DD83-404C-A0A4-B6FF0862370F}
112 | EndGlobalSection
113 | EndGlobal
114 |
--------------------------------------------------------------------------------
/FSEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FreeSWITCH.Native;
3 |
4 |
5 |
6 | namespace FSClient {
7 | public class FSEvent : EventArgs {
8 | private switch_event evt;
9 | public FSEvent(switch_event evt) {
10 | this.evt = evt;
11 | }
12 | public string get_header(string name) {
13 | return freeswitch.switch_event_get_header_idx(evt, name, -1);
14 | //for (var x = evt.headers; x != null; x = x.next){
15 | // if (name.ToLower() == x.name.ToLower())
16 | // return x.value;
17 | //}
18 | //return null;
19 | }
20 | public string body { get { return evt.body; } }
21 | public int flags { get { return evt.flags; } }
22 | public uint key { get { return evt.key; } }
23 | public string owner { get { return evt.owner; } }
24 | public string subclass_name { get { return evt.subclass_name; } }
25 | public switch_event_types_t event_id { get { return evt.event_id; } }
26 | public switch_event_header first_header { get { return evt.headers; } }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Field.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | namespace FSClient {
5 | public class SettingsField {
6 | public String name { get; set; }
7 | public String value { get; set; }
8 | public SettingsField() {
9 | }
10 | public SettingsField(FieldValue fv) {
11 | name = fv.field.name;
12 | value = fv.value;
13 | }
14 | }
15 | public class Field {
16 | public class FieldOption {
17 | public string display_value;
18 | public string value;
19 | public override string ToString() {
20 | return display_value;
21 | }
22 | public static FieldOption GetByValue(FieldOption[] options, String value) {
23 | return (from o in options where o.value == value select o).SingleOrDefault();
24 | }
25 | }
26 | public enum FIELD_TYPE { String, Int, Bool, Combo, MultiItem, Password, MultiItemSort };
27 | public FIELD_TYPE type;
28 | public delegate string validate_field_del(String value);
29 | public validate_field_del Validator;
30 | public static Field GetByName(Field[] fields, string name) {
31 | return (from f in fields where f.name == name select f).SingleOrDefault();
32 | }
33 | public Field(FIELD_TYPE type, string display_name, string name, string xml_name, string default_value, string category) {
34 | this.type = type;
35 | this.display_name = display_name;
36 | this.name = name;
37 | this.default_value = default_value;
38 | this.category = category;
39 | this.xml_name = xml_name;
40 | }
41 | public Field(FIELD_TYPE type, string display_name, string name, string xml_name, string default_value, string category, params string[] option_values) {
42 | this.type = type;
43 | this.display_name = display_name;
44 | this.name = name;
45 | this.default_value = default_value;
46 | this.category = category;
47 | this.xml_name = xml_name;
48 | foreach (string value in option_values)
49 | AddOption(value);
50 | }
51 | public Field(FIELD_TYPE type, string display_name, string name, string xml_name, string default_value, string category, params FieldOption[] option_values) {
52 | this.type = type;
53 | this.display_name = display_name;
54 | this.name = name;
55 | this.default_value = default_value;
56 | this.category = category;
57 | this.xml_name = xml_name;
58 | foreach (FieldOption value in option_values)
59 | _options.Add(value);
60 | }
61 | public FieldValue GetDefaultValue() {
62 | return new FieldValue { field = this, value = default_value };
63 | }
64 | public string display_name;
65 | public string name;
66 | public string xml_name;
67 | public string category;
68 | public string default_value;
69 | private List _options = new List();
70 | public FieldOption[] options {
71 | get {
72 | return _options.ToArray();
73 | }
74 | }
75 | public void AddOption(string value) {
76 | AddOption(value, value);
77 | }
78 | public void AddOption(string value, string display_value) {
79 | _options.Add(new FieldOption { display_value = display_value, value = value });
80 | }
81 |
82 | }
83 | public class FieldValue : ObservableClass {
84 | public Field field;
85 |
86 | public string value {
87 | get { return _value; }
88 | set {
89 | if (value == _value)
90 | return;
91 | _value = value;
92 | RaisePropertyChanged("value");
93 | }
94 | }
95 | private string _value;
96 |
97 | public override string ToString() {
98 | return value;
99 | }
100 | public static FieldValue[] FieldValues(Field[] fields) {
101 | FieldValue[] ret = (from f in fields select f.GetDefaultValue()).ToArray();
102 | return ret;
103 | }
104 | public static void SetValues(FieldValue[] values, params string[] name_value_pairs) {
105 | for (int i = 0; i < name_value_pairs.Length; i += 2)
106 | GetByName(values, name_value_pairs[i]).value = name_value_pairs[i + 1];
107 | }
108 | public static FieldValue GetByName(FieldValue[] values, string name) {
109 | return (from v in values where v.field.name == name select v).SingleOrDefault();
110 | }
111 | public static FieldValue GetByDisplayName(FieldValue[] values, string name) {
112 | return (from v in values where v.field.display_name == name select v).SingleOrDefault();
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/GenericEditor.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/GenericEditor.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Automation;
5 | using System.Windows.Controls;
6 | using System.Windows.Input;
7 |
8 | namespace FSClient {
9 | ///
10 | /// Interaction logic for GenericEditor.xaml
11 | ///
12 | public partial class GenericEditor : Window {
13 | public GenericEditor() {
14 | this.InitializeComponent();
15 |
16 | // Insert code required on object creation below this point.
17 | }
18 | private Dictionary elements_to_ui = new Dictionary();
19 | private Dictionary element_to_page = new Dictionary();
20 | public void Init(String title, IEnumerable values) {
21 | txtTitle.Text = title;
22 | Title = title;
23 | Dictionary tabs = new Dictionary();
24 | foreach (FieldValue value in values) {
25 | String cat = value.field.category;
26 | if (String.IsNullOrEmpty(cat))
27 | cat = "Default";
28 | StackPanel page;
29 | if (!tabs.TryGetValue(cat, out page)) {
30 | TabItem item = new TabItem();
31 | item.Header = cat;
32 | tabControlMain.Items.Add(item);
33 | page = tabs[cat] = new StackPanel();
34 | tabs[cat].Orientation = Orientation.Vertical;
35 | ScrollViewer viewer = new ScrollViewer();
36 | item.Content = viewer;
37 | tabs[cat].Tag = item;
38 | viewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
39 | viewer.Content = tabs[cat];
40 | }
41 | element_to_page[value] = page.Tag as TabItem;
42 | StackPanel row = new StackPanel();
43 | row.Margin = new Thickness(0, 3, 5, 0);
44 | row.Orientation = Orientation.Horizontal;
45 |
46 | TextBlock txt = new TextBlock();
47 | txt.Text = value.field.display_name + ": ";
48 | txt.FontWeight = FontWeights.Bold;
49 | txt.TextAlignment = TextAlignment.Right;
50 | txt.Width = 200;
51 | row.Children.Add(txt);
52 | elements_to_ui[value] = CreateElementValuer(value);
53 | row.Children.Add(elements_to_ui[value]);
54 | page.Children.Add(row);
55 |
56 | }
57 | }
58 | private void CreateSortableContextMenu(ListBox box){
59 | ContextMenu menu = new ContextMenu();
60 | var item = new MenuItem() { Header = "Move Up" };
61 | item.Click += (s,e) => SortableContextMenuMove(box,-1,s,e);
62 | menu.Items.Add(item);
63 | item = new MenuItem() { Header = "Move Down" };
64 | item.Click += (s, e) => SortableContextMenuMove(box, 1, s, e);
65 | menu.Items.Add(item);
66 | box.ContextMenu = menu;
67 | box.ContextMenuOpening += (s,e) => SortableMenuOpening(box,menu,s,e);
68 | box.PreviewMouseDown += box_MouseDown;
69 |
70 | }
71 |
72 | void box_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
73 | if (e.RightButton == MouseButtonState.Pressed)
74 | e.Handled = true;
75 | }
76 |
77 |
78 |
79 | private void SortableMenuOpening(ListBox box,ContextMenu menu,object sender, ContextMenuEventArgs e){
80 | menu.DataContext = ((FrameworkElement)e.OriginalSource).DataContext;
81 | object elem = menu.DataContext;
82 | }
83 |
84 |
85 | private void SortableContextMenuMove(ListBox box, int move_by, object sender, RoutedEventArgs e){
86 | MenuItem item = (sender as MenuItem);
87 | if (item == null || item.DataContext == null)
88 | return;
89 | object to_move = item.DataContext;
90 | for (int pos = 0; pos < box.Items.Count; pos++){
91 | if (box.Items[pos] == to_move){
92 | if (pos + move_by < 0 || pos + move_by >= box.Items.Count)
93 | return;
94 | bool selected = box.SelectedItems.Contains(to_move);
95 | box.Items.Remove(to_move);
96 | box.Items.Insert(pos + move_by, to_move);
97 | if (selected)
98 | box.SelectedItems.Add(to_move);
99 | return;
100 | }
101 | }
102 |
103 | }
104 |
105 | private UIElement CreateElementValuer(FieldValue value) {
106 | Control ret = null;
107 | switch (value.field.type) {
108 | case Field.FIELD_TYPE.Int:
109 | TextBox ibox = new TextBox();
110 | ibox.Text = value.value;
111 | ibox.Width = 50;
112 | ret = ibox;
113 | break;
114 | case Field.FIELD_TYPE.MultiItem:
115 | case Field.FIELD_TYPE.MultiItemSort:
116 | ListBox listBox = new ListBox();
117 | listBox.SelectionMode = SelectionMode.Multiple;
118 | if (value.field.type == Field.FIELD_TYPE.MultiItemSort)
119 | CreateSortableContextMenu(listBox);
120 | listBox.Height = 100;
121 | listBox.Width = 190;
122 | String[] vals = value.value.Split(',');
123 | foreach (String val in vals) {
124 | Field.FieldOption opt = Field.FieldOption.GetByValue(value.field.options, val);
125 | if (opt != null){
126 | listBox.Items.Add(opt);
127 | listBox.SelectedItems.Add(opt);
128 | }
129 | }
130 | foreach (Field.FieldOption option in value.field.options) {
131 | if (value.field.Validator != null && !String.IsNullOrEmpty(value.field.Validator(option.value)))
132 | continue;
133 | if (listBox.Items.Contains(option))
134 | continue;
135 | listBox.Items.Add(option);
136 | }
137 | ret = listBox;
138 | break;
139 | case Field.FIELD_TYPE.String:
140 | TextBox box = new TextBox();
141 | box.Text = value.value;
142 | box.Width = 200;
143 | ret = box;
144 | break;
145 | case Field.FIELD_TYPE.Password:
146 | PasswordBox pbox = new PasswordBox();
147 | pbox.Password = value.value;
148 | pbox.Width = 200;
149 | ret = pbox;
150 | break;
151 | case Field.FIELD_TYPE.Bool:
152 | CheckBox cbox = new CheckBox();
153 | cbox.IsChecked = (value.value == "true");
154 | ret = cbox;
155 | break;
156 | case Field.FIELD_TYPE.Combo:
157 | ComboBox comboBox = new ComboBox();
158 | foreach (Field.FieldOption option in value.field.options) {
159 | if (value.field.Validator == null || String.IsNullOrEmpty(value.field.Validator(option.value)))
160 | comboBox.Items.Add(option);
161 | }
162 | comboBox.SelectedItem = Field.FieldOption.GetByValue(value.field.options, value.value);
163 | if (comboBox.SelectedIndex == -1)
164 | comboBox.SelectedIndex = 0;
165 | ret = comboBox;
166 | break;
167 | }
168 | AutomationProperties.SetName(ret, value.field.display_name);
169 | return ret;
170 | }
171 | private void btnCancel_Click(object sender, RoutedEventArgs e) {
172 | DialogResult = false;
173 | Close();
174 | }
175 | private String IntValidator(String str) {
176 | int trash;
177 | if (Int32.TryParse(str, out trash) == false)
178 | return "Number must be an integer";
179 | return null;
180 | }
181 | private void btnSave_Click(object sender, RoutedEventArgs e) {
182 | foreach (KeyValuePair kvp in elements_to_ui) {
183 | String val = GetValueFromUI(kvp.Key, kvp.Value);
184 |
185 |
186 | String err = "";
187 | if (kvp.Key.field.Validator != null)
188 | err = kvp.Key.field.Validator(val);
189 | else if (kvp.Key.field.type == Field.FIELD_TYPE.Int)
190 | err = IntValidator(val);
191 |
192 | if (!String.IsNullOrEmpty(err)) {
193 | String cat = kvp.Key.field.category;
194 | if (String.IsNullOrEmpty(cat))
195 | cat = "Default";
196 | element_to_page[kvp.Key].IsSelected = true;
197 | MessageBox.Show("The field \"" + kvp.Key.field.display_name + "\"(" + cat + " Tab) Is valid due to: " + err);
198 | return;
199 | }
200 | }
201 | foreach (KeyValuePair kvp in elements_to_ui) {
202 | String val = GetValueFromUI(kvp.Key, kvp.Value);
203 | kvp.Key.value = val;
204 | }
205 | DialogResult = true;
206 | Close();
207 | }
208 | private String GetValueFromUI(FieldValue value, UIElement elem) {
209 | String val = null;
210 | switch (value.field.type) {
211 | case Field.FIELD_TYPE.MultiItem:
212 | case Field.FIELD_TYPE.MultiItemSort:
213 | ListBox listBox = (elem as ListBox);
214 | val = "";
215 | foreach (Object obj in listBox.Items) {
216 | if (listBox.SelectedItems.Contains(obj) == false)
217 | continue;
218 | Field.FieldOption opt2 = obj as Field.FieldOption;
219 | if (opt2 != null) {
220 | if (!String.IsNullOrEmpty(val))
221 | val += ",";
222 | val += opt2.value;
223 | }
224 | }
225 | break;
226 | case Field.FIELD_TYPE.Int:
227 | case Field.FIELD_TYPE.String:
228 | val = ((TextBox) elem).Text;
229 | break;
230 | case Field.FIELD_TYPE.Password:
231 | val = ((PasswordBox) elem).Password;
232 | break;
233 | case Field.FIELD_TYPE.Bool:
234 | val = (((CheckBox) elem).IsChecked == true) ? "true" : "false";
235 | break;
236 | case Field.FIELD_TYPE.Combo:
237 | Field.FieldOption opt = ((ComboBox) elem).SelectedItem as Field.FieldOption;
238 | if (opt != null)
239 | val = opt.value;
240 | break;
241 | }
242 | return val;
243 | }
244 | }
245 | }
--------------------------------------------------------------------------------
/HotKey.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Net.Mime;
7 | using System.Runtime.InteropServices;
8 | using System.Text;
9 | using System.Windows;
10 | using System.Windows.Input;
11 | using System.Windows.Interop;
12 |
13 |
14 | namespace UnManaged {
15 | #if ! HOTKEY_PROXY
16 | [Flags]
17 | public enum KeyModifier {
18 | None = 0x0000,
19 | Alt = 0x0001,
20 | Ctrl = 0x0002,
21 | NoRepeat = 0x4000,
22 | Shift = 0x0004,
23 | Win = 0x0008
24 | }
25 | #else
26 | using GlobalHotkeyLib;
27 | #endif
28 |
29 | public class GlobalHotKey : IDisposable {
30 | private static Dictionary _dictHotKeyToCalBackProc;
31 |
32 | [DllImport("user32.dll")]
33 | private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);
34 |
35 | [DllImport("user32.dll")]
36 | private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
37 |
38 | public const int WmHotKey = 0x0312;
39 |
40 | private bool _disposed = false;
41 | public static Key ParseStringToKey(string key) {
42 | if (key[0] >= 0 && key[0] <= 9)
43 | key = "D" + key;
44 | Enum.TryParse(key, out var hot_key);
45 | return hot_key;
46 | }
47 |
48 | public Key Key { get; private set; }
49 | public KeyModifier KeyModifiers { get; private set; }
50 | public Action Action { get; set; }
51 | public int Id { get; set; }
52 |
53 | // ******************************************************************
54 | public GlobalHotKey(Key k, KeyModifier keyModifiers, Action action, bool register = true) {
55 | Key = k;
56 | KeyModifiers = keyModifiers;
57 | Action = action;
58 | if (register)
59 | Register();
60 |
61 | }
62 | public GlobalHotKey() { }
63 | private static object lock_obj = new object();
64 | public void UpdateHotKey(Key k, KeyModifier keyModifiers, bool register = true) {
65 | Key = k;
66 | KeyModifiers = keyModifiers;
67 | if (register)
68 | Register();
69 | }
70 |
71 | // ******************************************************************
72 | public bool Register() {
73 | if (Id != 0)
74 | Unregister();
75 | int virtualKeyCode = KeyInterop.VirtualKeyFromKey(Key);
76 | Id = virtualKeyCode + ((int)KeyModifiers * 0x10000);
77 | bool result = RegisterHotKey(IntPtr.Zero, Id, (UInt32)KeyModifiers, (UInt32)virtualKeyCode);
78 | lock (lock_obj) {
79 | if (_dictHotKeyToCalBackProc == null) {
80 | _dictHotKeyToCalBackProc = new Dictionary();
81 | ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
82 | }
83 | }
84 |
85 | _dictHotKeyToCalBackProc.Add(Id, this);
86 | if (!result)
87 | throw new Exception("Unable to register hot key");
88 | //Debug.Print(result.ToString() + ", " + Id + ", " + virtualKeyCode);
89 | return result;
90 | }
91 |
92 | // ******************************************************************
93 | public void Unregister() {
94 | if (Id == 0)
95 | return;
96 | UnregisterHotKey(IntPtr.Zero, Id);
97 | _dictHotKeyToCalBackProc.Remove(Id);
98 | Id = 0;
99 | }
100 |
101 | // ******************************************************************
102 | private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled) {
103 | if (!handled) {
104 | if (msg.message == WmHotKey) {
105 | GlobalHotKey hotKey;
106 |
107 | if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey)) {
108 | if (hotKey.Action != null) {
109 | Application.Current.Dispatcher.BeginInvoke((Action)(()=>
110 | hotKey.Action.Invoke(hotKey)
111 | ));
112 | }
113 | handled = true;
114 | }
115 | }
116 | }
117 | }
118 |
119 | // ******************************************************************
120 | // Implement IDisposable.
121 | // Do not make this method virtual.
122 | // A derived class should not be able to override this method.
123 | public void Dispose() {
124 | Dispose(true);
125 | // This object will be cleaned up by the Dispose method.
126 | // Therefore, you should call GC.SupressFinalize to
127 | // take this object off the finalization queue
128 | // and prevent finalization code for this object
129 | // from executing a second time.
130 | GC.SuppressFinalize(this);
131 | }
132 |
133 | // ******************************************************************
134 | // Dispose(bool disposing) executes in two distinct scenarios.
135 | // If disposing equals true, the method has been called directly
136 | // or indirectly by a user's code. Managed and unmanaged resources
137 | // can be _disposed.
138 | // If disposing equals false, the method has been called by the
139 | // runtime from inside the finalizer and you should not reference
140 | // other objects. Only unmanaged resources can be _disposed.
141 | protected virtual void Dispose(bool disposing) {
142 | // Check to see if Dispose has already been called.
143 | if (!this._disposed) {
144 | // If disposing equals true, dispose all managed
145 | // and unmanaged resources.
146 | if (disposing) {
147 | // Dispose managed resources.
148 | Unregister();
149 | }
150 |
151 | // Note disposing has been done.
152 | _disposed = true;
153 | }
154 | }
155 | }
156 |
157 | // ******************************************************************
158 |
159 |
160 | // ******************************************************************
161 | }
162 |
--------------------------------------------------------------------------------
/IContactPlugin.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.Specialized;
5 | using System.ComponentModel;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Reflection;
9 | using System.Threading.Tasks;
10 | using System.Windows;
11 | using System.Windows.Controls;
12 | using FSClient.Controls;
13 |
14 | namespace FSClient {
15 | public class ContactPluginManager : PluginManagerBase {
16 |
17 | public static ContactPluginManager GetPluginManager(SettingsPluginDataCollection settings){
18 | ContactPluginManager manager = new ContactPluginManager();
19 | manager.LoadSettings(settings);
20 | return manager;
21 | }
22 | public static IEnumerable