From 7131f06e97a769644ced387ee9b5105b4ab71927 Mon Sep 17 00:00:00 2001 From: MrX13415 Date: Sun, 8 Jun 2014 22:15:48 +0200 Subject: [PATCH] Inital commit --- CursorAreaLock.sln | 22 + CursorAreaLock/ConsoleEx.cs | 113 +++++ CursorAreaLock/ConsolePage.cs | 32 ++ CursorAreaLock/CursorAreaLock.csproj | 117 ++++++ CursorAreaLock/Executable.cs | 33 ++ CursorAreaLock/HotkeyBar.cs | 217 ++++++++++ CursorAreaLock/ItemList.cs | 100 +++++ CursorAreaLock/Kernel32.cs | 27 ++ CursorAreaLock/Program.cs | 476 ++++++++++++++++++++++ CursorAreaLock/Properties/AssemblyInfo.cs | 38 ++ CursorAreaLock/Properties/app.manifest | 54 +++ CursorAreaLock/README.md | 19 + CursorAreaLock/Service.cs | 300 ++++++++++++++ CursorAreaLock/User32.cs | 95 +++++ 14 files changed, 1643 insertions(+) create mode 100644 CursorAreaLock.sln create mode 100644 CursorAreaLock/ConsoleEx.cs create mode 100644 CursorAreaLock/ConsolePage.cs create mode 100644 CursorAreaLock/CursorAreaLock.csproj create mode 100644 CursorAreaLock/Executable.cs create mode 100644 CursorAreaLock/HotkeyBar.cs create mode 100644 CursorAreaLock/ItemList.cs create mode 100644 CursorAreaLock/Kernel32.cs create mode 100644 CursorAreaLock/Program.cs create mode 100644 CursorAreaLock/Properties/AssemblyInfo.cs create mode 100644 CursorAreaLock/Properties/app.manifest create mode 100644 CursorAreaLock/README.md create mode 100644 CursorAreaLock/Service.cs create mode 100644 CursorAreaLock/User32.cs diff --git a/CursorAreaLock.sln b/CursorAreaLock.sln new file mode 100644 index 0000000..fd1bce8 --- /dev/null +++ b/CursorAreaLock.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CursorAreaLock", "CursorAreaLock\CursorAreaLock.csproj", "{E78579D9-B748-4BC8-B054-C2A13B55772B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E78579D9-B748-4BC8-B054-C2A13B55772B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E78579D9-B748-4BC8-B054-C2A13B55772B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E78579D9-B748-4BC8-B054-C2A13B55772B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E78579D9-B748-4BC8-B054-C2A13B55772B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/CursorAreaLock/ConsoleEx.cs b/CursorAreaLock/ConsoleEx.cs new file mode 100644 index 0000000..6e03444 --- /dev/null +++ b/CursorAreaLock/ConsoleEx.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.ConsoleExtentions +{ + public class ConsoleEx + { + public static ConsoleColor DefaultForegroundColor{ get; set; } + public static ConsoleColor DefaultBackgroundColor{ get; set; } + + static ConsoleEx(){ + DefaultForegroundColor = Console.ForegroundColor; + DefaultBackgroundColor = Console.BackgroundColor; + } + + public static void ResetColors() + { + Console.ForegroundColor = DefaultForegroundColor; + Console.BackgroundColor = DefaultBackgroundColor; + } + + public static void Write(string text, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.Write(text); + ResetColors(); + } + + public static void WriteKeyValue(string key, string value, ConsoleColor valueColor) + { + WriteKeyValue(key, value, valueColor, DefaultForegroundColor); + } + + public static void WriteKeyValue(string key, string value, ConsoleColor valueColor, string seperator) + { + WriteKeyValue(key, value, valueColor, DefaultForegroundColor, seperator); + } + + public static void WriteKeyValue(string key, string value, ConsoleColor valueColor, ConsoleColor keyColor) + { + _WriteKeyValue(key, value, valueColor, keyColor, ": "); + } + + public static void WriteKeyValue(string key, string value, ConsoleColor valueColor, ConsoleColor keyColor, string seperator) + { + _WriteKeyValue(key, value, valueColor, keyColor, seperator); + } + + private static string _WriteKeyValue(string key, string value, ConsoleColor valueColor, ConsoleColor keyColor, string template) + { + Console.ForegroundColor = keyColor; + Console.Write(key); + Console.Write(template); + Console.ForegroundColor = valueColor; + Console.Write(value); + ResetColors(); + return key + template + value; + } + + public static void WriteKeyValueLine(string key, string value, ConsoleColor valueColor) + { + WriteKeyValueLine(key, value, valueColor, DefaultForegroundColor, ": "); + } + + public static void WriteKeyValueLine(string key, string value, ConsoleColor valueColor, string seperator) + { + WriteKeyValueLine(key, value, valueColor, DefaultForegroundColor, seperator); + } + + public static void WriteKeyValueLine(string key, string value, ConsoleColor valueColor, ConsoleColor keyColor, string seperator) + { + string text = _WriteKeyValue(key, value, valueColor, keyColor, seperator); + Console.WriteLine("".PadRight(Console.WindowWidth - text.Length - 1)); + } + + public static void WriteLine() + { + WriteLine(""); + } + + public static void WriteLine(String text) + { + WriteLine(text, DefaultForegroundColor); + } + + public static void WriteLine(String text, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.WriteLine(text.PadRight(Console.WindowWidth - 1)); + ResetColors(); + } + + public static void WriteLineCenter(String text) + { + WriteLineCenter(text, DefaultForegroundColor); + } + + public static void WriteLineCenter(String text, ConsoleColor color) + { + int lineWidth = Console.WindowWidth - text.Length - 1; + Console.ForegroundColor = color; + Console.WriteLine(String.Format("{0," + (lineWidth / 2 * -1) + "}{1," + (lineWidth / 2 * -1) + "}", "", text)); + ResetColors(); + } + + public static string CutText(string text, int lenght) + { + if (text.Length <= lenght) return text; + return text.Substring(0, lenght - 3) + "..."; + } + } +} diff --git a/CursorAreaLock/ConsolePage.cs b/CursorAreaLock/ConsolePage.cs new file mode 100644 index 0000000..c38556e --- /dev/null +++ b/CursorAreaLock/ConsolePage.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.ConsoleExtentions +{ + class ConsolePage + { + public delegate void PrintHandler(T data); + private PrintHandler _printHandler; + public HotkeyBar Hotkeys { get; set; } + public T DataProvider { get; set; } + + public ConsolePage(PrintHandler handler, T data, HotkeyBar hotkeybar) + { + this._printHandler = handler; + this.DataProvider = data; + this.Hotkeys = hotkeybar; + } + + public void Print() + { + try + { + //print this page ... + _printHandler(DataProvider); + } + catch (Exception) { } + } + + } +} diff --git a/CursorAreaLock/CursorAreaLock.csproj b/CursorAreaLock/CursorAreaLock.csproj new file mode 100644 index 0000000..7b5d5b1 --- /dev/null +++ b/CursorAreaLock/CursorAreaLock.csproj @@ -0,0 +1,117 @@ + + + + + Debug + AnyCPU + {E78579D9-B748-4BC8-B054-C2A13B55772B} + Exe + Properties + CursorAreaLock + CursorAreaLock + v2.0 + 512 + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 2.0.0.%2a + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + AllRules.ruleset + + + true + + + false + + + false + + + MrX13415.pfx + + + LocalIntranet + + + false + + + Properties\app.manifest + + + CursorAreaLock.Program + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + + + \ No newline at end of file diff --git a/CursorAreaLock/Executable.cs b/CursorAreaLock/Executable.cs new file mode 100644 index 0000000..abf9b84 --- /dev/null +++ b/CursorAreaLock/Executable.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace CursorAreaLock +{ + class Executable + { + public string DisplayName { get; set; } + public string ExecutablePath { get; set; } + + public Executable(string exe) : this(exe, Path.GetFileName(exe)) { } + public Executable(string exe, string displayName) + { + ExecutablePath = exe; + DisplayName = displayName; + } + + public override bool Equals(object obj) + { + if (obj is Executable) + return this.ExecutablePath.ToLower().Equals(((Executable)obj).ExecutablePath.ToLower()); + else + return false; + } + + public override int GetHashCode() + { + return this.ExecutablePath.ToLower().GetHashCode(); + } + } +} diff --git a/CursorAreaLock/HotkeyBar.cs b/CursorAreaLock/HotkeyBar.cs new file mode 100644 index 0000000..e5a74c1 --- /dev/null +++ b/CursorAreaLock/HotkeyBar.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.ConsoleExtentions +{ + public class HotkeyBar + { + private List _items = new List(); + public List Items { get { return _items; } } + + private List positionsFilled = new List(); + + public ConsoleColor TriggerKeyForegroundColor { get; set; } + public ConsoleColor ItemNameForegroundColor { get; set; } + + public ConsoleColor TriggerKeyBackgroundColor { get; set; } + public ConsoleColor ItemNameBackgroundColor { get; set; } + + public bool HotkeyNameSeperatorVisible { get; set; } + public string HotkeyNameSeperator { get; set; } + public int Position { get; set; } + public int GridWidth { get; set; } + //public bool Debug { get; set; } + //private bool debug_colortoggled; + + public HotkeyBar() : this(0) { } + public HotkeyBar(int gridSizeX) + { + this.TriggerKeyForegroundColor = ConsoleColor.Yellow; + this.ItemNameForegroundColor = ConsoleEx.DefaultForegroundColor; + + this.TriggerKeyBackgroundColor = ConsoleEx.DefaultBackgroundColor; + this.ItemNameBackgroundColor = ConsoleEx.DefaultBackgroundColor; + + this.HotkeyNameSeperator = ": "; + this.HotkeyNameSeperatorVisible = true; + this.Position = 3; + this.GridWidth = gridSizeX; + } + + /// + /// Checks if any key has been pressed and triggers every matching hotkey-item, if any + /// + /// True if a key was pressed and at least one matching trigger has been found, otherwise False + public bool Process() + { + if (Console.KeyAvailable) + return TriggerHotkeyEvents(Console.ReadKey(true)); + + return false; + } + + private bool TriggerHotkeyEvents(ConsoleKeyInfo input) + { + bool triggermatch = false; + + foreach (HotkeyBarItem item in Items) + { + //Trigger the EventHandler for each matching hotkey ... + if (item.Enabled && item.TriggerKey == input.Key) + { + triggermatch = true; + item.EventHandler(item); + } + } + return triggermatch; + } + + + /// + /// Prints a message instead of the hotkey bar + /// + /// The message to be printed + public void Print(string message, ConsoleColor color) + { + int x = Console.CursorLeft; + int y = Console.CursorTop; + + Console.SetCursorPosition(0, Console.WindowHeight - Position); + ConsoleEx.WriteLine(message, color); + Console.SetCursorPosition(x, y); + } + + /// + /// Prints the hotkey bar + /// + public void Print() + { + int x = Console.CursorLeft; + int y = Console.CursorTop; + + Console.SetCursorPosition(0, Console.WindowHeight - Position); + positionsFilled.Clear(); + + int lastNoGridCursorLeft = 0; + int lastGridCursorLeft = 0; + + //draw all available hotkeys ... + foreach (HotkeyBarItem item in Items) + { + if (item.GridPosition >= 0) + { + int itemPos = Console.WindowWidth / GridWidth * item.GridPosition; + int length = itemPos - Console.CursorLeft; + Console.Write("".PadLeft(length)); + } + else + { + Console.SetCursorPosition(lastNoGridCursorLeft, Console.WindowHeight - Position); + } + + DrawItem(item); + + if (item.GridPosition == -1) + lastNoGridCursorLeft = Console.CursorLeft; + else + lastGridCursorLeft = Console.CursorLeft; + } + int lastPos = lastGridCursorLeft > lastNoGridCursorLeft ? lastGridCursorLeft : lastNoGridCursorLeft; + Console.SetCursorPosition(lastPos, Console.WindowHeight - Position); + Console.Write("".PadLeft(Console.WindowWidth - lastPos)); + + //make sure only thes hotkeys are visible wich have the visiblity option set to true ... + //for (int i = 0; i < GridWidth; i++) + //{ + // //there is already something drawn at this position ... + // if (positionsFilled.Contains(i)) continue; + + // //draw an empty fake hotkey to override previos hotkeys ... + // HotkeyBarItem tmpItem = new HotkeyBarItem(ConsoleKey.A, "", "", null, i); + // tmpItem.Enabled = false; + // tmpItem.Visible = true; + // DrawItem(tmpItem); + //} + + Console.SetCursorPosition(x, y); + } + + private void DrawItem(HotkeyBarItem item) + { + if (!item.Visible) return; + + if (item.GridPosition >= 0) + { + int itemPos = Console.WindowWidth / GridWidth * item.GridPosition; + Console.SetCursorPosition(itemPos, Console.WindowHeight - Position); + } + + string key = String.Format("{0, 5}", " " + item.TriggerKeyDisplayName); + string value = item.ItemName; + if (GridWidth > 0 && item.GridPosition >= 0) + { + int cellwidth = (Console.WindowWidth - 1) / GridWidth; + value = String.Format("{0, " + (cellwidth - 5 - HotkeyNameSeperator.Length) * -1 + "}", value); + } + + ConsoleColor backColor = Console.BackgroundColor; + ConsoleColor nameColor = ItemNameForegroundColor; + + if (item.Enabled) + { + if (value.Equals("")) + ConsoleEx.Write(item.TriggerKeyDisplayName, TriggerKeyForegroundColor); + else + ConsoleEx.WriteKeyValue(key, value, nameColor, TriggerKeyForegroundColor, HotkeyNameSeperator); + } + else + { + int textspace = key.Length + value.Length + HotkeyNameSeperator.Length; + Console.Write(String.Format("{0," + textspace + "}", "")); + } + + //positionsFilled.Add(item.GridPosition); + } + } + + public class HotkeyBarItem + { + public delegate void HotkeyEvent(HotkeyBarItem item); + public ConsoleKey TriggerKey { get; set; } + public string TriggerKeyDisplayName { get; set; } + public string ItemName { get; set; } + public HotkeyEvent EventHandler { get; set; } + public bool Visible { get; set; } + public bool Enabled { get; set; } + + private int _gridPosition; + public int GridPosition { get { return _gridPosition; } set { _gridPosition = value < -1 ? -1 : value; } } + + public HotkeyBarItem(ConsoleKey key, string name, HotkeyEvent handler) : this(key, key.ToString(), name, handler) { } + public HotkeyBarItem(ConsoleKey key, string name, HotkeyEvent handler, int gridPosition) : this(key, key.ToString(), name, handler, gridPosition) { } + public HotkeyBarItem(ConsoleKey key, string keytext, string name, HotkeyEvent handler) : this(key, keytext, name, handler, -1) { } + public HotkeyBarItem(ConsoleKey key, string keytext, string name, HotkeyEvent handler, int gridPosition) + { + this.TriggerKey = key; + this.TriggerKeyDisplayName = keytext; + this.ItemName = name; + this.EventHandler = handler; + this.Enabled = true; + this.Visible = true; + this.GridPosition = gridPosition; + } + + public void Show() + { + this.Enabled = true; + this.Visible = true; + } + + public void Hide() + { + this.Enabled = false; + this.Visible = false; + } + } +} diff --git a/CursorAreaLock/ItemList.cs b/CursorAreaLock/ItemList.cs new file mode 100644 index 0000000..c83fd99 --- /dev/null +++ b/CursorAreaLock/ItemList.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.ConsoleExtentions +{ + class ItemList + { + private List _items = new List(); + public List Items { get { return _items; } } + + public ConsoleColor ItemForegroundColor { get; set; } + public ConsoleColor SelectionForegroundColor { get; set; } + public ConsoleColor ItemBackgroundColor { get; set; } + public ConsoleColor SelectionBackgroundColor { get; set; } + + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } + + private int vStartIndex; + private int vEndIndex; + + private int _selectionIndex; + public int SelectionIndex + { + get { return _selectionIndex; } + set + { + _selectionIndex = value > Items.Count - 1 ? Items.Count - 1 + : value < 0 ? 0 : value; + } + } + + public ItemList() + { + ItemForegroundColor = ConsoleEx.DefaultForegroundColor; + ItemBackgroundColor = ConsoleEx.DefaultBackgroundColor; + SelectionForegroundColor = ConsoleColor.Black; + SelectionBackgroundColor = ConsoleColor.Yellow; + } + + public void Print() + { + if (Items.Count == 0) { + Items.Add("(empty)"); + } + + int _x = Console.CursorLeft; + int _y = Console.CursorTop; + ConsoleColor bColor = Console.BackgroundColor; + ConsoleColor fColor = Console.ForegroundColor; + + vEndIndex = vStartIndex + Height - 1; + + if (SelectionIndex >= Items.Count) + SelectionIndex = Items.Count - 1; + + if (SelectionIndex > vEndIndex) + { + vEndIndex = SelectionIndex; + vStartIndex = vEndIndex - (Height - 1); + } + + if (SelectionIndex < vStartIndex) + { + vStartIndex = SelectionIndex; + vEndIndex = vStartIndex + Height - 1; + } + + for (int index = vStartIndex; index <= vEndIndex; index++) + { + Console.SetCursorPosition(X, Y + index - vStartIndex); + Console.BackgroundColor = ItemBackgroundColor; + Console.ForegroundColor = ItemForegroundColor; + + if (index >= Items.Count) + { + Console.Write("".PadRight(Width)); + continue; + } + + //is selected ... + if (index == _selectionIndex) + { + Console.BackgroundColor = SelectionBackgroundColor; + Console.ForegroundColor = SelectionForegroundColor; + } + + string itemDisplayText = String.Format(" {0, " + (Width - 3) * -1 + "} ", ConsoleEx.CutText(Items[index], Width - 2)); + Console.Write(itemDisplayText); + } + + //Console.SetCursorPosition(_x, _y); + Console.BackgroundColor = bColor; + Console.ForegroundColor = fColor; + } + } +} diff --git a/CursorAreaLock/Kernel32.cs b/CursorAreaLock/Kernel32.cs new file mode 100644 index 0000000..f735b48 --- /dev/null +++ b/CursorAreaLock/Kernel32.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.WinAPI +{ + public static class Kernel32 + { + public delegate bool ConsoleCloseEventHandler(CtrlType sig); + + [DllImport("kernel32")] + public static extern IntPtr GetModuleHandle(string lpModuleName); + + [DllImport("kernel32")] + public static extern bool SetConsoleCtrlHandler(ConsoleCloseEventHandler handler, bool add); + + public enum CtrlType + { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6 + } + } +} diff --git a/CursorAreaLock/Program.cs b/CursorAreaLock/Program.cs new file mode 100644 index 0000000..934b04a --- /dev/null +++ b/CursorAreaLock/Program.cs @@ -0,0 +1,476 @@ + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Diagnostics; +using System.IO; +using System.Threading; +using Microsoft.WinAPI; +using System.ConsoleExtentions; + +namespace CursorAreaLock +{ + class Program + { + /* + * CursorAreaLock v2.0 + * + * --System requirements-- + * + * OS: Windows 2000 Professional or greater + * + * Version: 2.0 + * * [Code cleanup] + * * ADD: Handle Alt+Tab + * * ADD: Exe list + * + * Version: 1.1 + * * FIX: cursor visible + * * FIX: handle console window resize + */ + + private static Program app; + + static void Main(string[] args) + { + app = new Program(); + app.Start(args); + } + + public string Name { get; private set; } + public string Version { get; private set; } + public string Copyright { get; private set; } + + private Kernel32.ConsoleCloseEventHandler handler; + + private bool exitRequested; + private bool cleanedUp; + private bool mainLoopRunning = true; + private int exitCode = 0; + + private int consoleWindowWidth = Console.WindowWidth; + private int consoleWindowHeight = Console.WindowHeight; + + private Service service = new Service(); + + private ConsolePage page; + private ConsolePage nextPage; + + private ConsolePage mainPage; + private ConsolePage optionsPage; + private ConsolePage addExePage; + + //data holder for Executable list ... + private ItemList exeViewList = new ItemList(); + + public Program() + { + Name = "CursorAreaLock"; + Version = "2.0"; + Copyright = "(c) MrX13415 2014"; + } + + public void Exit() + { + exitRequested = true; + } + + private void ProcessArgs(string[] args) + { + + } + + private void Initialize() + { + //setup exit event handler ... + handler += new Kernel32.ConsoleCloseEventHandler(ConsoleCloseEvent); + Kernel32.SetConsoleCtrlHandler(handler, true); + + //make sure we are using default colors ... + ConsoleEx.ResetColors(); + + //set console settings ... + Console.Title = String.Format("{0} v{1}", this.Name, this.Version); + Console.CursorVisible = false; + + //init. console pages ... + InitPages(); + + //set default page; + page = mainPage; + + //load service options file ... + service.LoadFromDisk(); + } + + private void InitPages(){ + + //** MAIN PAGE *********************************** + + HotkeyBarItem startStopKey = new HotkeyBarItem(ConsoleKey.F4, "Start / Stop", HotKeyEventHandler_StateToggle); + HotkeyBarItem optinsKey = new HotkeyBarItem(ConsoleKey.F6, "Options", HotKeyEventHandler_Options); + HotkeyBarItem exitKey = new HotkeyBarItem(ConsoleKey.F10, "Exit", HotKeyEventHandler_Exit, 3); + + HotkeyBar mainBar = new HotkeyBar(4); + mainBar.Items.Add(startStopKey); + mainBar.Items.Add(optinsKey); + mainBar.Items.Add(exitKey); + + mainPage = new ConsolePage(MainPagePrintHandler, service, mainBar); + + //** OPTIONS PAGE ******************************** + + HotkeyBarItem escKey1 = new HotkeyBarItem(ConsoleKey.Escape, "ESC", "Back", HotKeyEventHandler_OptionsCancel); + HotkeyBarItem addKey = new HotkeyBarItem(ConsoleKey.Insert, "INS", "Add", HotKeyEventHandler_ListAdd); + HotkeyBarItem removeKey = new HotkeyBarItem(ConsoleKey.Delete, "DEL", "Remove", HotKeyEventHandler_ListRem); + HotkeyBarItem upKey = new HotkeyBarItem(ConsoleKey.UpArrow, " ↑", "", HotKeyEventHandler_ListUp); + HotkeyBarItem downKey = new HotkeyBarItem(ConsoleKey.DownArrow, " ↓", "", HotKeyEventHandler_ListDown); + HotkeyBarItem homeKey = new HotkeyBarItem(ConsoleKey.Home, "HOME", "", HotKeyEventHandler_ListStart); + homeKey.Visible = false; + HotkeyBarItem endKey = new HotkeyBarItem(ConsoleKey.End, "END", "", HotKeyEventHandler_ListEnd); + endKey.Visible = false; + + HotkeyBar optionsBar = new HotkeyBar(4); + optionsBar.Items.Add(escKey1); + optionsBar.Items.Add(addKey); + optionsBar.Items.Add(removeKey); + optionsBar.Items.Add(exitKey); + optionsBar.Items.Add(upKey); + optionsBar.Items.Add(downKey); + optionsBar.Items.Add(homeKey); + optionsBar.Items.Add(endKey); + + optionsPage = new ConsolePage(OptionsPagePrintHandler, service, optionsBar); + + //** ADD EXECUTABLE PAGE ************************* + + HotkeyBarItem escKey2 = new HotkeyBarItem(ConsoleKey.Escape, "ESC", "Cancel", HotKeyEventHandler_CaptureModeCancel); + HotkeyBarItem okKey = new HotkeyBarItem(ConsoleKey.Enter, " ENTER","Add Executable", HotKeyEventHandler_CaptureModeOK); + + HotkeyBar addExeBar = new HotkeyBar(4); + addExeBar.Items.Add(escKey2); + addExeBar.Items.Add(okKey); + addExeBar.Items.Add(exitKey); + + addExePage = new ConsolePage(AddExePagePrintHandler, service, addExeBar); + } + + private bool SwitchPage(ConsolePage _page) + { + if (nextPage != null) return false; + nextPage = _page; + return true; + } + + public void Start(string[] args) + { + //process cmd args if any ... + ProcessArgs(args); + + //do basic stuff and setup things for start ... + Initialize(); + + //start and initialize the main loop + //will block here until Application exit ... + StartMainLoop(); + + //*** The Main Loop has been stoped *** + + //save service options file ... + service.SaveToDisk(); + + //Wait a bit before exit ... + Thread.Sleep(100); + + //Exit now ... + Environment.Exit(exitCode); + } + + private void StartMainLoop() + { + //*** MAIN LOOP [START] ********************************************** + while (mainLoopRunning) + { + //Wait a bit ... + Thread.Sleep(10); + + //prepare for exit ... + if (exitRequested) CleanUp(); + + //detact console window size changhes ... + CheckConsoleWindowSize(); + + //make sure the page content will fit ... + if (ConsoleContentSizeMatching()) + { + //PrepareConsole console for page redprint ... + PrepareConsole(); + + //*** SERVICE ************************************************ + service.Update(); + service.ProcessService(); + + //*** PAGES ************************************************** + page.Print(); + + //print the current available hotkeys, print "Exit" instead, if exit was requested ... + if (exitRequested) + page.Hotkeys.Print(" Exiting ...", ConsoleColor.Yellow); + else + page.Hotkeys.Print(); + } + + //handle the current available hotkeys + if (!exitRequested) + page.Hotkeys.Process(); + + //switch safely to an other console page ... + if (nextPage != null) + { + page = nextPage; + nextPage = null; + Console.Clear(); + } + + //exit main loop ... + if (exitRequested && cleanedUp) mainLoopRunning = false; + } + //*** MAIN LOOP [END] ************************************************ + } + + private void CleanUp() + { + service.Active = false; + service.CleanUp(); + cleanedUp = true; + } + + public void PrepareConsole() + { + Console.SetCursorPosition(0, 0); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write(String.Format("\n {0} ", this.Name)); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(String.Format("v{0} {1}\n\n\n", this.Version, this.Copyright)); + ConsoleEx.ResetColors(); + } + + private bool ConsoleContentSizeMatching() + { + //TODO: recalc min size ... + + //Check if the console window is big enough ... + if (Console.WindowWidth < 56 || Console.WindowHeight <= 18) + { + Console.SetCursorPosition(0, 2); + ConsoleEx.WriteLineCenter("- Console window to small -", ConsoleColor.Yellow); + return false; + } + + return true; + } + + private void CheckConsoleWindowSize() + { + //Console size has changed ... ? + if (Console.WindowWidth != consoleWindowWidth || Console.WindowHeight != consoleWindowHeight) + { + //update the new size ... + consoleWindowWidth = Console.WindowWidth; + consoleWindowHeight = Console.WindowHeight; + //everything has to be reprinted ... + Console.Clear(); + } + } + + //*** EVENT HANDLERS [START] ********************************************* + + private void HotKeyEventHandler_Exit(HotkeyBarItem item) + { + app.Exit(); + } + + private static bool ConsoleCloseEvent(Kernel32.CtrlType sig) + { + app.Exit(); + return true; + } + + //*** MAIN PAGE ********** + + private void MainPagePrintHandler(Service data) + { + string exeName = data.CurrentExecutable.DisplayName; + + if (data.CurrentExecutable.Equals(data.ServiceExecutable)) + exeName = "(this)"; + + Console.SetCursorPosition(0, 6); + + ConsoleEx.WriteKeyValueLine(" State", service.Active ? "Active" : "Stoped", service.Active ? ConsoleColor.Green : ConsoleColor.Red); + ConsoleEx.WriteLine(); + + ConsoleEx.WriteKeyValueLine(" Executable", exeName, ConsoleColor.White); + ConsoleEx.WriteLine(); + + ConsoleEx.WriteKeyValueLine(" Cursor Lock", service.IsLockActive ? "Active" : "Inactive", service.IsLockActive ? ConsoleColor.Yellow : ConsoleColor.White); + ConsoleEx.WriteLine(); + + ConsoleEx.WriteKeyValueLine(" Cursor", String.Format("{0,5}, {1,5}", service.CursorPosition.X, service.CursorPosition.Y), ConsoleColor.White); + ConsoleEx.WriteLine(); + + if (data.IsCurrentExecutableMatching) + { + ConsoleEx.WriteKeyValueLine(" Bounds", + String.Format("{0,5}, {1,5}, {2,5}, {3,5}", + service.CurrentWindowBounds.Left, + service.CurrentWindowBounds.Top, + service.CurrentWindowBounds.Right, + service.CurrentWindowBounds.Bottom), + ConsoleColor.White); + ConsoleEx.WriteLine(); + } + else + { + ConsoleEx.WriteLine(); + } + + } + + private void HotKeyEventHandler_StateToggle(HotkeyBarItem item) + { + service.Active = !service.Active; + } + + private void HotKeyEventHandler_Options(HotkeyBarItem item) + { + SwitchPage(optionsPage); + } + + //*** OPTIONS PAGE ******* + + private void OptionsPagePrintHandler(Service data) + { + Console.SetCursorPosition(10, 5); + + Console.WriteLine("Target Executables: "); + ConsoleEx.WriteLine(); + + exeViewList.Items.Clear(); + exeViewList.X = 10; + exeViewList.Y = Console.CursorTop; + exeViewList.Width = Console.WindowWidth - (exeViewList.X * 2); + exeViewList.Height = Console.WindowHeight - exeViewList.Y - 7; + + /////DEBUG///// + //if (service.Executables.Count == 0) + //{ + // for (int i = 0; i < 10000; i++) + // { + // service.Executables.Add(new Executable("exe.displayname_" + i + ".exe", "#" + i + " [exe.displayname]")); + // } + //} + /////DEBUG///// + + foreach (Executable exe in service.Executables) + { + exeViewList.Items.Add(exe.DisplayName); + } + + exeViewList.Print(); + + Console.SetCursorPosition(0, Console.WindowHeight - 6); + if (service.Executables.Count > 0) + { + ConsoleEx.WriteLine(" " + ConsoleEx.CutText(service.Executables[exeViewList.SelectionIndex].ExecutablePath, exeViewList.Width), ConsoleColor.White); + } + else + { + ConsoleEx.WriteLine(); + } + } + + private void HotKeyEventHandler_OptionsCancel(HotkeyBarItem item) + { + SwitchPage(mainPage); + } + + private void HotKeyEventHandler_ListAdd(HotkeyBarItem item) + { + SwitchPage(addExePage); + service.SetCaptureMode(); + } + + private void HotKeyEventHandler_ListRem(HotkeyBarItem item) + { + if (service.Executables.Count > 0) + service.Executables.RemoveAt(exeViewList.SelectionIndex); + } + private void HotKeyEventHandler_ListUp(HotkeyBarItem item) + { + exeViewList.SelectionIndex -= 1; + } + private void HotKeyEventHandler_ListDown(HotkeyBarItem item) + { + exeViewList.SelectionIndex += 1; + } + private void HotKeyEventHandler_ListStart(HotkeyBarItem item) + { + exeViewList.SelectionIndex = 0; + } + private void HotKeyEventHandler_ListEnd(HotkeyBarItem item) + { + exeViewList.SelectionIndex = exeViewList.Items.Count - 1; + } + + //*** ADD EXE PAGE ******* + + private void AddExePagePrintHandler(Service data) + { + string exeName = "(none)"; + string windowTitle = ""; + + if (data.CapturedExecutableAvailable) + { + exeName = data.LastCapturedExecutable.DisplayName; + windowTitle = data.GetWindowTitle(data.LastCapturedWindowHandle); + } + + if (windowTitle.Equals("")) + windowTitle = "(none)"; + else + windowTitle = ConsoleEx.CutText(windowTitle, Console.WindowWidth - 20); + + Console.SetCursorPosition(0, 6); + + ConsoleEx.WriteKeyValueLine(" Executable", exeName, ConsoleColor.White); + ConsoleEx.WriteLine(); + + ConsoleEx.WriteKeyValueLine(" Window title", windowTitle, ConsoleColor.White); + ConsoleEx.WriteLine(); + + ConsoleEx.WriteLine(); + ConsoleEx.WriteLine(); + ConsoleEx.WriteLineCenter("- Switch to the program, which should be added to the list -", ConsoleColor.Yellow); + ConsoleEx.WriteLine(); + ConsoleEx.WriteLine(); + ConsoleEx.WriteLine(); + } + + private void HotKeyEventHandler_CaptureModeOK(HotkeyBarItem item) + { + service.AddCapturedExecutable(); + SwitchPage(optionsPage); + } + + private void HotKeyEventHandler_CaptureModeCancel(HotkeyBarItem item) + { + SwitchPage(optionsPage); + service.SetNormalMode(); + } + + //*** EVENT HANDLERS [END] *********************************************** + + } +} diff --git a/CursorAreaLock/Properties/AssemblyInfo.cs b/CursorAreaLock/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cd2c509 --- /dev/null +++ b/CursorAreaLock/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Resources; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("CursorAreaLock")] +[assembly: AssemblyDescription("A utility to lock the mouse cursor to the bounds of a program window")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CursorAreaLock")] +[assembly: AssemblyCopyright("Creative Commons 3.0 (CC BY-NC-SA 3.0)")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("02301575-d65b-45eb-99ab-0ce824652132")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] +[assembly: NeutralResourcesLanguageAttribute("en")] diff --git a/CursorAreaLock/Properties/app.manifest b/CursorAreaLock/Properties/app.manifest new file mode 100644 index 0000000..e5ac031 --- /dev/null +++ b/CursorAreaLock/Properties/app.manifest @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CursorAreaLock/README.md b/CursorAreaLock/README.md new file mode 100644 index 0000000..a269f5e --- /dev/null +++ b/CursorAreaLock/README.md @@ -0,0 +1,19 @@ +CursorAreaLock +============== + +A utility to lock the mouse cursor to the bounds of a program window + +**Author:** +MrX13415 + +**Version:** +2.0 + +**License:** +CreativeCommons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0) +[More Informations](http://creativecommons.org/licenses/by-nc-sa/3.0/ "About: CC BY-NC-SA 3.0") +##Changelog + +#####Version: 2.0 + + * Initial git release \ No newline at end of file diff --git a/CursorAreaLock/Service.cs b/CursorAreaLock/Service.cs new file mode 100644 index 0000000..7d298ac --- /dev/null +++ b/CursorAreaLock/Service.cs @@ -0,0 +1,300 @@ +using Microsoft.WinAPI; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ConsoleExtentions; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace CursorAreaLock +{ + class Service + { + public enum ServiceMode{ + NormalMode, CaptureMode + } + + private const string saveFile = "options.dat"; + + private List _executables = new List(); + public List Executables { get { return _executables; } } + + public bool IsCurrentExecutableMatching { get { return Executables.Contains(CurrentExecutable); } } + + public Executable ServiceExecutable { get; private set; } + public Executable CurrentExecutable { get; private set; } + public Executable LastCapturedExecutable { get; private set; } + public IntPtr LastCapturedWindowHandle { get; private set; } + public IntPtr CurrentWindowHandle { get; private set; } + public ServiceMode Mode { get; set; } + + private IntPtr _altTabWindowHandle; + + private bool _serviceState; + private bool _lockState; + private bool _clipState; + private bool keys_AltTab; + + + private User32.POINT _cursorPos; + private User32.RECT _defaultCursorBounds; + private User32.RECT _currentWindowBounds; + + private IntPtr m_hhook; + private User32.WinEventDelegate _procDelegate; + + public User32.POINT CursorPosition { get { return _cursorPos; } } + public User32.RECT DefaultCursorBounds { get { return _defaultCursorBounds; } } + public User32.RECT CurrentWindowBounds { get { return _currentWindowBounds; } } + + public bool Active { get { return _serviceState; } set { _serviceState = value; } } + public bool IsLockActive { get { return _lockState; } } + public bool IsClipApplied { get { return _clipState; } } + public bool CapturedExecutableAvailable { get { return LastCapturedExecutable != null; } } + + public Service(){ + //obtain the executable name which runs this service ... + ServiceExecutable = new Executable(Process.GetCurrentProcess().MainModule.FileName); + + //set default running mode ... + Mode = ServiceMode.NormalMode; + + //init. window event hook ... + InitHook(); + } + + private void InitHook(){ + //make sure the event delegate isn't getting lost ... + _procDelegate = new User32.WinEventDelegate(WinEventProc); + + m_hhook = User32.SetWinEventHook( + User32.EVENT_SYSTEM_SWITCHSTART, // eventMin + User32.EVENT_SYSTEM_SWITCHEND, // eventMax + IntPtr.Zero, // hmodWinEventProc + _procDelegate, // lpfnWinEventProc + 0, // idProcess + 0, // idThread + User32.WINEVENT_OUTOFCONTEXT); + } + + private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + { + if (eventType == User32.EVENT_SYSTEM_SWITCHSTART) + { + keys_AltTab = true; + } + + if (eventType == User32.EVENT_SYSTEM_SWITCHEND) + { + keys_AltTab = false; + } + } + + public void Update() + { + try + { + bool asd = keys_AltTab; + + //event message loop ... + User32.MSG _eventMessage = new User32.MSG(); + while (User32.PeekMessage(ref _eventMessage, IntPtr.Zero, 0, 0, User32.PM_REMOVE)) + { + User32.TranslateMessage(ref _eventMessage); + User32.DispatchMessage(ref _eventMessage); + } + + //set current curosr pos ... + User32.GetCursorPos(out _cursorPos); + + //get current window with focus ... + CurrentWindowHandle = User32.GetForegroundWindow(); + if (CurrentWindowHandle == null) return; + + if (keys_AltTab) _altTabWindowHandle = CurrentWindowHandle; + + Process process = GetProcessByHandle(CurrentWindowHandle); + if (process == null) return; + + ProcessModule pModule = process.MainModule; + if (pModule == null) return; + + //set current exe ... + CurrentExecutable = new Executable(pModule.FileName); + }catch { } + } + + public void ProcessService(){ + if (CurrentWindowHandle == null || + CurrentExecutable == null) return; + + if (_altTabWindowHandle == CurrentWindowHandle) + return; + + switch (Mode) + { + case ServiceMode.NormalMode: + DoNormalMode(); + break; + case ServiceMode.CaptureMode: + DoCaptureMode(); + break; + } + + //set lock if needed ... + if (_lockState) + { + //backup current cursor bounds first ... + if (!_clipState) User32.GetClipCursor(out _defaultCursorBounds); + + //set new cursor bounds ... + User32.ClipCursor(ref _currentWindowBounds); + _clipState = true; + } + else if (_clipState) + { + //restore default cursor bounds ... + User32.ClipCursor(ref _defaultCursorBounds); + _clipState = false; + } + } + + public void SetNormalMode() + { + Mode = ServiceMode.NormalMode; + } + + public void SetCaptureMode() + { + Mode = ServiceMode.CaptureMode; + } + + public void AddCapturedExecutable() + { + if (LastCapturedExecutable != null) + Executables.Add(LastCapturedExecutable); + LastCapturedExecutable = null; + //go back to normal mode ... + SetNormalMode(); + } + + private void DoCaptureMode() + { + if (!CurrentExecutable.Equals(ServiceExecutable) && !keys_AltTab) + { + //mark captured executable for adding ... + LastCapturedExecutable = CurrentExecutable; + LastCapturedWindowHandle = CurrentWindowHandle; + } + } + + private void DoNormalMode() + { + if (IsCurrentExecutableMatching) + { + //reacalc current window bounds ... + User32.GetWindowRect(CurrentWindowHandle, out _currentWindowBounds); + + //activate cursor lock if the service is active ... + _lockState = _serviceState; + } + else _lockState = false; + } + + public string GetCurrentWindowTitle() + { + if (CurrentWindowHandle == null) return ""; + StringBuilder sb = new StringBuilder(1024); + User32.GetWindowText(CurrentWindowHandle, sb, sb.MaxCapacity); + return sb.ToString(); + } + + public string GetWindowTitle(IntPtr windowHandle) + { + if (windowHandle == null) return ""; + StringBuilder sb = new StringBuilder(1024); + User32.GetWindowText(windowHandle, sb, sb.MaxCapacity); + return sb.ToString(); + } + + public bool SaveToDisk() + { + if (File.Exists(saveFile)) + File.Delete(saveFile); + + System.IO.StreamWriter file = null; + try + { + file = new System.IO.StreamWriter(saveFile); + + foreach (Executable exe in _executables) + { + file.WriteLine(exe.ExecutablePath); + } + } + catch (Exception) + { + return false; + } + finally + { + if (file != null) file.Close(); + } + + return true; + } + + public bool LoadFromDisk() + { + if (!File.Exists(saveFile)) return false; + + System.IO.StreamReader file = null; + try + { + file = new System.IO.StreamReader(saveFile); + + string line; + + while ((line = file.ReadLine()) != null) + { + Executables.Add(new Executable(line)); + } + } + catch (Exception) + { + return false; + } + finally + { + if (file != null) file.Close(); + } + + return Executables.Count > 0; + } + + public void CleanUp() + { + if (_clipState) + { + User32.ClipCursor(ref _defaultCursorBounds); + _clipState = false; + } + + User32.UnhookWinEvent(m_hhook); + } + + private static Process GetProcessByHandle(IntPtr hwnd) + { + try + { + uint processID; + User32.GetWindowThreadProcessId(hwnd, out processID); + return Process.GetProcessById((int)processID); + } + catch { return null; } + } + } +} diff --git a/CursorAreaLock/User32.cs b/CursorAreaLock/User32.cs new file mode 100644 index 0000000..de9e72f --- /dev/null +++ b/CursorAreaLock/User32.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.WinAPI +{ + public static class User32 + { + public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); + + public const uint WINEVENT_OUTOFCONTEXT = 0x0000; + public const uint EVENT_SYSTEM_FOREGROUND = 0x0003; + public const uint EVENT_SYSTEM_SWITCHEND = 0x0015; + public const uint EVENT_SYSTEM_SWITCHSTART = 0x0014; + + //Messages are not removed from the queue after processing by PeekMessage. + public const uint PM_NOREMOVE = 0x0000; + + //Messages are removed from the queue after processing by PeekMessage. + public const uint PM_REMOVE = 0x0001; + + // Prevents the system from releasing any thread that is waiting for the caller to go idle (see WaitForInputIdle). + public const uint PM_NOYIELD = 0x0002; + + [DllImport("user32")] + public static extern bool PeekMessage(ref MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); + + [DllImport("user32")] + public static extern bool TranslateMessage(ref MSG lpMsg); + + [DllImport("user32")] + public static extern IntPtr DispatchMessage(ref MSG lpmsg); + + [DllImport("user32")] + public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); + + [DllImport("user32")] + public static extern bool UnhookWinEvent(IntPtr hWinEventHook); + + [DllImport("user32")] + public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32")] + public static extern Int32 GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32")] + public static extern IntPtr GetForegroundWindow(); + + [DllImport("user32")] + public static extern bool GetCursorPos(out POINT pt); + + [DllImport("user32")] + public static extern bool ClipCursor(ref RECT lpRect); + + [DllImport("user32")] + public static extern bool GetClipCursor(out RECT lpRect); + + [DllImport("user32")] + public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int X; + public int Y; + + public POINT(int x, int y) + { + this.X = x; + this.Y = y; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MSG + { + public IntPtr hwnd; + public UInt32 message; + public IntPtr wParam; + public IntPtr lParam; + public UInt32 time; + public POINT pt; + } + } +}