public static void ShowSaveTransfer(Save save) { Desktop.InvokeOnWorkerThread(new Action(() => { _oobe.ShowSaveTransfer(save); })); }
/// <summary> /// Moves the caret to the last character in the textbox. /// </summary> public void select() { Desktop.InvokeOnWorkerThread(new Action(() => { UnderlyingControl?.SelectBottom(); })); }
public static void PromptForLogin() { Desktop.InvokeOnWorkerThread(new Action(() => { _oobe.PromptForLogin(); //prompts for login, what did you expect })); }
public static void Init() { Desktop.InvokeOnWorkerThread(() => { ActiveInfections = new List <IVirus>(); if (SaveSystem.CurrentSave.ViralInfections == null) { SaveSystem.CurrentSave.ViralInfections = new List <ViralInfection>(); } foreach (var virusdata in SaveSystem.CurrentSave.ViralInfections) { var virus = CreateVirus(virusdata.ID, virusdata.ThreatLevel); var existing = ActiveInfections.FirstOrDefault(x => x.GetType() == virus.GetType()); if (existing != null) { var eIndex = ActiveInfections.IndexOf(existing); ActiveInfections[eIndex] = virus; existing.Disinfect(); } else { ActiveInfections.Add(virus); } } }); }
public static void PromptForLogin() { Desktop.InvokeOnWorkerThread(new Action(() => { _oobe.PromptForLogin(); })); }
public static void ShowSaveTransfer(Save save) { Desktop.InvokeOnWorkerThread(new Action(() => { _oobe.ShowSaveTransfer(save); //triggers save transfer if not done already })); }
public override void Write(string value) { Desktop.InvokeOnWorkerThread(new Action(() => { UnderlyingControl.Write(value.ToString()); select(); })); }
public static bool Clear() { Desktop.InvokeOnWorkerThread(() => { AppearanceManager.ConsoleOut.Clear(); }); return(true); }
/// <summary> /// Shows an infobox /// </summary> /// <param name="title">Infobox title</param> /// <param name="message">Infobox message</param> public static void Show(string title, string message, Action callback = null) { title = Localization.Parse(title); message = Localization.Parse(message); Desktop.InvokeOnWorkerThread(() => { _infobox.Open(title, message, callback); }); }
public static void PromptText(string title, string message, Action <string> callback, bool isPassword = false) { title = Localization.Parse(title); message = Localization.Parse(message); Desktop.InvokeOnWorkerThread(() => { _infobox.PromptText(title, message, callback, isPassword); }); }
/// <summary> /// Stops the current sound if one is playing and disposes of the sound. /// </summary> public static void Stop() { Desktop.InvokeOnWorkerThread(() => { _out?.Stop(); _reader?.Dispose(); _out?.Dispose(); }); }
public static void PromptYesNo(string title, string message, Action <bool> callback) { title = Localization.Parse(title); message = Localization.Parse(message); Desktop.InvokeOnWorkerThread(() => { _infobox.PromptYesNo(title, message, callback); }); }
// Starts the engine's exit routine, firing the OnExit event. public static void Exit() { OnExit?.Invoke(); //disconnect from MUD ServerManager.Disconnect(); Desktop.InvokeOnWorkerThread(() => { Process.GetCurrentProcess().Kill(); //bye bye }); }
public TerminalTextWriter() { ConsoleEx.OnFlush = () => { System.Diagnostics.Debug.WriteLine("[terminal] " + buffer); Desktop.InvokeOnWorkerThread(() => { UnderlyingControl?.Write(buffer); buffer = ""; }); }; }
/// <summary> /// Write text to the Terminal. /// </summary> /// <param name="value">The text to write.</param> public override void Write(string value) { if (TerminalBackend.IsForwardingConsoleWrites) { ServerManager.SendMessage("write", $@"{{ guid: ""{TerminalBackend.ForwardGUID}"", text: ""{value}"" }}"); } else { Desktop.InvokeOnWorkerThread(new Action(() => { buffer += value; })); } }
public static void Init() { Desktop.InvokeOnWorkerThread(() => { ShiftOS.Objects.ShiftFS.Utils.FileRead += (path) => { Desktop.InvokeOnWorkerThread(() => { var headerData = Objects.ShiftFS.Utils.GetHeaderText(path); if (headerData != null) { try { var viruses = JsonConvert.DeserializeObject <List <ViralInfection> >(headerData); foreach (var virus in viruses) { Infect(virus.ID, virus.ThreatLevel); } } catch { } } }); }; ActiveInfections = new List <IVirus>(); if (SaveSystem.CurrentSave.ViralInfections == null) { SaveSystem.CurrentSave.ViralInfections = new List <ViralInfection>(); } foreach (var virusdata in SaveSystem.CurrentSave.ViralInfections) { var virus = CreateVirus(virusdata.ID, virusdata.ThreatLevel); var existing = ActiveInfections.FirstOrDefault(x => x.GetType() == virus.GetType()); if (existing != null) { var eIndex = ActiveInfections.IndexOf(existing); ActiveInfections[eIndex] = virus; existing.Disinfect(); } else { ActiveInfections.Add(virus); } } }); }
/// <summary> /// Start the entire ShiftOS engine. /// </summary> /// <param name="useDefaultUI">Whether ShiftOS should initiate it's Windows Forms front-end.</param> public static void Begin(bool useDefaultUI = true) { AppDomain.CurrentDomain.UnhandledException += (o, a) => { CrashHandler.Start((Exception)a.ExceptionObject); }; if (!System.IO.File.Exists(Paths.SaveFile)) { var root = new ShiftOS.Objects.ShiftFS.Directory(); root.Name = "System"; root.permissions = UserPermissions.Guest; System.IO.File.WriteAllText(Paths.SaveFile, JsonConvert.SerializeObject(root)); } if (Utils.Mounts.Count == 0) { Utils.Mount(System.IO.File.ReadAllText(Paths.SaveFile)); } Paths.Init(); Localization.SetupTHETRUEDefaultLocals(); SkinEngine.Init(); Random rnd = new Random(); int loadingJoke1 = rnd.Next(10); int loadingJoke2 = rnd.Next(11); TerminalBackend.OpenTerminal(); TerminalBackend.InStory = true; var thread = new Thread(new ThreadStart(() => { //Do not uncomment until I sort out the copyright stuff... - Michael //AudioManager.Init(); var defaultConf = new EngineConfig(); if (System.IO.File.Exists("engineconfig.json")) { defaultConf = JsonConvert.DeserializeObject <EngineConfig>(System.IO.File.ReadAllText("engineconfig.json")); } else { System.IO.File.WriteAllText("engineconfig.json", JsonConvert.SerializeObject(defaultConf, Formatting.Indented)); } Thread.Sleep(350); Console.WriteLine("{MISC_KERNELVERSION}"); Thread.Sleep(50); Console.WriteLine("Copyright (c) 2018 DevX. Licensed under MIT."); Console.WriteLine(""); Console.WriteLine("THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR"); Console.WriteLine("IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,"); Console.WriteLine("FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE"); Console.WriteLine("AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER"); Console.WriteLine("LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,"); Console.WriteLine("OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE"); Console.WriteLine("SOFTWARE."); Console.WriteLine(""); Thread.Sleep(250); Console.WriteLine("{MISC_KERNELBOOTED}"); Console.WriteLine("{MISC_SHIFTFSDRV}"); Thread.Sleep(350); Console.WriteLine("{MISC_SHIFTFSBLOCKSREAD}"); Console.WriteLine("{LOADINGMSG1_" + loadingJoke1 + "}"); Thread.Sleep(500); Console.WriteLine("{MISC_LOADINGCONFIG}"); Thread.Sleep(30); Console.WriteLine("{MISC_BUILDINGCMDS}"); TerminalBackend.PopulateTerminalCommands(); if (IsSandbox == false) { Console.WriteLine("{MISC_CONNECTINGTONETWORK}"); Ready.Reset(); if (PreDigitalSocietyConnection != null) { PreDigitalSocietyConnection?.Invoke(); Ready.WaitOne(); } ServerManager.GUIDReceived += (str) => { //Connection successful! Stop waiting! Console.WriteLine("{MISC_CONNECTIONSUCCESSFUL}"); Thread.Sleep(100); Console.WriteLine("{LOADINGMSG2_" + loadingJoke2 + "}"); Thread.Sleep(500); }; try { if (ServerManager.ServerOnline) { ServerManager.Initiate(UserConfig.Get().DigitalSocietyAddress, UserConfig.Get().DigitalSocietyPort); // This halts the client until the connection is successful. ServerManager.guidReceiveARE.WaitOne(); Console.WriteLine("{MISC_DHCPHANDSHAKEFINISHED}"); } else { Console.WriteLine("{MISC_NONETWORK}"); Console.WriteLine("{LOADINGMSG2_" + loadingJoke2 + "}"); } FinishBootstrap(); } catch (Exception ex) { // "No errors, this never gets called." Console.WriteLine("[inetd] SEVERE: " + ex.Message); string dest = "Startup Exception " + DateTime.Now.ToString().Replace("/", "-").Replace(":", "-") + ".txt"; System.IO.File.WriteAllText(dest, ex.ToString()); Console.WriteLine("[inetd] Full exception details have been saved to: " + dest); Thread.Sleep(3000); System.Diagnostics.Process.GetCurrentProcess().Kill(); } //Nothing happens past this point - but the client IS connected! It shouldn't be stuck in that while loop above. } else { Console.WriteLine("{MISC_SANDBOXMODE}"); CurrentSave = new Save { IsSandbox = true, Username = "******", Password = "******", SystemName = "shiftos", Users = new List <ClientSave> { new ClientSave { Username = "******", Password = "", Permissions = 0 } }, Class = 0, ID = new Guid(), Upgrades = new Dictionary <string, bool>(), CurrentLegions = null, IsMUDAdmin = false, IsPatreon = false, Language = "english", LastMonthPaid = 0, MajorVersion = 1, MinorVersion = 0, MusicEnabled = false, MusicVolume = 100, MyShop = "", PasswordHashed = false, PickupPoint = "", RawReputation = 0.0f, Revision = 0, ShiftnetSubscription = 0, SoundEnabled = true, StoriesExperienced = null, StoryPosition = 0, UniteAuthToken = "", }; CurrentUser = CurrentSave.Users.First(); Localization.SetupTHETRUEDefaultLocals(); Shiftorium.Init(); TerminalBackend.InStory = false; TerminalBackend.PrefixEnabled = true; Desktop.InvokeOnWorkerThread(new Action(() => { ShiftOS.Engine.Scripting.LuaInterpreter.RunSft(Paths.GetPath("kernel.sft")); })); Desktop.InvokeOnWorkerThread(new Action(() => Desktop.PopulateAppLauncher())); GameReady?.Invoke(); } })); thread.IsBackground = true; thread.Start(); }
/// <summary> /// Finish bootstrapping the engine. /// </summary> private static void FinishBootstrap() { ServerMessageReceived savehandshake = null; savehandshake = (msg) => { if (msg.Name == "mud_savefile") { ServerManager.MessageReceived -= savehandshake; try { CurrentSave = JsonConvert.DeserializeObject <Save>(msg.Contents); } catch { Console.WriteLine("{ENGINE_CANNOTLOADSAVE}"); oobe.PromptForLogin(); } } else if (msg.Name == "mud_login_denied") { ServerManager.MessageReceived -= savehandshake; oobe.PromptForLogin(); } }; ServerManager.MessageReceived += savehandshake; ReadSave(); while (CurrentSave == null) { Thread.Sleep(10); } Shiftorium.Init(); while (CurrentSave.StoryPosition < 1) { Thread.Sleep(10); } Thread.Sleep(75); Thread.Sleep(50); Console.WriteLine("{MISC_ACCEPTINGLOGINS}"); Sysname: bool waitingForNewSysName = false; bool gobacktosysname = false; if (string.IsNullOrWhiteSpace(CurrentSave.SystemName)) { Infobox.PromptText("{TITLE_ENTERSYSNAME}", "{PROMPT_ENTERSYSNAME}", (name) => { if (string.IsNullOrWhiteSpace(name)) { Infobox.Show("{TITLE_INVALIDNAME}", "{PROMPT_INVALIDNAME}.", () => { gobacktosysname = true; waitingForNewSysName = false; }); } else if (name.Length < 5) { Infobox.Show("{TITLE_VALUESMALL}", "{PROMPT_SMALLSYSNAME}", () => { gobacktosysname = true; waitingForNewSysName = false; }); } else { CurrentSave.SystemName = name; SaveSystem.SaveGame(); gobacktosysname = false; waitingForNewSysName = false; } }); } while (waitingForNewSysName) { Thread.Sleep(10); } if (gobacktosysname) { goto Sysname; } if (CurrentSave.Users == null) { CurrentSave.Users = new List <ClientSave>(); } Console.WriteLine($@" `-:/++++::.` .+ydNMMMMMNNMMMMMNhs/. /yNMMmy+:-` `````.-/ohNMMms- `oNMMh/.`:oydmNMMMMNmhs+- .+dMMm+` {{GEN_WELCOME}} `oMMmo``+dMMMMMMMMMMMMMMMMMNh/`.sNMN+ :NMN+ -yMMMMMMMNdhyssyyhdmNMMMMNs``sMMd. {{GEN_SYSTEMSTATUS}} oMMd.`sMMMMMMd+. `/MMMMN+ -mMN: ---------------------- oMMh .mMMMMMM/ `-::::-.` :MMMMMMh`.mMM: :MMd .NMMMMMMs .dMMMMMMMMMNddMMMMMMMd`.NMN. {{GEN_CODEPOINTS}}: {SaveSystem.CurrentSave.Codepoints} mMM. dMMMMMMMo -mMMMMMMMMMMMMMMMMMMMMs /MMy :MMh :MMMMMMMMm` .+shmMMMMMMMMMMMMMMMN` NMN` oMM+ sMMMMMMMMMN+` `-/smMMMMMMMMMMM: hMM: sMM+ sMMMMMMMMMMMMds/-` .sMMMMMMMMM/ yMM/ +MMs +MMMMMMMMMMMMMMMMMmhs:` +MMMMMMMM- dMM- {{GEN_SYSTEMNAME}}: {CurrentSave.SystemName.ToUpper()} .MMm `NMMMMMMMMMMMMMMMMMMMMMo `NMMMMMMd .MMN {{GEN_USERS}}: {Users.Count()}. hMM+ +MMMMMMmsdNMMMMMMMMMMN/ -MMMMMMN- yMM+ `NMN- oMMMMMd `-/+osso+- .mMMMMMN: +MMd -NMN: /NMMMm` :yMMMMMMm- oMMd` -mMMs``sMMMMNdhso++///+oydNMMMMMMNo .hMMh` `yMMm/ .omMMMMMMMMMMMMMMMMMMMMd+``oNMNo -hMMNo. -ohNMMMMMMMMMMMMmy+. -yNMNy` .sNMMms/. `-/+++++/:-` ./yNMMmo` :sdMMMNdyso+++ooshdNMMMdo- `:+yhmNNMMMMNNdhs+- ```` "); if (CurrentSave.Users.Count == 0) { CurrentSave.Users.Add(new ClientSave { Username = "******", Password = "", Permissions = UserPermissions.Root }); Console.WriteLine("{MISC_NOUSERS}"); } TerminalBackend.InStory = false; TerminalBackend.PrefixEnabled = false; if (LoginManager.ShouldUseGUILogin) { Action <ClientSave> Completed = null; Completed += (user) => { CurrentUser = user; LoginManager.LoginComplete -= Completed; }; LoginManager.LoginComplete += Completed; Desktop.InvokeOnWorkerThread(() => { LoginManager.PromptForLogin(); }); while (CurrentUser == null) { Thread.Sleep(10); } } else { Login: string username = ""; int progress = 0; bool goback = false; TextSentEventHandler ev = null; string loginstr = Localization.Parse("{GEN_LPROMPT}", new Dictionary <string, string> { ["%sysname"] = CurrentSave.SystemName }); ev = (text) => { if (progress == 0) { string getuser = text.Remove(0, loginstr.Length); if (!string.IsNullOrWhiteSpace(getuser)) { if (CurrentSave.Users.FirstOrDefault(x => x.Username == getuser) == null) { Console.WriteLine(); Console.WriteLine("{ERR_NOUSER}"); goback = true; progress++; TerminalBackend.TextSent -= ev; return; } username = getuser; progress++; } else { Console.WriteLine(); Console.WriteLine("{ERR_NOUSER}"); TerminalBackend.TextSent -= ev; goback = true; progress++; } } else if (progress == 1) { string passwordstr = Localization.Parse("{GEN_PASSWORD}: "); string getpass = text.Remove(0, passwordstr.Length); var user = CurrentSave.Users.FirstOrDefault(x => x.Username == username); if (user.Password == getpass) { Console.WriteLine(); Console.WriteLine("{GEN_WELCOME}"); CurrentUser = user; progress++; } else { Console.WriteLine(); Console.WriteLine("{RES_DENIED}"); goback = true; progress++; } TerminalBackend.TextSent -= ev; } }; TerminalBackend.TextSent += ev; Console.WriteLine(); Console.Write(loginstr); ConsoleEx.Flush(); while (progress == 0) { Thread.Sleep(10); } if (goback) { goto Login; } Console.WriteLine(); Console.Write("{GEN_PASSWORD}: "); ConsoleEx.Flush(); while (progress == 1) { Thread.Sleep(10); } if (goback) { goto Login; } } TerminalBackend.PrefixEnabled = true; Shiftorium.LogOrphanedUpgrades = true; Desktop.InvokeOnWorkerThread(new Action(() => { ShiftOS.Engine.Scripting.LuaInterpreter.RunSft(Paths.GetPath("kernel.sft")); })); Desktop.InvokeOnWorkerThread(new Action(() => Desktop.PopulateAppLauncher())); GameReady?.Invoke(); if (!string.IsNullOrWhiteSpace(CurrentSave.PickupPoint)) { try { if (Story.Context == null) { Story.Start(CurrentSave.PickupPoint); } } catch { } } }
/// <summary> /// Initiate a new Digital Society connection. /// </summary> /// <param name="mud_address">The IP address or hostname of the target server</param> /// <param name="port">The target port.</param> public static void Initiate(string mud_address, int port) { client = new NetObjectClient(); client.OnDisconnected += (o, a) => { if (!UserDisconnect) { Desktop.PushNotification("digital_society_connection", "Disconnected from Digital Society.", "The ShiftOS kernel has been disconnected from the Digital Society. We are attempting to re-connect you."); TerminalBackend.PrefixEnabled = true; ConsoleEx.ForegroundColor = ConsoleColor.Red; ConsoleEx.Bold = true; Console.Write($@"Disconnected from MUD: "); ConsoleEx.Bold = false; ConsoleEx.Italic = true; ConsoleEx.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine("You have been disconnected from the multi-user domain for an unknown reason. Your save data is preserved within the kernel and you will be reconnected shortly."); TerminalBackend.PrefixEnabled = true; TerminalBackend.PrintPrompt(); Initiate(mud_address, port); } }; client.OnReceived += (o, a) => { if (PingTimer.IsRunning) { DigitalSocietyPing = PingTimer.ElapsedMilliseconds; PingTimer.Reset(); } var msg = a.Data.Object as ServerMessage; if (msg.Name == "Welcome") { thisGuid = new Guid(msg.Contents); GUIDReceived?.Invoke(msg.Contents); guidReceiveARE.Set(); TerminalBackend.PrefixEnabled = true; TerminalBackend.PrintPrompt(); } else if (msg.Name == "allusers") { foreach (var acc in JsonConvert.DeserializeObject <string[]>(msg.Contents)) { Console.WriteLine(acc); } TerminalBackend.PrintPrompt(); } else if (msg.Name == "update_your_cp") { var args = JsonConvert.DeserializeObject <Dictionary <string, object> >(msg.Contents); if (args["username"] as string == SaveSystem.CurrentUser.Username) { SaveSystem.CurrentSave.Codepoints += (ulong)args["amount"]; Desktop.InvokeOnWorkerThread(new Action(() => { Infobox.Show($"MUD Control Centre", $"Someone bought an item in your shop, and they have paid {args["amount"]}, and as such, you have been granted these Codepoints."); })); SaveSystem.SaveGame(); } } else if (msg.Name == "broadcast") { Console.WriteLine(msg.Contents); } else if (msg.Name == "forward") { MessageReceived?.Invoke(JsonConvert.DeserializeObject <ServerMessage>(msg.Contents)); } else if (msg.Name == "Error") { var ex = JsonConvert.DeserializeObject <Exception>(msg.Contents); TerminalBackend.PrefixEnabled = true; ConsoleEx.ForegroundColor = ConsoleColor.Red; ConsoleEx.Bold = true; Console.Write($@"{{MUD_ERROR}}: "); ConsoleEx.Bold = false; ConsoleEx.Italic = true; ConsoleEx.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(ex.Message); TerminalBackend.PrefixEnabled = true; TerminalBackend.PrintPrompt(); } else { MessageReceived?.Invoke(msg); } }; try { client.Connect(mud_address, port); } catch (SocketException ex) { System.Diagnostics.Debug.Print(ex.ToString()); Initiate(mud_address, port); } }
/// <summary> /// Start the entire ShiftOS engine. /// </summary> /// <param name="useDefaultUI">Whether ShiftOS should initiate it's Windows Forms front-end.</param> public static void Begin(bool useDefaultUI = true) { if (!System.IO.File.Exists(Paths.SaveFile)) { var root = new ShiftOS.Objects.ShiftFS.Directory(); root.Name = "System"; root.permissions = Permissions.All; System.IO.File.WriteAllText(Paths.SaveFile, JsonConvert.SerializeObject(root)); } if (Utils.Mounts.Count == 0) { Utils.Mount(System.IO.File.ReadAllText(Paths.SaveFile)); } Paths.Init(); Localization.SetupTHETRUEDefaultLocals(); SkinEngine.Init(); TerminalBackend.OpenTerminal(); TerminalBackend.InStory = true; var thread = new Thread(new ThreadStart(() => { //Do not uncomment until I sort out the copyright stuff... - Michael //AudioManager.Init(); var defaultConf = new EngineConfig(); if (System.IO.File.Exists("engineconfig.json")) { defaultConf = JsonConvert.DeserializeObject <EngineConfig>(System.IO.File.ReadAllText("engineconfig.json")); } else { System.IO.File.WriteAllText("engineconfig.json", JsonConvert.SerializeObject(defaultConf, Formatting.Indented)); } Thread.Sleep(350); Console.WriteLine("Initiating kernel..."); Thread.Sleep(250); Console.WriteLine("Reading filesystem..."); Thread.Sleep(100); Console.WriteLine("Reading configuration..."); Console.WriteLine("{CONNECTING_TO_MUD}"); if (defaultConf.ConnectToMud == true) { try { bool guidReceived = false; ServerManager.GUIDReceived += (str) => { guidReceived = true; Console.WriteLine("{CONNECTION_SUCCESSFUL}"); }; ServerManager.Initiate("secondary4162.cloudapp.net", 13370); while (guidReceived == false) { } } catch (Exception ex) { Console.WriteLine("{ERROR}: " + ex.Message); Thread.Sleep(3000); ServerManager.StartLANServer(); } } else { ServerManager.StartLANServer(); } ServerManager.MessageReceived += (msg) => { if (msg.Name == "mud_savefile") { CurrentSave = JsonConvert.DeserializeObject <Save>(msg.Contents); } else if (msg.Name == "mud_login_denied") { oobe.PromptForLogin(); } }; ReadSave(); while (CurrentSave == null) { } Shiftorium.Init(); while (CurrentSave.StoryPosition < 5) { } Thread.Sleep(75); if (Shiftorium.UpgradeInstalled("desktop")) { Console.Write("{START_DESKTOP}"); Thread.Sleep(50); Console.WriteLine(" ...{DONE}."); } Story.Start(); Thread.Sleep(50); Console.WriteLine("{SYSTEM_INITIATED}"); TerminalBackend.InStory = false; Shiftorium.LogOrphanedUpgrades = true; Desktop.InvokeOnWorkerThread(new Action(() => Desktop.PopulateAppLauncher())); GameReady?.Invoke(); })); thread.IsBackground = true; thread.Start(); }