Desktop app main window UI update.

This commit is contained in:
Jared Goodwin 2019-06-21 15:18:52 -07:00
parent 2d96355110
commit 21af3c5b99
14 changed files with 200 additions and 141 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

View File

@ -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; }
}
}

View File

@ -15,12 +15,11 @@ namespace Remotely.Desktop.Unix.Services
IsExecutable = isExecutable;
}
public event EventHandler CanExecuteChanged;
private Action<object> ExecuteAction { get; set; }
private Predicate<object> IsExecutable { get; set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
if (IsExecutable == null)

View File

@ -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<Viewer> Viewers { get; } = new ObservableCollection<Viewer>();
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();
}
}

View File

@ -13,9 +13,9 @@
<Grid>
<StackPanel>
<Border Name="TitleBanner" Height="50" Background="#FF464646">
<DockPanel Margin="10,0,0,0">
<DockPanel Margin="10,4,0,0">
<StackPanel>
<TextBlock Foreground="DeepSkyBlue" FontWeight="Bold" FontSize="20" Margin="0,2,0,0">Remotely</TextBlock>
<TextBlock Foreground="DeepSkyBlue" FontWeight="Bold" FontSize="20">Remotely</TextBlock>
<TextBlock Foreground="White" FontSize="10" Text="Do IT Remotely"></TextBlock>
</StackPanel>
<Button Classes="TitlebarButton" Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Content="X" />
@ -65,10 +65,23 @@
</StackPanel>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Margin="10,5" IsVisible="{Binding AllowHostChange}">
<TextBlock FontSize="10" Text="Connected to: "></TextBlock>
<Button Classes="HyperlinkButton" Command="{Binding ChangeHostCommand}" FontSize="10" Margin="5,0" Content="{Binding Host}">
</Button>
</StackPanel>
<Button BorderThickness="0"
Background="Transparent"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Cursor="Hand"
Height="25"
Width="25"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10,5"
>
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Change Server" Command="{Binding ChangeServerCommand}"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
<Image Source="avares://Remotely_Desktop/Assets/Gear.png"></Image>
</Button>
</Grid>
</Window>

View File

@ -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">
<Window.DataContext>
<ViewModels:HostNamePromptViewModel/>
</Window.DataContext>

View File

@ -215,6 +215,7 @@
<DependentUpon>HostNamePrompt.xaml</DependentUpon>
</Compile>
<Compile Include="Services\Config.cs" />
<Compile Include="Services\Executor.cs" />
<Compile Include="ViewModels\HostNamePromptViewModel.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />
<Compile Include="ViewModels\ViewModelBase.cs" />

View File

@ -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">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<DrawingBrush x:Key="GearBrush">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M5.6385,-0.000199999999999534L5.3645,1.6368 4.0145,0.671800000000001 0.672499999999999,4.0128 1.6375,5.3648 -0.000500000000000611,5.6378 -0.000500000000000611,10.3628 1.6375,10.6358 0.6735,11.9858 4.0145,15.3268 5.3645,14.3628 5.6375,15.9998 10.3625,15.9998 10.6355,14.3618 11.9875,15.3278 15.3265,11.9858 14.3635,10.6358 16.0005,10.3628 16.0005,5.6378 14.3635,5.3648 15.3265,4.0138 11.9875,0.672800000000001 10.6355,1.6378 10.3625,-0.000199999999999534z" />
<GeometryDrawing Brush="#FF424242" Geometry="F1M8.0005,6.4623C7.1525,6.4623 6.4625,7.1523 6.4625,8.0003 6.4625,8.8483 7.1525,9.5373 8.0005,9.5373 8.8485,9.5373 9.5385,8.8483 9.5385,8.0003 9.5385,7.1523 8.8485,6.4623 8.0005,6.4623 M8.0005,10.5633C6.5875,10.5633 5.4375,9.4133 5.4375,8.0003 5.4375,6.5873 6.5875,5.4373 8.0005,5.4373 9.4135,5.4373 10.5635,6.5873 10.5635,8.0003 10.5635,9.4133 9.4135,10.5633 8.0005,10.5633 M7.3535,13.9753L8.6475,13.9753 8.9555,12.1263 9.2565,12.0323C9.5005,11.9563,9.7375,11.8583,9.9635,11.7393L10.2425,11.5933 11.7675,12.6823 12.6825,11.7673 11.5915,10.2403 11.7385,9.9613C11.7675,9.9043 11.7975,9.8543 11.8265,9.8053 11.8655,9.7373 11.8995,9.6783 11.9225,9.6243 11.9445,9.5703 11.9615,9.5043 11.9815,9.4283 11.9965,9.3743 12.0115,9.3173 12.0305,9.2573L12.1235,8.9553 13.9755,8.6473 13.9755,7.3533 12.1265,7.0453 12.0325,6.7443C11.9565,6.5003,11.8585,6.2633,11.7395,6.0373L11.5935,5.7583 12.6825,4.2333 11.7675,3.3183 10.2405,4.4083 9.9615,4.2623C9.9045,4.2323 9.8545,4.2033 9.8055,4.1743 9.7375,4.1353 9.6785,4.1003 9.6245,4.0783L9.6245,4.0783C9.5705,4.0563 9.5045,4.0383 9.4285,4.0183 9.3745,4.0043 9.3175,3.9893 9.2575,3.9703L8.9565,3.8763 8.6475,2.0253 7.3535,2.0253 7.0455,3.8743 6.7445,3.9683C6.5005,4.0443,6.2635,4.1423,6.0375,4.2603L5.7585,4.4073 4.2335,3.3173 3.3185,4.2323 4.4085,5.7593 4.2625,6.0393C4.2325,6.0953 4.2035,6.1463 4.1745,6.1953 4.1355,6.2633 4.1015,6.3213 4.0785,6.3753 4.0565,6.4303 4.0385,6.4953 4.0185,6.5723 4.0045,6.6263 3.9895,6.6823 3.9705,6.7433L3.8775,7.0443 2.0255,7.3533 2.0255,8.6473 3.8745,8.9553 3.9685,9.2563C4.0445,9.5003,4.1425,9.7363,4.2615,9.9633L4.4075,10.2423 3.3175,11.7673 4.2325,12.6823 5.7605,11.5913 6.0395,11.7383C6.0955,11.7673 6.1465,11.7973 6.1955,11.8253 6.2635,11.8653 6.3215,11.8993 6.3755,11.9213 6.4295,11.9443 6.4955,11.9613 6.5715,11.9813 6.6265,11.9963 6.6825,12.0103 6.7435,12.0293L7.0445,12.1233 7.3535,13.9753z M9.5155,15.0003L6.4845,15.0003 6.1385,12.9243C6.0865,12.9083 6.0345,12.8903 5.9835,12.8693 5.9325,12.8473 5.8835,12.8243 5.8345,12.7983L4.1225,14.0213 1.9795,11.8783 3.2135,10.1503C3.1705,10.0563,3.1315,9.9613,3.0945,9.8643L1.0005,9.5153 1.0005,6.4843 3.0755,6.1383C3.0925,6.0863 3.1105,6.0343 3.1315,5.9833 3.1525,5.9323 3.1765,5.8823 3.2025,5.8343L1.9795,4.1213 4.1225,1.9783 5.8505,3.2123C5.9445,3.1703,6.0395,3.1313,6.1365,3.0943L6.4855,1.0003 9.5165,1.0003 9.8615,3.0753C9.9115,3.0913 9.9635,3.1093 10.0175,3.1313 10.0685,3.1523 10.1175,3.1763 10.1665,3.2013L11.8785,1.9793 14.0215,4.1223 12.7875,5.8493C12.8305,5.9433,12.8695,6.0393,12.9065,6.1353L15.0005,6.4853 15.0005,9.5153 12.9255,9.8613C12.9085,9.9143 12.8905,9.9663 12.8695,10.0163 12.8485,10.0673 12.8245,10.1173 12.7985,10.1663L14.0215,11.8783 11.8785,14.0213 10.1505,12.7873C10.0565,12.8293,9.9615,12.8693,9.8645,12.9063L9.5155,15.0003z" />
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Resources>
<Window.DataContext>
<ViewModels:MainWindowViewModel/>
@ -59,17 +69,34 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Style="{StaticResource NormalButton}" HorizontalAlignment="Right" Margin="0,5,0,0" Click="RemoveButton_Click">Remove</Button>
<Button Style="{StaticResource NormalButton}"
HorizontalAlignment="Right"
Command="{Binding RemoveViewersCommand}"
CommandParameter="{Binding ElementName=ViewerListBox, Path=SelectedItems}"
Margin="0,5,0,0"
Content="Remove">
</Button>
</StackPanel>
</Grid>
</StackPanel>
<DockPanel VerticalAlignment="Bottom" Margin="10,5" Visibility="{Binding AllowHostChange, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<TextBlock FontSize="10">
<Run>Connected to: </Run>
<Hyperlink Click="HostHyperlink_Click">
<TextBlock Text="{Binding Host}"></TextBlock>
</Hyperlink>
</TextBlock>
</DockPanel>
<Button BorderThickness="0"
Background="Transparent"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Cursor="Hand"
Height="20"
Width="20"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10,5"
Click="OptionsButton_Click">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Change Server" Command="{Binding Path=ChangeServerCommand, Source={x:Static ViewModels:MainWindowViewModel.Current}}"></MenuItem>
<MenuItem Header="Restart as Admin" Command="{Binding Path=RestartAsAdminCommand, Source={x:Static ViewModels:MainWindowViewModel.Current}}"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
<Rectangle Fill="{StaticResource GearBrush}"></Rectangle>
</Button>
</Grid>
</Window>

View File

@ -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<Viewer>());
(sender as Button).ContextMenu.IsOpen = true;
}
}
}

View File

@ -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
{
}
}
}
}

View File

@ -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<object> executeAction, Predicate<object> isExecutable = null)
{
ExecuteAction = executeAction;
IsExecutable = isExecutable;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action<object> ExecuteAction { get; set; }
private Predicate<object> 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);
}
}
}

View File

@ -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<Viewer> Viewers { get; } = new ObservableCollection<Viewer>();
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<Viewer> 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<object>))
{
viewer.DisconnectRequested = true;
await Conductor.CasterSocket.SendViewerRemoved(viewer.ViewerConnectionID);
}
},
(param) =>
{
return (param as IList<object>)?.Count > 0;
});
}
}
private async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)

View File

@ -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.

View File

@ -1 +1 @@
2019.06.19.1959
2019.06.20.1643