diff --git a/Desktop.Unix/Assets/Gear.png b/Desktop.Unix/Assets/Gear.png new file mode 100644 index 00000000..4ef3c0bb Binary files /dev/null and b/Desktop.Unix/Assets/Gear.png differ diff --git a/Desktop.Unix/Services/Config.cs b/Desktop.Unix/Services/Config.cs index 105ae3fe..096a1ea1 100644 --- a/Desktop.Unix/Services/Config.cs +++ b/Desktop.Unix/Services/Config.cs @@ -11,9 +11,9 @@ namespace Remotely.Desktop.Unix.Services { public class Config { - private static string ConfigFolder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Remotely"); + public string Host { get; set; } = ""; private static string ConfigFile => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Remotely", "Config.json"); - + private static string ConfigFolder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Remotely"); public static Config GetConfig() { if (!Directory.Exists(ConfigFolder)) @@ -46,6 +46,5 @@ namespace Remotely.Desktop.Unix.Services { } } - public string Host { get; set; } } } diff --git a/Desktop.Unix/Services/Executor.cs b/Desktop.Unix/Services/Executor.cs index aa474be7..7ae44ca7 100644 --- a/Desktop.Unix/Services/Executor.cs +++ b/Desktop.Unix/Services/Executor.cs @@ -15,12 +15,11 @@ namespace Remotely.Desktop.Unix.Services IsExecutable = isExecutable; } + public event EventHandler CanExecuteChanged; + private Action ExecuteAction { get; set; } private Predicate IsExecutable { get; set; } - - public event EventHandler CanExecuteChanged; - public bool CanExecute(object parameter) { if (IsExecutable == null) diff --git a/Desktop.Unix/ViewModels/MainWindowViewModel.cs b/Desktop.Unix/ViewModels/MainWindowViewModel.cs index 72f65b42..6831da01 100644 --- a/Desktop.Unix/ViewModels/MainWindowViewModel.cs +++ b/Desktop.Unix/ViewModels/MainWindowViewModel.cs @@ -25,6 +25,9 @@ namespace Remotely.Desktop.Unix.ViewModels { public class MainWindowViewModel : ReactiveObject { + private double copyMessageOpacity; + private string host; + private bool isCopyMessageVisible; private string sessionID; public MainWindowViewModel() @@ -40,15 +43,7 @@ namespace Remotely.Desktop.Unix.ViewModels public static MainWindowViewModel Current { get; private set; } - public bool AllowHostChange - { - get - { - return string.IsNullOrWhiteSpace(ForceHost); - } - } - - public ICommand ChangeHostCommand => new Executor(async (param) => + public ICommand ChangeServerCommand => new Executor(async (param) => { await PromptForHostName(); await Init(); @@ -61,8 +56,6 @@ namespace Remotely.Desktop.Unix.ViewModels public Conductor Conductor { get; } - public Config Config { get; private set; } - public ICommand CopyLinkCommand => new Executor(async (param) => { await App.Current.Clipboard.SetTextAsync($"{Host}/RemoteControl?sessionID={SessionID.Replace(" ", "")}"); @@ -77,29 +70,22 @@ namespace Remotely.Desktop.Unix.ViewModels } IsCopyMessageVisible = false; }); - - private double copyMessageOpacity; public double CopyMessageOpacity { get => copyMessageOpacity; set => this.RaiseAndSetIfChanged(ref copyMessageOpacity, value); } - - public string ForceHost { get; } - - private string host; public string Host { get => host; set => this.RaiseAndSetIfChanged(ref host, value); } - - private bool isCopyMessageVisible; public bool IsCopyMessageVisible { get => isCopyMessageVisible; set => this.RaiseAndSetIfChanged(ref isCopyMessageVisible, value); } + public ICommand MinimizeCommand => new Executor((param) => { (param as Window).WindowState = WindowState.Minimized; @@ -114,32 +100,25 @@ namespace Remotely.Desktop.Unix.ViewModels await Conductor.CasterSocket.SendViewerRemoved(viewer.ViewerConnectionID); } }); + public string SessionID { get => sessionID; set => this.RaiseAndSetIfChanged(ref sessionID, value); } - public ObservableCollection Viewers { get; } = new ObservableCollection(); public async Task Init() { SessionID = "Retrieving..."; - Config = Config.GetConfig(); - if (AllowHostChange) + Host = Config.GetConfig().Host; + while (string.IsNullOrWhiteSpace(Host)) { - while (string.IsNullOrWhiteSpace(Config.Host)) - { - Config.Host = "https://"; - await PromptForHostName(); - } + Host = "https://"; + await PromptForHostName(); } - else - { - Config.Host = ForceHost; - } - Host = Config.Host; - Conductor.ProcessArgs(new string[] { "-mode", "Normal", "-host", Config.Host }); + Host = Host; + Conductor.ProcessArgs(new string[] { "-mode", "Normal", "-host", Host }); try { await Conductor.Connect(); @@ -168,9 +147,9 @@ namespace Remotely.Desktop.Unix.ViewModels public async Task PromptForHostName() { var prompt = new HostNamePrompt(); - if (!string.IsNullOrWhiteSpace(Config.Host)) + if (!string.IsNullOrWhiteSpace(Host)) { - HostNamePromptViewModel.Current.Host = Config.Host; + HostNamePromptViewModel.Current.Host = Host; } prompt.Owner = App.Current?.MainWindow; await prompt.ShowDialog(App.Current?.MainWindow); @@ -179,11 +158,12 @@ namespace Remotely.Desktop.Unix.ViewModels { result = $"https://{result}"; } - if (result != Config.Host) + if (result != Host) { - Config.Host = result; Host = result; - Config.Save(); + var config = Config.GetConfig(); + config.Host = Host; + config.Save(); } } diff --git a/Desktop.Unix/Views/MainWindow.xaml b/Desktop.Unix/Views/MainWindow.xaml index 2e52886d..88de3021 100644 --- a/Desktop.Unix/Views/MainWindow.xaml +++ b/Desktop.Unix/Views/MainWindow.xaml @@ -13,9 +13,9 @@ - + - Remotely + Remotely - + diff --git a/Desktop.Win/Controls/HostNamePrompt.xaml b/Desktop.Win/Controls/HostNamePrompt.xaml index 96e08ba3..8e2f3947 100644 --- a/Desktop.Win/Controls/HostNamePrompt.xaml +++ b/Desktop.Win/Controls/HostNamePrompt.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:Remotely.Desktop.Win.Controls" xmlns:ViewModels="clr-namespace:Remotely.Desktop.Win.ViewModels" x:Name="PromptWindow" x:Class="Remotely.Desktop.Win.Controls.HostNamePrompt" mc:Ignorable="d" - Title="Remotely Host Name" Height="150" Width="350" Icon="/Remotely_Desktop;component/favicon.ico" WindowStartupLocation="CenterOwner"> + Title="Remotely Host Name" Height="150" Width="350" WindowStartupLocation="CenterOwner" Icon="/Remotely_Desktop;component/favicon.ico"> diff --git a/Desktop.Win/Desktop.Win.csproj b/Desktop.Win/Desktop.Win.csproj index f244b3c9..7eda54ba 100644 --- a/Desktop.Win/Desktop.Win.csproj +++ b/Desktop.Win/Desktop.Win.csproj @@ -215,6 +215,7 @@ HostNamePrompt.xaml + diff --git a/Desktop.Win/MainWindow.xaml b/Desktop.Win/MainWindow.xaml index eb3146d6..7b362512 100644 --- a/Desktop.Win/MainWindow.xaml +++ b/Desktop.Win/MainWindow.xaml @@ -8,7 +8,17 @@ mc:Ignorable="d" Title="Remotely" Height="250" Width="350" MouseLeftButtonDown="Window_MouseLeftButtonDown" WindowStyle="None" ResizeMode="NoResize" Icon="favicon.ico" Loaded="Window_Loaded"> - + + + + + + + + + + + @@ -59,17 +69,34 @@ - + - - - Connected to: - - - - - + diff --git a/Desktop.Win/MainWindow.xaml.cs b/Desktop.Win/MainWindow.xaml.cs index 5e1f6ff5..f39bfd71 100644 --- a/Desktop.Win/MainWindow.xaml.cs +++ b/Desktop.Win/MainWindow.xaml.cs @@ -47,14 +47,8 @@ namespace Remotely.Desktop.Win private async void Window_Loaded(object sender, RoutedEventArgs e) { await MainWindowViewModel.Current.Init(); - MainWindowViewModel.Current.CheckForAdminRights(); } - private async void HostHyperlink_Click(object sender, RoutedEventArgs e) - { - MainWindowViewModel.Current.PromptForHostName(); - await MainWindowViewModel.Current.Init(); - } private async void CopyLinkButton_Click(object sender, RoutedEventArgs e) { @@ -73,9 +67,9 @@ namespace Remotely.Desktop.Win tooltip.BeginAnimation(OpacityProperty, animation); } - private async void RemoveButton_Click(object sender, RoutedEventArgs e) + private void OptionsButton_Click(object sender, RoutedEventArgs e) { - await MainWindowViewModel.Current.RemoveViewers(ViewerListBox.SelectedItems.Cast()); + (sender as Button).ContextMenu.IsOpen = true; } } } diff --git a/Desktop.Win/Services/Config.cs b/Desktop.Win/Services/Config.cs index 360c4a1d..c11cfc2c 100644 --- a/Desktop.Win/Services/Config.cs +++ b/Desktop.Win/Services/Config.cs @@ -11,34 +11,10 @@ namespace Remotely.Desktop.Win.Services { public class Config { - private Config() - { - - } - - public string Host { get; set; } + public string Host { get; set; } = ""; private static string ConfigFile => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Remotely", "Config.json"); private static string ConfigFolder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Remotely"); - public static string GetHostName() - { - return GetConfig()?.Host; - } - - public static void SaveHostName(string hostName) - { - try - { - var config = GetConfig(); - config.Host = hostName; - Directory.CreateDirectory(ConfigFolder); - File.WriteAllText(ConfigFile, JsonConvert.SerializeObject(config)); - } - catch - { - } - } - - private static Config GetConfig() + public static Config GetConfig() { if (!Directory.Exists(ConfigFolder)) { @@ -58,5 +34,17 @@ namespace Remotely.Desktop.Win.Services } return new Config(); } + + public void Save() + { + try + { + Directory.CreateDirectory(ConfigFolder); + File.WriteAllText(ConfigFile, JsonConvert.SerializeObject(this)); + } + catch + { + } + } } } diff --git a/Desktop.Win/Services/Executor.cs b/Desktop.Win/Services/Executor.cs new file mode 100644 index 00000000..d4650034 --- /dev/null +++ b/Desktop.Win/Services/Executor.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Remotely.Desktop.Win.Services +{ + public class Executor : ICommand + { + public Executor(Action executeAction, Predicate isExecutable = null) + { + ExecuteAction = executeAction; + IsExecutable = isExecutable; + } + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + private Action ExecuteAction { get; set; } + + private Predicate IsExecutable { get; set; } + public bool CanExecute(object parameter) + { + if (IsExecutable == null) + { + return true; + } + return IsExecutable.Invoke(parameter); + } + + public void Execute(object parameter) + { + ExecuteAction.Invoke(parameter); + } + } +} diff --git a/Desktop.Win/ViewModels/MainWindowViewModel.cs b/Desktop.Win/ViewModels/MainWindowViewModel.cs index 1404af6d..b8dde76d 100644 --- a/Desktop.Win/ViewModels/MainWindowViewModel.cs +++ b/Desktop.Win/ViewModels/MainWindowViewModel.cs @@ -22,6 +22,8 @@ using System.Threading.Tasks; using System.Windows; using System.Security.Principal; using System.Security.Claims; +using System.Windows.Input; +using System.Windows.Controls; namespace Remotely.Desktop.Win.ViewModels { @@ -44,22 +46,21 @@ namespace Remotely.Desktop.Win.ViewModels public static MainWindowViewModel Current { get; private set; } - public bool AllowHostChange + public AudioCapturer AudioCapturer { get; private set; } + public ICommand ChangeServerCommand { get { - return string.IsNullOrWhiteSpace(ForceHost); + return new Executor(async (param) => + { + PromptForHostName(); + await Init(); + }); } } - public AudioCapturer AudioCapturer { get; private set; } public Conductor Conductor { get; } - - public CursorIconWatcher CursorIconWatcher { get; private set; } - - public string ForceHost { get; } - public string Host { get => host; @@ -70,6 +71,28 @@ namespace Remotely.Desktop.Win.ViewModels } } + public bool IsAdministrator => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + public ICommand RestartAsAdminCommand + { + get + { + return new Executor((param) => + { + try + { + var psi = new ProcessStartInfo(Assembly.GetExecutingAssembly().Location); + psi.Verb = "RunAs"; + Process.Start(psi); + Environment.Exit(0); + } + // Exception can be thrown if UAC is dialog is cancelled. + catch { } + }, (param) => { + return !IsAdministrator; + }); + } + } + public string SessionID { get => sessionID; @@ -81,39 +104,19 @@ namespace Remotely.Desktop.Win.ViewModels } public ObservableCollection Viewers { get; } = new ObservableCollection(); - - public void CheckForAdminRights() - { - if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) - { - var result = MessageBox.Show(Application.Current.MainWindow, "Remotely isn't running with administrator rights. Would you like to re-launch as an admin?", "Run as Admin", MessageBoxButton.YesNo, MessageBoxImage.Question); - if (result == MessageBoxResult.Yes) - { - var psi = new ProcessStartInfo(Assembly.GetExecutingAssembly().Location); - psi.Verb = "RunAs"; - Process.Start(psi); - Environment.Exit(0); - } - } - } - public async Task Init() { SessionID = "Retrieving..."; - Host = Config.GetHostName(); - if (AllowHostChange) + + var config = Config.GetConfig(); + Host = config.Host; + + while (string.IsNullOrWhiteSpace(Host)) { - while (string.IsNullOrWhiteSpace(Host)) - { - Host = "https://"; - PromptForHostName(); - } + Host = "https://"; + PromptForHostName(); } - else - { - Host = ForceHost; - } - + Conductor.ProcessArgs(new string[] { "-mode", "Normal", "-host", Host }); try { @@ -147,23 +150,35 @@ namespace Remotely.Desktop.Win.ViewModels if (result != Host) { Host = result; - Config.SaveHostName(Host); - FirePropertyChanged("Host"); + var config = Config.GetConfig(); + config.Host = Host; + config.Save(); } } - internal void CopyLink() + public void CopyLink() { Clipboard.SetText($"{Host}/RemoteControl?sessionID={SessionID.Replace(" ", "")}"); } - internal async Task RemoveViewers(IEnumerable viewerList) + public ICommand RemoveViewersCommand { - foreach (Viewer viewer in viewerList) + get { - viewer.DisconnectRequested = true; - await Conductor.CasterSocket.SendViewerRemoved(viewer.ViewerConnectionID); + return new Executor(async (param) => + { + foreach (Viewer viewer in (param as IList)) + { + viewer.DisconnectRequested = true; + await Conductor.CasterSocket.SendViewerRemoved(viewer.ViewerConnectionID); + } + }, + (param) => + { + return (param as IList)?.Count > 0; + }); } + } private async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor) diff --git a/README.md b/README.md index a86eb049..fe9336ad 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ The following steps will configure your Windows 10 machine for building the Remo * Run Publish.ps1 in the Utilities folder. * Example: powershell -f [path]\Publish.ps1 -outdir C:\inetpub\remotely -rid win10-x86 * The output folder will now contain the server, with the clients in the Downloads folder. - * By default, the screen-sharing desktop app prompts for a host URL and can be changed thereafter. To hard-code a URL, set the ForceHost value in /Desktop.Win/ViewModels/MainWindowViewModel.cs to the server's URL (same for the Desktop.Unix project). + * By default, the screen-sharing desktop app prompts for a host URL and can be changed thereafter. + * To hard-code a URL, set the Host value in /Desktop.Win/Services/Config.cs to the server's URL (same for the Desktop.Unix project). + * To hide the server name in the app, set ShowHostName to false. ## Hosting a Server (Windows) * Create a site in IIS that will run Remotely. diff --git a/Server/CurrentVersion.txt b/Server/CurrentVersion.txt index b2082ab8..99f4f5ba 100644 --- a/Server/CurrentVersion.txt +++ b/Server/CurrentVersion.txt @@ -1 +1 @@ -2019.06.19.1959 +2019.06.20.1643