Add project files.

This commit is contained in:
Elijahr2411 2023-08-29 08:52:35 -04:00
parent bea0b3ca1d
commit 349af128ab
43 changed files with 1981 additions and 0 deletions

454
.gitignore vendored Normal file
View file

@ -0,0 +1,454 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# JetBrains Rider
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

View file

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.-->
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Desktop" Version="11.0.4" />
<PackageReference Include="Projektanker.Icons.Avalonia" Version="8.2.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="8.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CollabVMClient\CollabVMClient.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,30 @@
using System;
using Avalonia;
using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.MaterialDesign;
namespace CollabVMClient.Desktop;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
IconProvider.Current
.Register<MaterialDesignIconProvider>();
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

37
CollabVMClient.sln Normal file
View file

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34018.315
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CollabVMClient", "CollabVMClient\CollabVMClient.csproj", "{74F3DDE0-C0D2-473B-942E-78EDFB956937}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CollabVMClient.Desktop", "CollabVMClient.Desktop\CollabVMClient.Desktop.csproj", "{8A36CCA1-A1F9-47B4-BA88-4768B6BDC5B9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibVLCSharp.Avalonia", "LibVLCSharp.Avalonia\LibVLCSharp.Avalonia.csproj", "{E85E9F2A-5F72-40D1-88BC-21511717BD3E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{74F3DDE0-C0D2-473B-942E-78EDFB956937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74F3DDE0-C0D2-473B-942E-78EDFB956937}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74F3DDE0-C0D2-473B-942E-78EDFB956937}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74F3DDE0-C0D2-473B-942E-78EDFB956937}.Release|Any CPU.Build.0 = Release|Any CPU
{8A36CCA1-A1F9-47B4-BA88-4768B6BDC5B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A36CCA1-A1F9-47B4-BA88-4768B6BDC5B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A36CCA1-A1F9-47B4-BA88-4768B6BDC5B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A36CCA1-A1F9-47B4-BA88-4768B6BDC5B9}.Release|Any CPU.Build.0 = Release|Any CPU
{E85E9F2A-5F72-40D1-88BC-21511717BD3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E85E9F2A-5F72-40D1-88BC-21511717BD3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E85E9F2A-5F72-40D1-88BC-21511717BD3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E85E9F2A-5F72-40D1-88BC-21511717BD3E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1C71D0CF-BEED-41E1-94E1-03003C3E9199}
EndGlobalSection
EndGlobal

10
CollabVMClient/App.axaml Normal file
View file

@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="CollabVMClient.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

View file

@ -0,0 +1,41 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using CollabVMClient.ViewModels;
using CollabVMClient.Views;
namespace CollabVMClient;
public partial class App : Application
{
private IConfig _config;
public IConfig Config => _config;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
BindingPlugins.DataValidators.RemoveAt(0);
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
this._config = Utilities.LoadConfig();
desktop.MainWindow = new MainWindow
{
DataContext = new ServerListViewModel(this.Config)
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
}
base.OnFrameworkInitializationCompleted();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View file

@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.4" />
<PackageReference Include="Avalonia.HtmlRenderer" Version="11.0.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.4" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.4" />
<PackageReference Include="CollabVMSharp" Version="2.2.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.4" />
<PackageReference Include="LibVLCSharp" Version="3.7.0" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.4" />
<PackageReference Include="Projektanker.Icons.Avalonia" Version="8.2.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="8.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
</ItemGroup>
<ItemGroup Condition=" '$(RuntimeIdentifier)' == 'win-x64' Or '$(RuntimeIdentifier)' == 'win-x86' ">
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.18" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LibVLCSharp.Avalonia\LibVLCSharp.Avalonia.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="CollabVMResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>CollabVMResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="CollabVMResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>CollabVMResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View file

@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CollabVMClient {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class CollabVMResources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal CollabVMResources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CollabVMClient.CollabVMResources", typeof(CollabVMResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] notify {
get {
object obj = ResourceManager.GetObject("notify", resourceCulture);
return ((byte[])(obj));
}
}
}
}

View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="notify" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\notify.ogg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

View file

@ -0,0 +1,148 @@
using Avalonia.Controls;
using CollabVMClient.ViewModels;
using CollabVMClient.Views;
using CollabVMSharp;
using LibVLCSharp.Shared;
using MsBox.Avalonia;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollabVMClient
{
public class CollabVMViewer
{
private CollabVMSharp.CollabVMClient cvm;
private CollabVMScreenViewModel screen_vm;
private CollabVMScreenView screen;
private CollabVMChatViewModel chat_vm;
private CollabVMChatView chat;
private UserListViewModel users_vm;
private UserList users;
private string url;
private string node;
public CollabVMViewer(string url, string node)
{
this.url = url;
this.node = node;
cvm = new CollabVMSharp.CollabVMClient(url, null, node);
cvm.ScreenSize += Cvm_ScreenSize;
cvm.Rect += Cvm_Rect;
cvm.Chat += Cvm_Chat;
cvm.ChatHistory += Cvm_ChatHistory;
cvm.Renamed += Cvm_Renamed;
cvm.UserJoined += Cvm_UserJoined;
cvm.UserLeft += Cvm_UserLeft;
cvm.UserRenamed += Cvm_UserRenamed;
cvm.TurnUpdate += Cvm_TurnUpdate;
screen_vm = new CollabVMScreenViewModel(node);
screen = new CollabVMScreenView();
screen.DataContext = screen_vm;
chat = new CollabVMChatView();
chat_vm = new CollabVMChatViewModel(node);
chat.DataContext = chat_vm;
chat.ChatSend += (s, e) => cvm.SendChat(e);
users_vm = new UserListViewModel(node);
users = new UserList();
users.DataContext = users_vm;
// Exit if any window is closed
screen.Closed += WindowClosed;
chat.Closed += WindowClosed;
users.Closed += WindowClosed;
}
private void Cvm_TurnUpdate(object? sender, TurnUpdateEventArgs e)
{
users_vm.ParseTurnUpdate(e);
//users.ReloadUsers();
}
private void Cvm_UserRenamed(object? sender, UserRenamedEventArgs e)
{
users_vm.RenameUser(e.OldName, e.NewName);
}
private void Cvm_UserLeft(object? sender, User e)
{
users_vm.RemoveUser(e.Username);
}
private void Cvm_UserJoined(object? sender, User e)
{
users_vm.AddUser(e.Username, e.Rank);
}
public async Task<bool> Open()
{
try {
await cvm.Connect();
} catch (Exception e)
{
await MessageBoxManager.GetMessageBoxStandard("CollabVM Connection Error", $"Failed to connect to the CollabVM Server at {url}:\n{e.Message}", MsBox.Avalonia.Enums.ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error).ShowAsync();
return false;
}
cvm.ConnectedToNode += Cvm_ConnectedToNode;
cvm.NodeConnectFailed += Cvm_NodeConnectFailed;
return true;
}
private void Cvm_NodeConnectFailed(object? sender, EventArgs e)
{
MessageBoxManager.GetMessageBoxStandard("CollabVM Connection Error", $"Failed to connect to the CollabVM Server at {url}:\n{node} is not a valid node on this server.", MsBox.Avalonia.Enums.ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error).ShowAsync();
}
private void Cvm_ConnectedToNode(object? sender, EventArgs e)
{
screen.Show();
chat.Show();
users.Show();
}
private void WindowClosed(object? sender, EventArgs e)
{
cvm.Disconnect();
screen.Close();
chat.Close();
users.Close();
}
private void Cvm_Renamed(object? sender, string e)
{
chat_vm.SetUsername(e);
}
private void Cvm_ChatHistory(object? sender, ChatMessage[] e)
{
foreach (var chat in e)
{
chat_vm.AddChatMessage(chat.Username, chat.Message);
}
}
private void Cvm_Chat(object? sender, ChatMessage e)
{
chat_vm.AddChatMessage(e.Username, e.Message);
}
private void Cvm_ScreenSize(object? sender, ScreenSizeEventArgs e)
{
screen_vm.SetSize(e.Width, e.Height);
}
private void Cvm_Rect(object? sender, RectEventArgs e)
{
byte[] rect = new byte[e.Data.Width * e.Data.Height * 4];
e.Data.CloneAs<Rgba32>().CopyPixelDataTo(rect);
screen_vm.LoadRect(rect, e.Data.Width, e.Data.Height, e.X, e.Y);
screen.RefreshScreen();
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollabVMClient
{
public static class Constants
{
public static string UserVMNodeList => "https://computernewb.com/collab-vm/user-vm/nodelist.json";
public static IConfig DefaultConfig => new IConfig
{
Nodes = new string[] {
"wss://computernewb.com/collab-vm/vm0",
"wss://computernewb.com/collab-vm/vm1",
"wss://computernewb.com/collab-vm/vm2",
"wss://computernewb.com/collab-vm/vm3",
"wss://computernewb.com/collab-vm/vm4",
"wss://computernewb.com/collab-vm/vm5",
"wss://computernewb.com/collab-vm/vm6",
"wss://computernewb.com/collab-vm/vm7",
"wss://computernewb.com/collab-vm/vm8",
}
};
}
}

15
CollabVMClient/IConfig.cs Normal file
View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
namespace CollabVMClient
{
public class IConfig
{
public string[] Nodes { get; set; }
}
}

Binary file not shown.

View file

@ -0,0 +1,59 @@
using CollabVMClient.ViewModels;
using CollabVMSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace CollabVMClient
{
public static class Utilities
{
/// <summary>
/// Return view models for each node a cvm server offers
/// </summary>
public static async Task<CVMNodeViewModel[]> GetNodes(string url)
{
var cvm = new CollabVMSharp.CollabVMClient(url);
await cvm.Connect();
var nodes = await cvm.ListVMs();
await cvm.Disconnect();
var nodelist = new List<CVMNodeViewModel>();
foreach (var node in nodes)
{
using var ms = new MemoryStream(node.Thumbnail);
nodelist.Add(new CVMNodeViewModel(url, node.ID, node.Name, new Avalonia.Media.Imaging.Bitmap(ms)));
}
return nodelist.ToArray();
}
public static void WriteDefaultConfig()
{
var cj = JsonSerializer.Serialize(Constants.DefaultConfig);
File.WriteAllText("config.json", cj);
}
public static IConfig LoadConfig()
{
try
{
var cr = File.ReadAllText("config.json");
var c = JsonSerializer.Deserialize<IConfig>(cr);
if (c == null)
{
Utilities.WriteDefaultConfig();
return Constants.DefaultConfig;
}
else return c;
}
catch (FileNotFoundException)
{
Utilities.WriteDefaultConfig();
return Constants.DefaultConfig;
}
}
}
}

View file

@ -0,0 +1,25 @@
using Avalonia.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollabVMClient.ViewModels
{
public class CVMNodeViewModel : ViewModelBase
{
public string URL { get; set; }
public string ID { get; set; }
public string Name { get; set; }
public Bitmap Thumbnail { get; set; }
public CVMNodeViewModel(string url, string iD, string name, Bitmap thumbnail)
{
URL = url;
ID = iD;
Name = name;
Thumbnail = thumbnail;
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollabVMClient.ViewModels
{
internal class ChatMessageViewModel : ViewModelBase
{
private string _username;
private string _message;
public string Username => _username;
public string Message => _message;
public string HTML => string.IsNullOrEmpty(_username) ? _message : $"<b>{_username}></b> {_message}";
public ChatMessageViewModel(string username, string message)
{
this._username = username;
this._message = message;
}
}
}

View file

@ -0,0 +1,36 @@
using CommunityToolkit.Mvvm.ComponentModel;
using LibVLCSharp.Shared;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollabVMClient.ViewModels
{
internal partial class CollabVMChatViewModel : ViewModelBase
{
public ObservableCollection<ChatMessageViewModel> ChatMessages { get; } = new ObservableCollection<ChatMessageViewModel>();
[ObservableProperty] private string username;
[ObservableProperty] private string id;
public string Title => $"CollabVM - Chat ({id})";
public void AddChatMessage(string username, string message)
{
ChatMessages.Add(new ChatMessageViewModel(username, message));
}
public CollabVMChatViewModel(string id)
{
this.Username = "username";
this.Id = id;
}
public void SetUsername(string username)
{
this.Username = username;
}
}
}

View file

@ -0,0 +1,51 @@
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using Avalonia.Media;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CollabVMClient.ViewModels
{
internal partial class CollabVMScreenViewModel : ViewModelBase
{
public WriteableBitmap Framebuffer { get; set; }
[ObservableProperty] private string id;
[ObservableProperty] private int width;
[ObservableProperty] private int height;
public string Title => $"CollabVM - Screen ({id})";
public CollabVMScreenViewModel(string id)
{
this.Id = id;
Framebuffer = new WriteableBitmap(new Avalonia.PixelSize(1, 1), new Avalonia.Vector(1, 1));
}
public void SetSize(int width, int height)
{
this.Width = width;
this.Height = height;
Framebuffer = new WriteableBitmap(new Avalonia.PixelSize(width, height), new Avalonia.Vector(96.0f, 96.0f), PixelFormat.Rgba8888);
}
public void LoadRect(byte[] rect, int width, int height, int x, int y)
{
// writing this method made me suicidal
var l = Framebuffer.Lock();
var byteswritten = 0;
for (var i = 0; i < height; i++)
{
Marshal.Copy(rect, byteswritten, IntPtr.Add(l.Address, (int)(4 * ((y + i) * Framebuffer.Size.Width + x))), width * 4);
byteswritten += width * 4;
}
l.Dispose();
}
}
}

View file

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using CollabVMClient.Views;
using MsBox.Avalonia;
namespace CollabVMClient.ViewModels
{
internal class ServerListViewModel : ViewModelBase
{
private IConfig config;
private CancellationTokenSource populateListCt;
public string ServerListHeader => "Servers";
public ObservableCollection<CVMNodeViewModel> Nodes { get; } = new ObservableCollection<CVMNodeViewModel>();
public ServerListViewModel(IConfig config)
{
this.config = config;
populateListCt = new();
PopulateList(config.Nodes, populateListCt.Token);
}
public async Task PopulateList(string[] urls, CancellationToken token)
{
foreach (var url in urls)
{
if (token.IsCancellationRequested) return;
await Multicollab(url, token);
}
}
public async Task Multicollab(string url, CancellationToken token)
{
CVMNodeViewModel[] nodes;
try
{
nodes = await Utilities.GetNodes(url);
} catch (Exception e)
{
await MessageBoxManager.GetMessageBoxStandard("CollabVM Connection Error", $"Failed to connect to the CollabVM Server at {url}:\n{e.Message}", MsBox.Avalonia.Enums.ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error).ShowAsync();
return;
}
foreach (var node in nodes)
{
if (token.IsCancellationRequested) return;
Nodes.Add(node);
var newNodes = Nodes.OrderBy(n => n.ID).ToArray();
Nodes.Clear();
foreach (CVMNodeViewModel m in newNodes)
{
Nodes.Add(m);
}
}
}
public void RefreshList()
{
populateListCt.Cancel();
populateListCt = new();
Nodes.Clear();
PopulateList(config.Nodes, populateListCt.Token);
}
public bool CanRefreshList() => true;
public async void AddServer(object parent)
{
var win = new AddServer();
await win.ShowDialog((Window)parent);
if (win.URL != null) Multicollab(win.URL, populateListCt.Token);
}
public bool CanAddServer() => true;
}
}

View file

@ -0,0 +1,77 @@
using CollabVMSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollabVMClient.ViewModels
{
internal class UserListViewModel : ViewModelBase
{
public ObservableCollection<UserViewModel> Users { get; } = new ObservableCollection<UserViewModel>();
private string id;
public string Title => $"CollabVM - Users ({id})";
public UserListViewModel(string id)
{
this.id = id;
}
public void AddUser(string username, Rank rank)
{
// Needed if a user ranks up
RemoveUser(username);
Users.Add(new UserViewModel(username, rank));
}
public void RenameUser(string username, string newName)
{
var user = Users.First(u => u.Username == username);
user.Rename(newName);
}
public void RemoveUser(string username)
{
UserViewModel user;
try
{
user = Users.First(u => u.Username == username);
} catch (InvalidOperationException ex)
{
// just ignore it
return;
}
Users.Remove(user);
}
public void ParseTurnUpdate(TurnUpdateEventArgs e)
{
foreach (var user in Users)
{
user.SetTurnStatus(TurnStatus.None);
}
if (e.Queue.Length == 0) return;
var currentTurningUser = Users.First(u => u.Username == e.Queue[0].Username);
currentTurningUser.SetTurnStatus(TurnStatus.HasTurn);
for (int i = 1; i < e.Queue.Length; i++) {
var user = Users.First(u => u.Username == e.Queue[i].Username);
user.SetTurnStatus(TurnStatus.Waiting);
}
var newUsers = Users.OrderByDescending(i => GetSortIndex(i, e.Queue)).ToArray();
Users.Clear();
foreach (UserViewModel u in newUsers)
{
Users.Add(u);
}
}
public int GetSortIndex(UserViewModel u, User[] queue)
{
int i = Array.FindIndex(queue, x => x.Username == u.Username);
if (i == -1) return -1;
else return queue.Length - i;
}
}
}

View file

@ -0,0 +1,51 @@
using Avalonia.Media;
using CollabVMSharp;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollabVMClient.ViewModels
{
internal partial class UserViewModel : ViewModelBase
{
[ObservableProperty] private string username;
private Rank rank;
private TurnStatus turn;
public IBrush Foreground => rank switch
{
Rank.Unregistered => Brush.Parse("Black"),
Rank.Moderator => Brush.Parse("Green"),
Rank.Admin => Brush.Parse("Red"),
};
[ObservableProperty] private IBrush background;
public void Rename(string newName)
{
Username = newName;
}
public UserViewModel(string username, Rank rank)
{
Username = username;
this.rank = rank;
}
public void SetTurnStatus(TurnStatus turn)
{
this.turn = turn;
Background = turn switch
{
TurnStatus.None => Brush.Parse("White"),
TurnStatus.Waiting => Brush.Parse("Yellow"),
TurnStatus.HasTurn => Brush.Parse("SkyBlue"),
};
}
}
}

View file

@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace CollabVMClient.ViewModels;
public class ViewModelBase : ObservableObject
{
}

View file

@ -0,0 +1,17 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="90"
x:Class="CollabVMClient.Views.AddServer"
Title="CollabVM - Add Server"
Width="300" Height="90" CanResize="False">
<StackPanel Orientation="Vertical">
<Label>URL</Label>
<TextBox Width="300" Name="urlTextBox"/>
<StackPanel Orientation="Horizontal">
<Button Name="confirm">Confirm</Button>
<Button Name="cancel">Cancel</Button>
</StackPanel>
</StackPanel>
</Window>

View file

@ -0,0 +1,35 @@
using Avalonia.Controls;
using MsBox.Avalonia;
using System;
namespace CollabVMClient.Views
{
public partial class AddServer : Window
{
private string? url = null;
public string? URL => url;
public AddServer()
{
InitializeComponent();
cancel.Click += Cancel_Click;
confirm.Click += Confirm_Click;
}
private void Confirm_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Uri u;
if (!Uri.TryCreate(urlTextBox.Text, UriKind.Absolute, out u) || (u.Scheme != "ws" && u.Scheme != "wss"))
{
MessageBoxManager.GetMessageBoxStandard("Error", "URL must be a valid WebSocket url.", MsBox.Avalonia.Enums.ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error).ShowWindowDialogAsync(this);
return;
}
url = urlTextBox.Text;
Close();
}
private void Cancel_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Close();
}
}
}

View file

@ -0,0 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="using:CollabVMClient.Views"
xmlns:av="clr-namespace:TheArtOfDev.HtmlRenderer.Avalonia;assembly=Avalonia.HtmlRenderer"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="100"
x:Class="CollabVMClient.Views.CVMNode"
Width="400" Height="100" >
<Control.Styles>
<Style Selector="views|CVMNode">
<Setter Property="Background" Value="White"/>
</Style>
<Style Selector="views|CVMNode:nth-child(odd)">
<Setter Property="Background" Value="Purple"/>
</Style>
</Control.Styles>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image Source="{Binding Thumbnail}" Width="133" Height="100"/>
<!--<TextBlock VerticalAlignment="Top" Text="{Binding Name}" TextWrapping="Wrap" Width="265"/>-->
<StackPanel Orientation="Vertical">
<av:HtmlPanel Text="{Binding Name}" Height="70" Width="265"/>
<Button Name="ConnectButton" HorizontalAlignment="Right" Height="30" Width="100">Connect</Button>
</StackPanel>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,21 @@
using Avalonia.Controls;
using CollabVMClient.ViewModels;
namespace CollabVMClient.Views
{
public partial class CVMNode : UserControl
{
CVMNodeViewModel VM => ((CVMNodeViewModel)DataContext);
public CVMNode()
{
InitializeComponent();
ConnectButton.Click += ConnectButton_Click;
}
private void ConnectButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var v = new CollabVMViewer(VM.URL, VM.ID);
v.Open();
}
}
}

View file

@ -0,0 +1,32 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:av="clr-namespace:TheArtOfDev.HtmlRenderer.Avalonia;assembly=Avalonia.HtmlRenderer"
xmlns:i="https://github.com/projektanker/icons.avalonia"
xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="800"
x:Class="CollabVMClient.Views.CollabVMChatView"
Title="{Binding Title}"
Width="400" Height="800" CanResize="False">
<StackPanel Orientation="Vertical">
<ScrollViewer Width="400" Height="765">
<ItemsControl ItemsSource="{Binding ChatMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<av:HtmlPanel Text="{Binding HTML}" Width="400" MaxWidth="400"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5 0 0 0" VerticalAlignment="Center" FontWeight="Bold" Text="{Binding Username}" Width="95"/>
<TextBox Name="chatInput" AcceptsReturn="False" Width="250"/>
<Button Name="sendBtn" Background="Blue" Width="50" Height="35">
<i:Icon Value="mdi-send" FontSize="25" Foreground="LightGray"/>
</Button>
</StackPanel>
<vlc:VideoView MediaPlayer="{Binding Player}" IsVisible="False"/>
</StackPanel>
</Window>

View file

@ -0,0 +1,27 @@
using Avalonia.Controls;
using System;
using System.Linq;
namespace CollabVMClient.Views
{
public partial class CollabVMChatView : Window
{
public event EventHandler<string> ChatSend;
public CollabVMChatView()
{
InitializeComponent();
chatInput.KeyDown += (s, e) =>
{
if (e.Key == Avalonia.Input.Key.Return) SendChat();
};
sendBtn.Click += (s, e) => SendChat();
}
private void SendChat()
{
if (string.IsNullOrEmpty(chatInput.Text) || chatInput.Text.ToCharArray().All(c => c == ' ')) return;
ChatSend.Invoke(this, chatInput.Text);
chatInput.Text = "";
}
}
}

View file

@ -0,0 +1,10 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="768"
x:Class="CollabVMClient.Views.CollabVMScreenView"
Title="{Binding Title}"
Width="{Binding Width}" Height="{Binding Height}" CanResize="False">
<Image Name="screen" Source="{Binding Framebuffer}" Width="{Binding Width}" Height="{Binding Height}"/>
</Window>

View file

@ -0,0 +1,19 @@
using Avalonia.Controls;
using CollabVMClient.ViewModels;
namespace CollabVMClient.Views
{
public partial class CollabVMScreenView : Window
{
public CollabVMScreenView()
{
InitializeComponent();
}
public void RefreshScreen()
{
screen.Source = ((CollabVMScreenViewModel)DataContext).Framebuffer;
screen.InvalidateVisual();
}
}
}

View file

@ -0,0 +1,19 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="150"
x:Class="CollabVMClient.Views.DirectConnect"
Title="CollabVM - Direct Connect"
Width="300" Height="150" CanResize="False">
<StackPanel Orientation="Vertical">
<Label>URL</Label>
<TextBox Width="300" Name="url" HorizontalAlignment="Left"/>
<Label>Node</Label>
<TextBox Width="150" Name="node" HorizontalAlignment="Left"/>
<StackPanel Orientation="Horizontal">
<Button Name="connectBtn">Connect</Button>
<Button Name="cancelBtn">Cancel</Button>
</StackPanel>
</StackPanel>
</Window>

View file

@ -0,0 +1,39 @@
using Avalonia.Controls;
using MsBox.Avalonia;
using System;
namespace CollabVMClient.Views
{
public partial class DirectConnect : Window
{
public DirectConnect()
{
InitializeComponent();
connectBtn.Click += ConnectBtn_Click;
cancelBtn.Click += CancelBtn_Click;
}
private void CancelBtn_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Close();
}
private void ConnectBtn_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Uri u;
if (!Uri.TryCreate(url.Text, UriKind.Absolute, out u) || (u.Scheme != "ws" && u.Scheme != "wss"))
{
MessageBoxManager.GetMessageBoxStandard("Error", "URL must be a valid WebSocket url.", MsBox.Avalonia.Enums.ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error).ShowWindowDialogAsync(this);
return;
}
if (string.IsNullOrEmpty(node.Text))
{
MessageBoxManager.GetMessageBoxStandard("Error", "Node must not be empty", MsBox.Avalonia.Enums.ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Error).ShowWindowDialogAsync(this);
return;
}
var cvm = new CollabVMViewer(url.Text, node.Text);
cvm.Open();
Close();
}
}
}

View file

@ -0,0 +1,37 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:CollabVMClient.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:CollabVMClient.Views"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="800"
x:Class="CollabVMClient.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="CollabVM - Server List"
Width="400" Height="800" CanResize="False">
<Window.Styles>
</Window.Styles>
<StackPanel Orientation="Vertical">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_CollabVM">
<MenuItem Name="refresh" Header="Refresh" Command="{Binding RefreshList}"/>
<MenuItem Name="addServer" Header="Add Server" Command="{Binding AddServer}"/>
<MenuItem Name="directConnect" Header="Direct Connect"/>
<MenuItem Name="settings" Header="Settings"/>
<MenuItem Header="Exit" Name="exit"/>
</MenuItem>
</Menu>
</DockPanel>
<ScrollViewer Height="750">
<ItemsControl ItemsSource="{Binding Nodes}" Classes="nodelist">
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:CVMNode/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Window>

View file

@ -0,0 +1,27 @@
using Avalonia.Controls;
using System.IO;
using System.Text.Json;
namespace CollabVMClient.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
directConnect.Click += DirectConnect_Click;
exit.Click += Exit_Click;
addServer.CommandParameter = this;
}
private void Exit_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Close();
}
private void DirectConnect_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
new DirectConnect().ShowDialog(this);
}
}

View file

@ -0,0 +1,24 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="400"
x:Class="CollabVMClient.Views.UserList"
Title="{Binding Title}"
Width="300" Height="400" CanResize="False">
<Window.Styles>
<Style Selector="TextBlock.User">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Styles>
<ScrollViewer>
<ItemsControl Name="userList" ItemsSource="{Binding Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Classes="User" Text="{Binding Username}" Foreground="{Binding Foreground}" Background="{Binding Background}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Window>

View file

@ -0,0 +1,14 @@
using Avalonia.Controls;
namespace CollabVMClient.Views
{
public partial class UserList : Window
{
public UserList()
{
InitializeComponent();
}
public void ReloadUsers() => userList.InvalidateArrange();
}
}

6
Directory.Build.props Normal file
View file

@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.0.2</AvaloniaVersion>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Title>LibVLCSharp.Avalonia</Title>
<Summary>Avalonia integration for LibVLCSharp</Summary>
<Description>LibVLCSharp is a cross-platform audio and video API for .NET platforms based on VideoLAN's LibVLC Library. It provides a comprehensive multimedia API that can be used across mobile, server and desktop to render video and output audio. Mono, .NET Framework and .NET Core runtimes are supported.
LibVLCSharp.Avalonia contains the integration with Avalonia.
This package contains the views that allows to display a video played with LibVLCSharp
in a Avalonia app.
This package depends on LibVLCSharp as well as Avalonia.
LibVLC needs to be installed separately, see VideoLAN.LibVLC.* packages.
</Description>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>LibVLCSharp.Avalonia</RootNamespace>
<PackageId>LibVLCSharp.Avalonia</PackageId>
<PackageTags>$(PackageTags);avalonia</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.4" />
<PackageReference Include="LibVLCSharp" Version="3.7.0" />
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,18 @@
# LibVLCSharp.Avalonia
The official [Avalonia](https://github.com/AvaloniaUI/Avalonia) views for [LibVLCSharp](../LibVLCSharp/README.md).
This package contains the views that allows to display a video played with [LibVLCSharp](../LibVLCSharp/README.md)
in an Avalonia app.
This package depends on [LibVLCSharp](../LibVLCSharp/README.md) as well as [Avalonia](https://github.com/AvaloniaUI/Avalonia).
Supported frameworks:
- netstandard2.0
Supported platforms:
- Windows
- MacOS
- Linux

View file

@ -0,0 +1,115 @@
using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Platform;
using LibVLCSharp.Shared;
namespace LibVLCSharp.Avalonia
{
/// <summary>
/// Avalonia VideoView for Windows, Linux and Mac.
/// </summary>
public class VideoView : NativeControlHost
{
private IPlatformHandle? _platformHandle = null;
private MediaPlayer? _mediaPlayer = null;
/// <summary>
/// MediaPlayer Data Bound property
/// </summary>
/// <summary>
/// Defines the <see cref="MediaPlayer"/> property.
/// </summary>
public static readonly DirectProperty<VideoView, MediaPlayer?> MediaPlayerProperty =
AvaloniaProperty.RegisterDirect<VideoView, MediaPlayer?>(
nameof(MediaPlayer),
o => o.MediaPlayer,
(o, v) => o.MediaPlayer = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets the MediaPlayer that will be displayed.
/// </summary>
public MediaPlayer? MediaPlayer
{
get { return _mediaPlayer; }
set
{
if (ReferenceEquals(_mediaPlayer, value))
{
return;
}
Detach();
_mediaPlayer = value;
Attach();
}
}
private void Attach()
{
if(_mediaPlayer == null || _platformHandle == null || !IsInitialized)
return;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_mediaPlayer.Hwnd = _platformHandle.Handle;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_mediaPlayer.XWindow = (uint)_platformHandle.Handle;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_mediaPlayer.NsObject = _platformHandle.Handle;
}
}
private void Detach()
{
if (_mediaPlayer == null)
return;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_mediaPlayer.Hwnd = IntPtr.Zero;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_mediaPlayer.XWindow = 0;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_mediaPlayer.NsObject = IntPtr.Zero;
}
}
/// <inheritdoc />
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
_platformHandle = base.CreateNativeControlCore(parent);
if (_mediaPlayer == null)
return _platformHandle;
Attach();
return _platformHandle;
}
/// <inheritdoc />
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
Detach();
base.DestroyNativeControlCore(control);
if (_platformHandle != null)
{
_platformHandle = null;
}
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2" />
</packageSources>
</configuration>