├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question-or-other.md └── workflows │ └── dotnet.yml ├── .gitignore ├── 86BoxManager.API ├── 86BoxManager.API.csproj ├── IEnv.cs ├── IExecVars.cs ├── IExecutor.cs ├── IManager.cs ├── IMessageLoop.cs ├── IMessageReceiver.cs ├── IMessageSender.cs ├── IShell.cs ├── IVerInfo.cs └── IVm.cs ├── 86BoxManager.Common ├── 86BoxManager.Common.csproj ├── CommonExecVars.cs ├── CommonExecutor.cs ├── CommonManager.cs ├── CommonShell.cs └── CommonVerInfo.cs ├── 86BoxManager.Core ├── 86BoxManager.Core.csproj ├── Core │ └── Serializer.cs ├── Model │ ├── SortType.cs │ └── VM.cs ├── Registry │ ├── ConfigKey.cs │ ├── Configs.cs │ ├── Settings.cs │ └── ValueKind.cs └── Xplat │ ├── CurrentApp.cs │ ├── Platforms.cs │ └── Search.cs ├── 86BoxManager.Gtk ├── 86BoxManager.Gtk.csproj ├── Core │ ├── VMCenter.cs │ ├── VMHandler.cs │ └── VMWatch.cs ├── Model │ ├── Cache.cs │ └── VMRow.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ ├── 86Box-gray.ico │ ├── 86Box-gray.ma.png │ ├── 86Box-gray.png │ ├── 86Box-gray.sm.png │ ├── 86Box-gray.svg │ ├── vm-paused.png │ ├── vm-running.png │ ├── vm-stopped.png │ └── vm-waiting.png ├── Tools │ ├── Dialogs.cs │ ├── Resources.cs │ ├── ToolTips.cs │ └── Trays.cs └── View │ ├── dlgAddVM.Designer.cs │ ├── dlgAddVM.cs │ ├── dlgAddVM.glade │ ├── dlgCloneVM.Designer.cs │ ├── dlgCloneVM.cs │ ├── dlgCloneVM.glade │ ├── dlgEditVM.Designer.cs │ ├── dlgEditVM.cs │ ├── dlgEditVM.glade │ ├── dlgSettings.Designer.cs │ ├── dlgSettings.cs │ ├── dlgSettings.glade │ ├── frmMain.Designer.cs │ ├── frmMain.cs │ └── frmMain.glade ├── 86BoxManager.Linux ├── 86BoxManager.Linux.csproj ├── LinuxEnv.cs ├── LinuxManager.cs └── LinuxShell.cs ├── 86BoxManager.Mac ├── 86BoxManager.Mac.csproj ├── MacEnv.cs ├── MacManager.cs └── MacShell.cs ├── 86BoxManager.Unix ├── 86BoxManager.Unix.csproj ├── UnixExecutor.cs ├── UnixLoop.cs └── UnixManager.cs ├── 86BoxManager.Windows ├── 86BoxManager.Windows.csproj ├── Internal │ ├── Win32Imports.cs │ ├── WinExecutor.cs │ ├── WinLoop.cs │ └── WinVerInfo.cs ├── WinEnv.cs ├── WinManager.cs ├── WinShell.cs └── lib │ └── Interop.IWshRuntimeLibrary.dll ├── 86BoxManager.sln ├── 86BoxManager ├── 86BoxManager.csproj ├── App.axaml ├── App.axaml.cs ├── Assets │ ├── 86Box-gray.ico │ ├── 86Box-gray.ma.png │ ├── 86Box-gray.png │ └── 86Box-gray.sm.png ├── Core │ ├── VMCenter.cs │ ├── VMHandler.cs │ └── VMWatch.cs ├── Models │ └── Cache.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ ├── 86Box-gray.svg │ ├── vm-paused.png │ ├── vm-running.png │ ├── vm-stopped.png │ └── vm-waiting.png ├── Tools │ ├── Compat.cs │ ├── Dialogs.cs │ ├── Lifetimes.cs │ ├── Resources.cs │ ├── ToolTips.cs │ └── Trays.cs ├── ViewModels │ ├── MainModel.cs │ └── VMRow.cs ├── Views │ ├── dlgAddVM.axaml │ ├── dlgAddVM.axaml.cs │ ├── dlgCloneVM.axaml │ ├── dlgCloneVM.axaml.cs │ ├── dlgEditVM.axaml │ ├── dlgEditVM.axaml.cs │ ├── dlgSettings.axaml │ ├── dlgSettings.axaml.cs │ ├── frmMain.axaml │ └── frmMain.axaml.cs └── app.manifest ├── AUTHORS ├── LICENSE ├── README.md └── build.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Use this template only for bug reports. 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: daviunic 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | - Host OS: [e.g. WIndows 10 version 2004] 28 | - 86Box build number and type [e.g. 2529 regular, 2515 dev, etc.] 29 | - 86Box Manager Version [e.g. 1.7.1] 30 | - VM config file (86box.cfg) if applicable 31 | - VM name and full path if applicable 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Use this template only for feature requests. 4 | title: "[Feature]" 5 | labels: enhancement 6 | assignees: daviunic 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question-or-other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question or other 3 | about: Use this template only for questions or other kinds of issues. Do NOT use for 4 | bug reports or feature requests. 5 | title: '' 6 | labels: '' 7 | assignees: daviunic 8 | 9 | --- 10 | 11 | Ask away. 12 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | tags: [ "v*" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v3 21 | with: 22 | dotnet-version: 6.0.x 23 | 24 | - name: Restore dependencies 25 | run: dotnet restore 26 | 27 | - name: Build 28 | run: dotnet build --no-restore 29 | 30 | - name: Test 31 | run: dotnet test --no-build --verbosity normal 32 | 33 | - name: Publish 34 | if: startsWith(github.ref, 'refs/tags/') 35 | run: | 36 | sudo apt-get install -y zip 37 | ./build.sh 38 | 39 | - name: Release 40 | uses: softprops/action-gh-release@v1 41 | if: startsWith(github.ref, 'refs/tags/') 42 | with: 43 | files: pub/*.* 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_h.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *_wpftmp.csproj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush personal settings 296 | .cr/personal 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | # Local History for Visual Studio 334 | .localhistory/ 335 | /dist 336 | /pub 337 | .DS_Store 338 | -------------------------------------------------------------------------------- /86BoxManager.API/86BoxManager.API.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | _86BoxManager.API 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /86BoxManager.API/IEnv.cs: -------------------------------------------------------------------------------- 1 | namespace _86BoxManager.API 2 | { 3 | public interface IEnv 4 | { 5 | string MyComputer { get; } 6 | 7 | string UserProfile { get; } 8 | 9 | string[] ExeNames { get; } 10 | 11 | string MyDocuments { get; } 12 | 13 | string Desktop { get; } 14 | 15 | string[] GetProgramFiles(string appName); 16 | } 17 | } -------------------------------------------------------------------------------- /86BoxManager.API/IExecVars.cs: -------------------------------------------------------------------------------- 1 | namespace _86BoxManager.API 2 | { 3 | public interface IExecVars 4 | { 5 | string FileName { get; } 6 | 7 | string RomPath { get; } 8 | 9 | string LogFile { get; } 10 | 11 | string VmPath { get; } 12 | 13 | IVm Vm { get; } 14 | 15 | (string id, string hWnd)? Handle { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /86BoxManager.API/IExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace _86BoxManager.API 4 | { 5 | public interface IExecutor 6 | { 7 | ProcessStartInfo BuildStartInfo(IExecVars args); 8 | 9 | ProcessStartInfo BuildConfigInfo(IExecVars args); 10 | } 11 | } -------------------------------------------------------------------------------- /86BoxManager.API/IManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace _86BoxManager.API 4 | { 5 | public interface IManager 6 | { 7 | bool IsFirstInstance(string name); 8 | 9 | IntPtr RestoreAndFocus(string title, string handleTitle); 10 | 11 | bool IsProcessRunning(string name); 12 | 13 | IVerInfo GetBoxVersion(string exeDir); 14 | 15 | IMessageLoop GetLoop(IMessageReceiver callback); 16 | 17 | IMessageSender GetSender(); 18 | 19 | IExecutor GetExecutor(); 20 | } 21 | } -------------------------------------------------------------------------------- /86BoxManager.API/IMessageLoop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace _86BoxManager.API 4 | { 5 | public interface IMessageLoop 6 | { 7 | IntPtr GetHandle(); 8 | } 9 | } -------------------------------------------------------------------------------- /86BoxManager.API/IMessageReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace _86BoxManager.API 4 | { 5 | public interface IMessageReceiver 6 | { 7 | void OnEmulatorInit(IntPtr hWnd, uint vmId); 8 | void OnEmulatorShutdown(IntPtr hWnd); 9 | 10 | void OnVmPaused(IntPtr hWnd); 11 | void OnVmResumed(IntPtr hWnd); 12 | 13 | void OnDialogOpened(IntPtr hWnd); 14 | void OnDialogClosed(IntPtr hWnd); 15 | 16 | void OnManagerStartVm(string vmName); 17 | } 18 | } -------------------------------------------------------------------------------- /86BoxManager.API/IMessageSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace _86BoxManager.API 4 | { 5 | public interface IMessageSender 6 | { 7 | void DoVmRequestStop(IVm vm); 8 | void DoVmForceStop(IVm vm); 9 | 10 | void DoVmPause(IVm vm); 11 | void DoVmResume(IVm vm); 12 | 13 | void DoVmCtrlAltDel(IVm vm); 14 | void DoVmHardReset(IVm vm); 15 | 16 | void DoVmConfigure(IVm vm); 17 | 18 | void DoManagerStartVm(IntPtr hWnd, string vmName); 19 | } 20 | } -------------------------------------------------------------------------------- /86BoxManager.API/IShell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace _86BoxManager.API 4 | { 5 | public interface IShell 6 | { 7 | void CreateShortcut(string address, string name, string desc, string startup); 8 | 9 | void PushToForeground(IntPtr window); 10 | 11 | void PrepareAppId(string appId); 12 | 13 | void OpenFolder(string folder); 14 | 15 | void EditFile(string file); 16 | } 17 | } -------------------------------------------------------------------------------- /86BoxManager.API/IVerInfo.cs: -------------------------------------------------------------------------------- 1 | namespace _86BoxManager.API 2 | { 3 | public interface IVerInfo 4 | { 5 | int FilePrivatePart { get; } 6 | 7 | int FileMajorPart { get; } 8 | 9 | int FileMinorPart { get; } 10 | 11 | int FileBuildPart { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /86BoxManager.API/IVm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable InconsistentNaming 4 | 5 | namespace _86BoxManager.API 6 | { 7 | public interface IVm 8 | { 9 | /// 10 | /// Name of the virtual machine 11 | /// 12 | string Name { get; } 13 | 14 | /// 15 | /// Window handle for the VM once it's started 16 | /// 17 | IntPtr hWnd { get; } 18 | 19 | /// 20 | /// Callback to invoke when VM is gone 21 | /// 22 | Action OnExit { set; } 23 | } 24 | } -------------------------------------------------------------------------------- /86BoxManager.Common/86BoxManager.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | _86BoxManager.Common 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /86BoxManager.Common/CommonExecVars.cs: -------------------------------------------------------------------------------- 1 | using _86BoxManager.API; 2 | 3 | namespace _86BoxManager.Common 4 | { 5 | public sealed class CommonExecVars : IExecVars 6 | { 7 | public string FileName { get; set; } 8 | 9 | public string RomPath { get; set; } 10 | 11 | public string LogFile { get; set; } 12 | 13 | public string VmPath { get; set; } 14 | 15 | public IVm Vm { get; set; } 16 | 17 | public (string id, string hWnd)? Handle { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /86BoxManager.Common/CommonExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using _86BoxManager.API; 3 | 4 | namespace _86BoxManager.Common 5 | { 6 | public abstract class CommonExecutor : IExecutor 7 | { 8 | public virtual ProcessStartInfo BuildStartInfo(IExecVars args) 9 | { 10 | var info = new ProcessStartInfo(args.FileName); 11 | var ops = info.ArgumentList; 12 | if (!string.IsNullOrWhiteSpace(args.RomPath)) 13 | { 14 | ops.Add("-R"); 15 | ops.Add(args.RomPath); 16 | } 17 | if (!string.IsNullOrWhiteSpace(args.LogFile)) 18 | { 19 | ops.Add("-L"); 20 | ops.Add(args.LogFile); 21 | } 22 | ops.Add("-P"); 23 | ops.Add(args.VmPath); 24 | ops.Add("-V"); 25 | ops.Add(args.Vm.Name); 26 | return info; 27 | } 28 | 29 | public virtual ProcessStartInfo BuildConfigInfo(IExecVars args) 30 | { 31 | var info = new ProcessStartInfo(args.FileName); 32 | var ops = info.ArgumentList; 33 | ops.Add("--settings"); 34 | ops.Add("-P"); 35 | ops.Add(args.VmPath); 36 | return info; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /86BoxManager.Common/CommonManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | using _86BoxManager.API; 6 | 7 | namespace _86BoxManager.Common 8 | { 9 | public abstract class CommonManager : IManager 10 | { 11 | private IntPtr _lastEnemy; 12 | 13 | public virtual bool IsFirstInstance(string name) 14 | { 15 | var entry = Assembly.GetEntryAssembly()?.Location; 16 | if (entry != null) 17 | { 18 | var exeName = Path.GetFileNameWithoutExtension(entry); 19 | var myProcId = Environment.ProcessId; 20 | var processes = Process.GetProcessesByName(exeName); 21 | foreach (var proc in processes) 22 | if (proc.Id != myProcId) 23 | { 24 | _lastEnemy = new IntPtr(proc.Id); 25 | return false; 26 | } 27 | } 28 | return true; 29 | } 30 | 31 | public virtual IntPtr RestoreAndFocus(string title, string handleTitle) 32 | { 33 | return _lastEnemy; 34 | } 35 | 36 | public virtual bool IsProcessRunning(string name) 37 | { 38 | var processes = Process.GetProcessesByName(name); 39 | return processes.Length > 0; 40 | } 41 | 42 | public abstract IVerInfo GetBoxVersion(string exeDir); 43 | public abstract IMessageLoop GetLoop(IMessageReceiver callback); 44 | public abstract IMessageSender GetSender(); 45 | public abstract IExecutor GetExecutor(); 46 | } 47 | } -------------------------------------------------------------------------------- /86BoxManager.Common/CommonShell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using _86BoxManager.API; 4 | 5 | namespace _86BoxManager.Common 6 | { 7 | public abstract class CommonShell : IShell 8 | { 9 | public abstract void CreateShortcut(string address, string name, string desc, string startup); 10 | 11 | public virtual void PushToForeground(IntPtr window) 12 | { 13 | // NO-OP 14 | } 15 | 16 | public virtual void PrepareAppId(string appId) 17 | { 18 | // NO-OP 19 | } 20 | 21 | public virtual void OpenFolder(string folder) 22 | { 23 | Process.Start(new ProcessStartInfo(folder) 24 | { 25 | UseShellExecute = true 26 | }); 27 | } 28 | 29 | public virtual void EditFile(string file) 30 | { 31 | Process.Start(new ProcessStartInfo(file) 32 | { 33 | UseShellExecute = true 34 | }); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /86BoxManager.Common/CommonVerInfo.cs: -------------------------------------------------------------------------------- 1 | using _86BoxManager.API; 2 | 3 | namespace _86BoxManager.Common 4 | { 5 | public sealed class CommonVerInfo : IVerInfo 6 | { 7 | public int FilePrivatePart { get; set; } 8 | 9 | public int FileMajorPart { get; set; } 10 | 11 | public int FileMinorPart { get; set; } 12 | 13 | public int FileBuildPart { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /86BoxManager.Core/86BoxManager.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | _86BoxManager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /86BoxManager.Core/Core/Serializer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.Serialization.Formatters.Binary; 3 | 4 | #pragma warning disable SYSLIB0011 5 | 6 | namespace _86BoxManager.Core 7 | { 8 | public static class Serializer 9 | { 10 | private static readonly BinaryFormatter Bf = new(); 11 | 12 | public static T Read(byte[] array) 13 | { 14 | using var ms = new MemoryStream(array); 15 | var res = (T)Bf.Deserialize(ms); 16 | ms.Close(); 17 | return res; 18 | } 19 | 20 | public static byte[] Write(object obj) 21 | { 22 | using var ms = new MemoryStream(); 23 | var formatter = new BinaryFormatter(); 24 | formatter.Serialize(ms, obj); 25 | var data = ms.ToArray(); 26 | return data; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Model/SortType.cs: -------------------------------------------------------------------------------- 1 | namespace _86BoxManager.Models 2 | { 3 | public enum SortType 4 | { 5 | Ascending = 0, 6 | 7 | Descending 8 | } 9 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Model/VM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.API; 3 | 4 | // ReSharper disable InconsistentNaming 5 | 6 | namespace _86BoxManager.Models 7 | { 8 | [Serializable] //For serializing VMs so they can be stored in the registry 9 | public class VM : IVm 10 | { 11 | public IntPtr hWnd { get; set; } //Window handle for the VM once it's started 12 | public string Name { get; set; } //Name of the virtual machine 13 | public string Desc { get; set; } //Description 14 | public string Path { get; set; } //Path to config, nvr, etc. 15 | public int Status { get; set; } //Status 16 | public int Pid { get; set; } //Process ID of 86box executable running the VM 17 | 18 | public const int STATUS_STOPPED = 0; //VM is not running 19 | public const int STATUS_RUNNING = 1; //VM is running 20 | public const int STATUS_WAITING = 2; //VM is waiting for user response 21 | public const int STATUS_PAUSED = 3; //VM is paused 22 | 23 | public VM(){ 24 | Name = "defaultName"; 25 | Desc = "defaultDesc"; 26 | Path = "defaultPath"; 27 | Status = STATUS_STOPPED; 28 | hWnd = IntPtr.Zero; 29 | } 30 | 31 | public VM(string name, string desc, string path) 32 | { 33 | Name = name; 34 | Desc = desc; 35 | Path = path; 36 | Status = STATUS_STOPPED; 37 | hWnd = IntPtr.Zero; 38 | } 39 | 40 | public override string ToString() 41 | { 42 | return $"Name: {Name}, description: {Desc}, path: {Path}, status: {Status}"; 43 | } 44 | 45 | //Returns a lovely status string for use in UI 46 | public string GetStatusString() 47 | { 48 | switch (Status) 49 | { 50 | case STATUS_STOPPED: return "Stopped"; 51 | case STATUS_RUNNING: return "Running"; 52 | case STATUS_PAUSED: return "Paused"; 53 | case STATUS_WAITING: return "Waiting"; 54 | default: return "Invalid status"; 55 | } 56 | } 57 | 58 | public Action OnExit { get; set; } 59 | } 60 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Registry/ConfigKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json.Linq; 4 | using RegistryValueKind = _86BoxManager.Registry.ValueKind; 5 | 6 | namespace _86BoxManager.Registry 7 | { 8 | public sealed class ConfigKey 9 | { 10 | private readonly JObject _content; 11 | private readonly Action _closer; 12 | 13 | public ConfigKey(object o, Action closer) 14 | { 15 | _content = (JObject)o; 16 | _closer = closer; 17 | } 18 | 19 | public void SetValue(string key, object value, RegistryValueKind kind) 20 | { 21 | if (kind == RegistryValueKind.String) 22 | { 23 | _content[key] = value.ToString(); 24 | return; 25 | } 26 | if (kind == RegistryValueKind.DWord) 27 | { 28 | _content[key] = value is bool vb ? vb ? 1 : 0 : (int)value; 29 | return; 30 | } 31 | if (kind == RegistryValueKind.Binary) 32 | { 33 | _content[key] = (byte[])value; 34 | return; 35 | } 36 | throw new InvalidOperationException($"{key} => {kind}"); 37 | } 38 | 39 | public object GetValue(string key) 40 | { 41 | var raw = _content[key]; 42 | if (raw is JValue jv) 43 | { 44 | if (jv.Type == JTokenType.Integer) 45 | { 46 | return jv.Value(); 47 | } 48 | return jv.Value; 49 | } 50 | return raw; 51 | } 52 | 53 | public T GetValue(string key) 54 | { 55 | var raw = _content[key]; 56 | if (raw == null) 57 | return default; 58 | var conv = raw.ToObject(); 59 | return conv; 60 | } 61 | 62 | private void Save() 63 | { 64 | _closer?.Invoke(_content); 65 | } 66 | 67 | public void Close() 68 | { 69 | Save(); 70 | } 71 | 72 | public void DeleteValue(string key) 73 | { 74 | _content.Remove(key); 75 | } 76 | 77 | public IEnumerable GetValueNames() 78 | { 79 | foreach (var pair in _content) 80 | yield return pair.Key; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Registry/Configs.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using IOPath = System.IO.Path; 6 | 7 | namespace _86BoxManager.Registry 8 | { 9 | public static class Configs 10 | { 11 | private static readonly JsonSerializerSettings JsonConfig; 12 | private static readonly string BoxConfigName; 13 | private static readonly string VmxConfigName; 14 | 15 | static Configs() 16 | { 17 | JsonConfig = new JsonSerializerSettings 18 | { 19 | Formatting = Formatting.Indented 20 | }; 21 | var ass = typeof(Configs).Assembly; 22 | var loc = IOPath.GetFullPath(ass.Location); 23 | var dir = IOPath.GetDirectoryName(loc) ?? string.Empty; 24 | BoxConfigName = IOPath.Combine(dir, "86Box.json"); 25 | VmxConfigName = IOPath.Combine(dir, "86BoxVMs.json"); 26 | } 27 | 28 | private static void WriteJson(string fileName, object obj) 29 | { 30 | var json = JsonConvert.SerializeObject(obj, JsonConfig); 31 | File.WriteAllText(fileName, json, Encoding.UTF8); 32 | } 33 | 34 | private static object ReadJson(string fileName) 35 | { 36 | if (!File.Exists(fileName)) 37 | return null; 38 | var json = File.ReadAllText(fileName, Encoding.UTF8); 39 | return JsonConvert.DeserializeObject(json, JsonConfig); 40 | } 41 | 42 | public static void Create86BoxKey() 43 | { 44 | var obj = new JObject(); 45 | WriteJson(BoxConfigName, obj); 46 | } 47 | 48 | public static void Create86BoxVmKey() 49 | { 50 | var obj = new JObject(); 51 | WriteJson(VmxConfigName, obj); 52 | } 53 | 54 | public static ConfigKey Open86BoxKey(bool readWrite = false) 55 | { 56 | var obj = ReadJson(BoxConfigName); 57 | return obj == null 58 | ? null 59 | : new ConfigKey(obj, readWrite ? x => WriteJson(BoxConfigName, x) : null); 60 | } 61 | 62 | public static ConfigKey Open86BoxVmKey(bool readWrite = false) 63 | { 64 | var obj = ReadJson(VmxConfigName); 65 | return obj == null 66 | ? null 67 | : new ConfigKey(obj, readWrite ? x => WriteJson(VmxConfigName, x) : null); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Registry/Settings.cs: -------------------------------------------------------------------------------- 1 | using static _86BoxManager.Registry.ValueKind; 2 | 3 | namespace _86BoxManager.Registry 4 | { 5 | public static class Settings 6 | { 7 | public static Setting Default { get; } = new(); 8 | 9 | public sealed class Setting 10 | { 11 | private readonly ConfigKey _key; 12 | 13 | public Setting() 14 | { 15 | _key = Configs.Open86BoxKey(true); 16 | } 17 | 18 | private ConfigKey GetKey() => _key; 19 | 20 | public int NameColWidth 21 | { 22 | get => GetKey().GetValue(nameof(NameColWidth)) ?? 10; 23 | set => GetKey().SetValue(nameof(NameColWidth), value, DWord); 24 | } 25 | 26 | public int StatusColWidth 27 | { 28 | get => GetKey().GetValue(nameof(StatusColWidth)) ?? 10; 29 | set => GetKey().SetValue(nameof(StatusColWidth), value, DWord); 30 | } 31 | 32 | public int DescColWidth 33 | { 34 | get => GetKey().GetValue(nameof(DescColWidth)) ?? 10; 35 | set => GetKey().SetValue(nameof(DescColWidth), value, DWord); 36 | } 37 | 38 | public int PathColWidth 39 | { 40 | get => GetKey().GetValue(nameof(PathColWidth)) ?? 10; 41 | set => GetKey().SetValue(nameof(PathColWidth), value, DWord); 42 | } 43 | 44 | public void Save() 45 | { 46 | _key.Close(); 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Registry/ValueKind.cs: -------------------------------------------------------------------------------- 1 | namespace _86BoxManager.Registry 2 | { 3 | public enum ValueKind 4 | { 5 | Unknown = 0, 6 | 7 | DWord, 8 | 9 | String, 10 | 11 | Binary 12 | } 13 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Xplat/CurrentApp.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | 4 | namespace _86BoxManager.Xplat 5 | { 6 | public static class CurrentApp 7 | { 8 | public static string ProductVersion { get; } = ReadVersion(); 9 | 10 | public static string StartupPath { get; } = ReadStartup(); 11 | 12 | private static Assembly GetEntryAss() 13 | { 14 | return Assembly.GetEntryAssembly() ?? typeof(CurrentApp).Assembly; 15 | } 16 | 17 | private static string ReadStartup() 18 | { 19 | var ass = GetEntryAss(); 20 | var path = Path.GetFullPath(ass.Location); 21 | var dir = Path.GetDirectoryName(path); 22 | return dir; 23 | } 24 | 25 | private static string ReadVersion() 26 | { 27 | var ass = GetEntryAss(); 28 | var ver = ass.GetName().Version; 29 | return ver?.ToString(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Xplat/Platforms.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using _86BoxManager.API; 4 | using _86BoxManager.Linux; 5 | using _86BoxManager.Mac; 6 | using _86BoxManager.Windows; 7 | 8 | namespace _86BoxManager.Xplat 9 | { 10 | public static class Platforms 11 | { 12 | public static readonly IShell Shell; 13 | public static readonly IManager Manager; 14 | public static readonly IEnv Env; 15 | 16 | static Platforms() 17 | { 18 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 19 | { 20 | Shell = new LinuxShell(); 21 | Manager = new LinuxManager(); 22 | Env = new LinuxEnv(); 23 | return; 24 | } 25 | 26 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 27 | { 28 | Shell = new MacShell(); 29 | Manager = new MacManager(); 30 | Env = new MacEnv(); 31 | return; 32 | } 33 | 34 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 35 | { 36 | Shell = new WinShell(); 37 | Manager = new WinManager(); 38 | Env = new WinEnv(); 39 | return; 40 | } 41 | 42 | throw new InvalidOperationException("Not supported OS! Sorry!"); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /86BoxManager.Core/Xplat/Search.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace _86BoxManager.Xplat 4 | { 5 | public static class Search 6 | { 7 | public static string Find(string[] folders, string[] exeNames) 8 | { 9 | foreach (var folder in folders) 10 | foreach (var exeName in exeNames) 11 | { 12 | var exePath = Path.Combine(folder, exeName); 13 | if (!File.Exists(exePath)) 14 | continue; 15 | return folder; 16 | } 17 | return null; 18 | } 19 | 20 | public static string CheckTrail(this string path) 21 | { 22 | //To make sure there's a trailing backslash at the end, as other code using these strings expects it! 23 | if (!path.EndsWith(Path.DirectorySeparatorChar)) 24 | { 25 | path += Path.DirectorySeparatorChar; 26 | } 27 | return path; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/86BoxManager.Gtk.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | _86BoxManager 6 | 86Manager 7 | 8 | net6.0 9 | false 10 | 11 | Resources\86Box-gray.ico 12 | 13 | 14 | 15 | 16 | 17 | %(Filename)%(Extension) 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Always 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /86BoxManager.Gtk/Core/VMWatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using _86BoxManager.Models; 6 | using _86BoxManager.Model; 7 | using _86BoxManager.Tools; 8 | using Gtk; 9 | 10 | // ReSharper disable InconsistentNaming 11 | 12 | namespace _86BoxManager.Core 13 | { 14 | public sealed class VMWatch 15 | { 16 | private readonly BackgroundWorker _bgw; 17 | 18 | public VMWatch(BackgroundWorker bgw) 19 | { 20 | _bgw = bgw; 21 | _bgw.DoWork += background_DoWork; 22 | _bgw.RunWorkerCompleted += background_RunCompleted; 23 | } 24 | 25 | public void Dispose() 26 | { 27 | _bgw.DoWork -= background_DoWork; 28 | _bgw.RunWorkerCompleted -= background_RunCompleted; 29 | _bgw.Dispose(); 30 | } 31 | 32 | // Wait for the associated window of a VM to close 33 | private void background_DoWork(object sender, DoWorkEventArgs e) 34 | { 35 | var vm = e.Argument as VM; 36 | try 37 | { 38 | // Find the process associated with the VM 39 | var p = Process.GetProcessById(vm.Pid); 40 | 41 | // Wait for it to exit 42 | p.WaitForExit(); 43 | } 44 | catch (Exception ex) 45 | { 46 | Dialogs.ShowMessageBox("An error has occurred. Please provide the following details" + 47 | $" to the developer:\n{ex.Message}\n{ex.StackTrace}", 48 | MessageType.Error, ButtonsType.Ok, "Error"); 49 | } 50 | e.Result = vm; 51 | } 52 | 53 | // Update the UI once the VM's window is closed 54 | private void background_RunCompleted(object sender, RunWorkerCompletedEventArgs e) 55 | { 56 | var ui = Program.Root; 57 | var lstVMs = ui.lstVMs; 58 | var vm = e.Result as VM; 59 | 60 | var allItems = lstVMs.GetAllItems(); 61 | var selected = lstVMs.GetSelItems(); 62 | 63 | // Go through the listview, find the item representing the VM and update things accordingly 64 | foreach (var item in allItems) 65 | { 66 | if (item.Tag.Equals(vm)) 67 | { 68 | vm.Status = VM.STATUS_STOPPED; 69 | vm.hWnd = IntPtr.Zero; 70 | item.SetStatus(vm.GetStatusString()); 71 | item.SetIcon(vm.Status); 72 | 73 | if (vm.OnExit != null) 74 | { 75 | vm.OnExit(vm); 76 | vm.OnExit = null; 77 | } 78 | 79 | if (selected.Count > 0 && selected[0].Equals(item)) 80 | { 81 | ui.btnEdit.Sensitive = true; 82 | ui.btnDelete.Sensitive = true; 83 | ui.btnStart.Sensitive = true; 84 | ui.btnStart.Label = "Start"; 85 | ui.btnStart.SetToolTip("Start this virtual machine"); 86 | ui.btnConfigure.Sensitive = true; 87 | ui.btnPause.Sensitive = false; 88 | ui.btnPause.Label = "Pause"; 89 | ui.btnCtrlAltDel.Sensitive = false; 90 | ui.btnReset.Sensitive = false; 91 | } 92 | } 93 | } 94 | 95 | VMCenter.CountRefresh(); 96 | } 97 | 98 | public static bool TryWaitForInputIdle(Process process, int forceDelay) 99 | { 100 | try 101 | { 102 | return process.WaitForInputIdle(); 103 | } 104 | catch (InvalidOperationException) 105 | { 106 | Thread.Sleep(forceDelay); 107 | return false; 108 | } 109 | } 110 | 111 | public static uint GetTempId(VM vm) 112 | { 113 | /* This generates a VM ID on the fly from the VM path. The reason it's done this way is 114 | * it doesn't break existing VMs and doesn't require extensive modifications to this 115 | * legacy version for it to work with newer 86Box versions... 116 | * IDs also have to be unsigned for 86Box, but GetHashCode() returns signed and result 117 | * can be negative, so shift it up by int.MaxValue to ensure it's always positive. */ 118 | 119 | var tempid = vm.Path.GetHashCode(); 120 | uint id = 0; 121 | 122 | if (tempid < 0) 123 | id = (uint)(tempid + int.MaxValue); 124 | else 125 | id = (uint)tempid; 126 | 127 | return id; 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/Model/Cache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Gtk; 3 | using System.Linq; 4 | using _86BoxManager.Models; 5 | 6 | namespace _86BoxManager.Model 7 | { 8 | internal static class Cache 9 | { 10 | private static readonly IDictionary _tags; 11 | 12 | static Cache() 13 | { 14 | _tags = new Dictionary(); 15 | } 16 | 17 | public static void ClearAll(this TreeView view) 18 | { 19 | var store = view.GetStore(); 20 | store.Clear(); 21 | _tags.Clear(); 22 | } 23 | 24 | private static ListStore GetStore(this TreeView view) 25 | { 26 | var model = GetStore(view.Model); 27 | return model; 28 | } 29 | 30 | private static ListStore GetStore(this ITreeModel view) 31 | { 32 | var model = (ListStore)view; 33 | return model; 34 | } 35 | 36 | public static void ClearSelect(this TreeView view) 37 | { 38 | view.Selection.UnselectAll(); 39 | } 40 | 41 | public static List GetSelItems(this TreeView view) 42 | { 43 | return GetSelItems(view.Selection); 44 | } 45 | 46 | public static List GetSelItems(this TreeSelection selection) 47 | { 48 | var res = new List(); 49 | selection.SelectedForeach((model, _, it) => 50 | { 51 | var store = model.GetStore(); 52 | var key = GetKey(it, store); 53 | var vm = _tags[key]; 54 | res.Add(vm); 55 | }); 56 | return res; 57 | } 58 | 59 | public static List GetAllItems(this TreeView view) 60 | { 61 | var store = view.GetStore(); 62 | var res = store.OfType().Select(row => 63 | { 64 | var key = GetKey(row); 65 | var vm = _tags[key]; 66 | return vm; 67 | }).ToList(); 68 | return res; 69 | } 70 | 71 | private static string GetKey(this IReadOnlyList array) 72 | => (string)array[1]; 73 | 74 | private static string GetKey(this TreeIter it, ITreeModel store) 75 | => (string)store.GetValue(it, 1); 76 | 77 | public static void RemoveItem(this TreeView view, object item) 78 | { 79 | // TODO 80 | throw new System.NotImplementedException(); 81 | } 82 | 83 | public static VMRow FindItemWithText(string text) 84 | { 85 | if (!_tags.TryGetValue(text, out var row)) 86 | return null; 87 | 88 | return row; 89 | } 90 | 91 | public static VMRow Insert(this TreeView view, string key, VM vm) 92 | { 93 | var icon = VMRow.GetIcon(vm.Status); 94 | var name = vm.Name; 95 | var status = vm.GetStatusString(); 96 | var desc = vm.Desc; 97 | var path = vm.Path; 98 | 99 | var nv = new object[] { icon, name, status, desc, path }; 100 | var model = view.GetStore(); 101 | var it = model.AppendValues(nv); 102 | 103 | return _tags[key] = new VMRow(vm, it, model); 104 | } 105 | 106 | public static VMRow FindItemWithText(this TreeView view, string vmName) 107 | { 108 | var rows = view.GetAllItems(); 109 | var row = rows.FirstOrDefault(r => r.Tag.Name.Equals(vmName)); 110 | return row; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/Model/VMRow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using _86BoxManager.Models; 3 | using Gdk; 4 | using Gtk; 5 | using static _86BoxManager.Tools.Resources; 6 | 7 | // ReSharper disable InconsistentNaming 8 | 9 | namespace _86BoxManager.Model 10 | { 11 | internal sealed class VMRow 12 | { 13 | private static readonly IDictionary _icons; 14 | 15 | static VMRow() 16 | { 17 | var pause = LoadImage(FindResource("/vm-paused.png")); 18 | var wait = LoadImage(FindResource("/vm-waiting.png")); 19 | var run = LoadImage(FindResource("/vm-running.png")); 20 | var stop = LoadImage(FindResource("/vm-stopped.png")); 21 | 22 | _icons = new Dictionary 23 | { 24 | { VM.STATUS_PAUSED, pause }, 25 | { VM.STATUS_WAITING, wait }, 26 | { VM.STATUS_RUNNING, run }, 27 | { VM.STATUS_STOPPED, stop } 28 | }; 29 | } 30 | 31 | private readonly TreeIter _it; 32 | private readonly ListStore _store; 33 | 34 | public VMRow(VM real, TreeIter it, ListStore store) 35 | { 36 | Tag = real; 37 | _it = it; 38 | _store = store; 39 | } 40 | 41 | public VM Tag { get; } 42 | 43 | public static Pixbuf GetIcon(int status) 44 | { 45 | var pix = _icons[status]; 46 | return pix; 47 | } 48 | 49 | public void SetStatus(string text) 50 | { 51 | _store.SetValue(_it, 2, text); 52 | } 53 | 54 | public void SetIcon(int status) 55 | { 56 | var icon = GetIcon(status); 57 | _store.SetValue(_it, 0, icon); 58 | } 59 | 60 | public bool Focused 61 | { 62 | set => Selected = value; 63 | } 64 | 65 | public bool Selected 66 | { 67 | set 68 | { 69 | var ui = Program.Root; 70 | if (value) 71 | ui.lstVMs.Selection.SelectIter(_it); 72 | else 73 | ui.lstVMs.Selection.UnselectIter(_it); 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Tools; 3 | using _86BoxManager.View; 4 | using _86BoxManager.Xplat; 5 | using Gdk; 6 | using Gtk; 7 | using static _86BoxManager.Tools.Resources; 8 | 9 | namespace _86BoxManager 10 | { 11 | internal static class Program 12 | { 13 | //Get command line arguments 14 | public static string[] Args; 15 | 16 | //For grouping windows together in Win7+ taskbar 17 | private static readonly string AppId = "86Box.86Box"; 18 | 19 | internal static frmMain Root; 20 | 21 | [STAThread] 22 | private static void Main(string[] args) 23 | { 24 | Args = args; 25 | 26 | Platforms.Shell.PrepareAppId(AppId); 27 | Application.Init(); 28 | 29 | //Check if it is the very first and only instance running. 30 | //If it's not, we need to restore and focus the existing window, 31 | //as well as pass on any potential command line arguments 32 | if (CheckRunningManagerAndAbort(args)) 33 | return; 34 | 35 | //Then check if any instances of 86Box are already running and warn the user 36 | if (CheckRunningEmulatorAndAbort()) 37 | return; 38 | 39 | var app = new Application("org.86box.manager", GLib.ApplicationFlags.None); 40 | app.Register(GLib.Cancellable.Current); 41 | 42 | var win = new frmMain(); 43 | win.SetPosition(WindowPosition.Center); 44 | win.Icon = LoadIcon(); 45 | 46 | app.AddWindow(Root = win); 47 | 48 | win.Show(); 49 | Application.Run(); 50 | } 51 | 52 | internal static Pixbuf LoadIcon(int? size = null) 53 | => LoadImage(FindResource("/86Box-gray.png"), size); 54 | 55 | private static bool CheckRunningManagerAndAbort(string[] args) 56 | { 57 | const string name = "86Box Manager"; 58 | const string handleName = "86Box Manager Secret"; 59 | 60 | var firstInstance = Platforms.Manager.IsFirstInstance(name); 61 | if (!firstInstance) 62 | { 63 | var hWnd = Platforms.Manager.RestoreAndFocus(name, handleName); 64 | 65 | // If this second instance comes from a VM shortcut, we need to pass on the 66 | // command line arguments so the VM will start in the existing instance. 67 | // NOTE: This code will have to be modified in case more 68 | // command line arguments are added in the future. 69 | if (GetVmArg(args, out var message)) 70 | { 71 | var sender = Platforms.Manager.GetSender(); 72 | sender.DoManagerStartVm(hWnd, message); 73 | } 74 | return true; 75 | } 76 | return false; 77 | } 78 | 79 | internal static bool GetVmArg(string[] args, out string vmName) 80 | { 81 | if (args.Length == 2 && args[0] == "-S" && args[1] != null) 82 | { 83 | vmName = args[1]; 84 | return true; 85 | } 86 | vmName = default; 87 | return false; 88 | } 89 | 90 | private static bool CheckRunningEmulatorAndAbort() 91 | { 92 | var isRunning = Platforms.Manager.IsProcessRunning("86box") || 93 | Platforms.Manager.IsProcessRunning("86Box"); 94 | if (isRunning) 95 | { 96 | var result = Dialogs.ShowMessageBox("At least one instance of 86Box is already running. " + 97 | "It's not recommended that you run 86Box directly " + 98 | "outside of Manager. Do you want to continue at your own risk?", 99 | MessageType.Warning, ButtonsType.YesNo, "Warning"); 100 | if (result == (int)ResponseType.No) 101 | { 102 | return true; 103 | } 104 | } 105 | return false; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("86Box Manager")] 8 | [assembly: AssemblyDescription("A configuration manager for 86Box emulator")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("David Simunič and others")] 11 | [assembly: AssemblyProduct("86Box Manager")] 12 | [assembly: AssemblyCopyright("Copyright © 2018-2023 David Simunič and others")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("559f81b9-d1a5-45fc-aa69-e98f3b3926bb")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | 34 | [assembly: AssemblyVersion("1.7.6.0")] 35 | [assembly: AssemblyFileVersion("1.7.6.0")] -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/86Box-gray.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/86Box-gray.ico -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/86Box-gray.ma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/86Box-gray.ma.png -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/86Box-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/86Box-gray.png -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/86Box-gray.sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/86Box-gray.sm.png -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/vm-paused.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/vm-paused.png -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/vm-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/vm-running.png -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/vm-stopped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/vm-stopped.png -------------------------------------------------------------------------------- /86BoxManager.Gtk/Resources/vm-waiting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Gtk/Resources/vm-waiting.png -------------------------------------------------------------------------------- /86BoxManager.Gtk/Tools/Dialogs.cs: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | namespace _86BoxManager.Tools 4 | { 5 | public static class Dialogs 6 | { 7 | public static string SaveFile(string title, string dir, string filter, Window parent) 8 | { 9 | var dialog = new FileChooserDialog(title, parent, FileChooserAction.Save, 10 | "Cancel", ResponseType.Cancel, 11 | "Save", ResponseType.Accept); 12 | dialog.SetCurrentFolder(dir); 13 | 14 | if (filter != null) 15 | dialog.Filter = new FileFilter { Name = filter }; 16 | 17 | string result = null; 18 | if (dialog.Run() == (int)ResponseType.Accept) 19 | { 20 | result = dialog.Filename; 21 | } 22 | dialog.Destroy(); 23 | 24 | return result; 25 | } 26 | 27 | public static string SelectFolder(string title, string dir, Window parent) 28 | { 29 | var dialog = new FileChooserDialog(title, parent, FileChooserAction.SelectFolder, 30 | "Cancel", ResponseType.Cancel, 31 | "Open", ResponseType.Accept); 32 | dialog.SetCurrentFolder(dir); 33 | 34 | string result = null; 35 | if (dialog.Run() == (int)ResponseType.Accept) 36 | { 37 | result = dialog.Filename; 38 | } 39 | dialog.Destroy(); 40 | 41 | return result; 42 | } 43 | 44 | public static int ShowMessageBox(string text, MessageType type, 45 | ButtonsType btn = ButtonsType.Ok, string title = null, 46 | DialogFlags flags = DialogFlags.Modal, Window parent = null) 47 | { 48 | if (parent == null) 49 | parent = Program.Root; 50 | var dialog = new MessageDialog(parent, flags, type, btn, text); 51 | if (title != null) 52 | dialog.Title = title; 53 | var res = dialog.Run(); 54 | dialog.Destroy(); 55 | return res; 56 | } 57 | 58 | public static void RunDialog(this Window parent, Dialog dlg, System.Action after = null) 59 | { 60 | using (dlg) 61 | { 62 | dlg.TransientFor = parent; 63 | dlg.ShowAll(); 64 | dlg.Run(); 65 | after?.Invoke(); 66 | dlg.Destroy(); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/Tools/Resources.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Gdk; 3 | 4 | namespace _86BoxManager.Tools 5 | { 6 | internal static class Resources 7 | { 8 | public static Pixbuf LoadImage(Stream stream, int? size = null) 9 | { 10 | var res = new Pixbuf(stream); 11 | if (size == null) 12 | return res; 13 | 14 | var px = size.Value; 15 | return res.ScaleSimple(px, px, InterpType.Bilinear); 16 | } 17 | 18 | public static Stream FindResource(string path) 19 | { 20 | const string n = $".{nameof(Resources)}"; 21 | var type = typeof(Program); 22 | var dll = type.Assembly; 23 | var prefix = type.FullName?.Replace(".Program", n); 24 | var fullName = prefix + path.Replace('/', '.'); 25 | var resource = dll.GetManifestResourceStream(fullName); 26 | return resource; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/Tools/ToolTips.cs: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | namespace _86BoxManager.Tools 4 | { 5 | public static class ToolTips 6 | { 7 | public static void SetToolTip(this Widget widget, string text) 8 | { 9 | widget.TooltipText = text; 10 | widget.HasTooltip = true; 11 | } 12 | 13 | public static void UnsetToolTip(this Widget widget) 14 | { 15 | widget.TooltipText = string.Empty; 16 | widget.HasTooltip = false; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/Tools/Trays.cs: -------------------------------------------------------------------------------- 1 | using Gdk; 2 | using Gtk; 3 | 4 | #pragma warning disable CS0612 5 | 6 | namespace _86BoxManager.Tools 7 | { 8 | public static class Trays 9 | { 10 | public static void ApplyIcon(this StatusIcon trayIcon, Pixbuf pix) 11 | { 12 | trayIcon.Icon = pix; 13 | } 14 | 15 | public static void MakeVisible(this StatusIcon trayIcon, bool value) 16 | { 17 | trayIcon.Visible = value; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgAddVM.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Tools; 3 | using Gtk; 4 | using UI = Gtk.Builder.ObjectAttribute; 5 | using IOPath = System.IO.Path; 6 | 7 | namespace _86BoxManager.View 8 | { 9 | partial class dlgAddVM 10 | { 11 | private void InitializeComponent() 12 | { 13 | Shown += dlgAddVM_Load; 14 | cbxImport.Toggled += cbxImport_CheckedChanged; 15 | btnBrowse.Clicked += btnBrowse_Click; 16 | txtName.Changed += txtName_TextChanged; 17 | btnAdd.Clicked += btnAdd_Click; 18 | } 19 | 20 | [UI] private Label lblPath1 = null; 21 | [UI] private Entry txtName = null; 22 | [UI] private Entry txtDescription = null; 23 | [UI] private Entry txtImportPath = null; 24 | [UI] private CheckButton cbxOpenCFG = null; 25 | [UI] private CheckButton cbxStartVM = null; 26 | [UI] private CheckButton cbxImport = null; 27 | [UI] private Button btnBrowse = null; 28 | [UI] private Button btnAdd = null; 29 | } 30 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgAddVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Core; 3 | using _86BoxManager.Tools; 4 | using _86BoxManager.Xplat; 5 | using Gtk; 6 | using UI = Gtk.Builder.ObjectAttribute; 7 | using IOPath = System.IO.Path; 8 | 9 | namespace _86BoxManager.View 10 | { 11 | internal sealed partial class dlgAddVM : Dialog 12 | { 13 | public dlgAddVM() : this(new Builder("dlgAddVM.glade")) 14 | { 15 | InitializeComponent(); 16 | } 17 | 18 | private dlgAddVM(Builder builder) : base(builder.GetRawOwnedObject("dlgAddVM")) 19 | { 20 | builder.Autoconnect(this); 21 | DefaultResponse = ResponseType.Cancel; 22 | 23 | Response += Dialog_Response; 24 | } 25 | 26 | private void Dialog_Response(object o, ResponseArgs args) 27 | { 28 | Hide(); 29 | } 30 | 31 | private bool existingVM = false; // Is this importing an existing VM or not 32 | 33 | private void dlgAddVM_Load(object sender, EventArgs e) 34 | { 35 | lblPath1.Text = Program.Root.CfgPath; 36 | 37 | // Disable on start 38 | cbxImport_CheckedChanged(sender, e); 39 | txtName_TextChanged(sender, e); 40 | } 41 | 42 | private void cbxImport_CheckedChanged(object sender, EventArgs e) 43 | { 44 | var status = cbxImport.Active; 45 | existingVM = status; 46 | txtImportPath.IsEditable = txtImportPath.CanFocus = status; 47 | btnBrowse.Sensitive = btnBrowse.CanFocus = status; 48 | } 49 | 50 | private void btnBrowse_Click(object sender, EventArgs e) 51 | { 52 | var initDir = Platforms.Env.MyComputer; 53 | var text = "Select a folder where your virtual machine (configs, nvr folders, etc.) will be located"; 54 | 55 | var fileName = Dialogs.SelectFolder(text, initDir, this); 56 | 57 | if (!string.IsNullOrWhiteSpace(fileName)) 58 | { 59 | txtImportPath.Text = fileName; 60 | txtName.Text = IOPath.GetFileName(fileName); 61 | } 62 | } 63 | 64 | private void txtName_TextChanged(object sender, EventArgs e) 65 | { 66 | if (string.IsNullOrWhiteSpace(txtName.Text)) 67 | { 68 | btnAdd.Sensitive = false; 69 | txtName.UnsetToolTip(); 70 | return; 71 | } 72 | 73 | if (txtName.Text.IndexOfAny(IOPath.GetInvalidFileNameChars()) >= 0) 74 | { 75 | btnAdd.Sensitive = false; 76 | lblPath1.Text = "Invalid path"; 77 | txtName.SetToolTip("You cannot use the following characters" + 78 | " in the name: \\ / : * ? \" < > |"); 79 | return; 80 | } 81 | 82 | var cfgPath = Program.Root.CfgPath; 83 | btnAdd.Sensitive = true; 84 | lblPath1.Text = cfgPath + txtName.Text; 85 | lblPath1.SetToolTip(cfgPath + txtName.Text); 86 | } 87 | 88 | // Check if VM with this name already exists, and send the data to the main form for processing if it doesn't 89 | private void btnAdd_Click(object sender, EventArgs e) 90 | { 91 | if (VMCenter.CheckIfExists(txtName.Text)) 92 | { 93 | Dialogs.ShowMessageBox("A virtual machine with this name already exists. Please pick a different name.", 94 | MessageType.Error, ButtonsType.Ok, "Error"); 95 | return; 96 | } 97 | 98 | if (existingVM && string.IsNullOrWhiteSpace(txtImportPath.Text)) 99 | { 100 | Dialogs.ShowMessageBox("If you wish to import VM files, you must specify a path.", 101 | MessageType.Error, ButtonsType.Ok, "Error"); 102 | return; 103 | } 104 | 105 | if (existingVM) 106 | { 107 | VMCenter.Import(txtName.Text, txtDescription.Text, txtImportPath.Text, 108 | cbxOpenCFG.Active, cbxStartVM.Active, Program.Root); 109 | } 110 | else 111 | { 112 | VMCenter.Add(txtName.Text, txtDescription.Text, cbxOpenCFG.Active, cbxStartVM.Active); 113 | } 114 | 115 | Respond(ResponseType.Close); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgAddVM.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | Add a virtual machine 8 | False 9 | 320 10 | 260 11 | dialog 12 | 13 | 14 | True 15 | False 16 | 12 17 | 12 18 | 12 19 | 12 20 | vertical 21 | 22 | 23 | True 24 | False 25 | end 26 | 12 27 | 12 28 | 12 29 | True 30 | end 31 | 32 | 33 | Add 34 | True 35 | True 36 | False 37 | 38 | 39 | True 40 | True 41 | 0 42 | 43 | 44 | 45 | 46 | Cancel 47 | True 48 | True 49 | True 50 | 51 | 52 | True 53 | True 54 | 1 55 | 56 | 57 | 58 | 59 | False 60 | False 61 | 0 62 | 63 | 64 | 65 | 66 | Import VM files from: 67 | True 68 | True 69 | False 70 | True 71 | 72 | 73 | False 74 | True 75 | 1 76 | 77 | 78 | 79 | 80 | True 81 | False 82 | 83 | 84 | True 85 | True 86 | True 87 | Enter path 88 | 89 | 90 | False 91 | True 92 | 0 93 | 94 | 95 | 96 | 97 | Browse 98 | True 99 | True 100 | False 101 | 102 | 103 | False 104 | True 105 | 1 106 | 107 | 108 | 109 | 110 | False 111 | True 112 | 2 113 | 114 | 115 | 116 | 117 | True 118 | False 119 | Name: 120 | 121 | 122 | False 123 | True 124 | 3 125 | 126 | 127 | 128 | 129 | True 130 | True 131 | True 132 | Enter name 133 | 134 | 135 | False 136 | True 137 | 4 138 | 139 | 140 | 141 | 142 | True 143 | False 144 | Description: 145 | 146 | 147 | False 148 | True 149 | 5 150 | 151 | 152 | 153 | 154 | True 155 | True 156 | True 157 | Enter description 158 | 159 | 160 | False 161 | True 162 | 6 163 | 164 | 165 | 166 | 167 | True 168 | False 169 | Path: <path goes here> 170 | 171 | 172 | False 173 | True 174 | 7 175 | 176 | 177 | 178 | 179 | Start this virtual machine now 180 | True 181 | True 182 | False 183 | 20 184 | True 185 | 186 | 187 | False 188 | True 189 | 8 190 | 191 | 192 | 193 | 194 | Configure this virtual machine now 195 | True 196 | True 197 | False 198 | 10 199 | True 200 | 201 | 202 | False 203 | True 204 | 9 205 | 206 | 207 | 208 | 209 | 210 | btnCancel 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgCloneVM.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Tools; 3 | using Gtk; 4 | using UI = Gtk.Builder.ObjectAttribute; 5 | using IOPath = System.IO.Path; 6 | 7 | namespace _86BoxManager.View 8 | { 9 | partial class dlgCloneVM 10 | { 11 | private void InitializeComponent() 12 | { 13 | Shown += dlgCloneVM_Load; 14 | txtName.Changed += txtName_TextChanged; 15 | btnClone.Clicked += btnClone_Click; 16 | } 17 | 18 | [UI] private Label lblPath1 = null; 19 | [UI] private Label lblOldVM = null; 20 | [UI] private Entry txtName = null; 21 | [UI] private Entry txtDescription = null; 22 | [UI] private Button btnClone = null; 23 | [UI] private CheckButton cbxOpenCFG = null; 24 | [UI] private CheckButton cbxStartVM = null; 25 | } 26 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgCloneVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Core; 3 | using _86BoxManager.Tools; 4 | using Gtk; 5 | using UI = Gtk.Builder.ObjectAttribute; 6 | using IOPath = System.IO.Path; 7 | 8 | namespace _86BoxManager.View 9 | { 10 | internal sealed partial class dlgCloneVM : Dialog 11 | { 12 | // Path of the VM to be cloned 13 | private readonly string _oldPath; 14 | 15 | public dlgCloneVM(string oldPath) : this(new Builder("dlgCloneVM.glade")) 16 | { 17 | InitializeComponent(); 18 | _oldPath = oldPath; 19 | } 20 | 21 | private dlgCloneVM(Builder builder) : base(builder.GetRawOwnedObject("dlgCloneVM")) 22 | { 23 | builder.Autoconnect(this); 24 | DefaultResponse = ResponseType.Cancel; 25 | 26 | Response += Dialog_Response; 27 | } 28 | 29 | private void Dialog_Response(object o, ResponseArgs args) 30 | { 31 | Hide(); 32 | } 33 | 34 | private void dlgCloneVM_Load(object sender, EventArgs e) 35 | { 36 | var cfgPath = Program.Root.CfgPath; 37 | lblPath1.Text = cfgPath; 38 | lblOldVM.Text = $@"Virtual machine ""{IOPath.GetFileName(_oldPath)}"" will be cloned into:"; 39 | } 40 | 41 | private void txtName_TextChanged(object sender, EventArgs e) 42 | { 43 | if (string.IsNullOrWhiteSpace(txtName.Text)) 44 | { 45 | btnClone.Sensitive = false; 46 | txtName.UnsetToolTip(); 47 | return; 48 | } 49 | 50 | if (txtName.Text.IndexOfAny(IOPath.GetInvalidFileNameChars()) >= 0) 51 | { 52 | btnClone.Sensitive = false; 53 | lblPath1.Text = "Invalid path"; 54 | txtName.SetToolTip("You cannot use the following characters" + 55 | " in the name: \\ / : * ? \" < > |"); 56 | return; 57 | } 58 | 59 | var cfgPath = Program.Root.CfgPath; 60 | btnClone.Sensitive = true; 61 | lblPath1.Text = cfgPath + txtName.Text; 62 | lblPath1.SetToolTip(cfgPath + txtName.Text); 63 | } 64 | 65 | private void btnClone_Click(object sender, EventArgs e) 66 | { 67 | if (VMCenter.CheckIfExists(txtName.Text)) 68 | { 69 | Dialogs.ShowMessageBox("A virtual machine with this name already exists. Please pick a different name.", 70 | MessageType.Error, ButtonsType.Ok, "Error"); 71 | return; 72 | } 73 | 74 | if (txtName.Text.IndexOfAny(IOPath.GetInvalidFileNameChars()) >= 0) 75 | { 76 | Dialogs.ShowMessageBox("There are invalid characters in the name you specified. " + 77 | "You can't use the following characters: \\ / : * ? \" < > |", 78 | MessageType.Error, ButtonsType.Ok, "Error"); 79 | return; 80 | } 81 | 82 | // Just import stuff from the existing VM 83 | VMCenter.Import(txtName.Text, txtDescription.Text, _oldPath, cbxOpenCFG.Active, 84 | cbxStartVM.Active, Program.Root); 85 | 86 | Respond(ResponseType.Close); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgCloneVM.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | Clone a virtual machine 8 | False 9 | 320 10 | 260 11 | dialog 12 | 13 | 14 | True 15 | False 16 | 12 17 | 12 18 | 12 19 | 12 20 | vertical 21 | 22 | 23 | False 24 | end 25 | 26 | 27 | Clone 28 | True 29 | True 30 | False 31 | 32 | 33 | True 34 | True 35 | 0 36 | 37 | 38 | 39 | 40 | Cancel 41 | True 42 | True 43 | True 44 | 45 | 46 | True 47 | True 48 | 1 49 | 50 | 51 | 52 | 53 | False 54 | False 55 | 0 56 | 57 | 58 | 59 | 60 | True 61 | False 62 | 10 63 | Virtual Machine <name here> will be cloned into: 64 | 65 | 66 | False 67 | True 68 | 1 69 | 70 | 71 | 72 | 73 | True 74 | False 75 | Name: 76 | 77 | 78 | False 79 | True 80 | 2 81 | 82 | 83 | 84 | 85 | True 86 | True 87 | True 88 | Enter name 89 | 90 | 91 | False 92 | True 93 | 3 94 | 95 | 96 | 97 | 98 | True 99 | False 100 | Description: 101 | 102 | 103 | False 104 | True 105 | 4 106 | 107 | 108 | 109 | 110 | True 111 | True 112 | True 113 | Enter description 114 | 115 | 116 | False 117 | True 118 | 5 119 | 120 | 121 | 122 | 123 | True 124 | False 125 | Path: <path goes here> 126 | 127 | 128 | False 129 | True 130 | 6 131 | 132 | 133 | 134 | 135 | Start this virtual machine now 136 | True 137 | True 138 | False 139 | 20 140 | True 141 | 142 | 143 | False 144 | True 145 | 7 146 | 147 | 148 | 149 | 150 | Configure this virtual machine now 151 | True 152 | True 153 | False 154 | 10 155 | True 156 | 157 | 158 | False 159 | True 160 | 8 161 | 162 | 163 | 164 | 165 | 166 | btnCancel 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgEditVM.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Tools; 3 | using Gtk; 4 | using UI = Gtk.Builder.ObjectAttribute; 5 | using IOPath = System.IO.Path; 6 | 7 | namespace _86BoxManager.View 8 | { 9 | partial class dlgEditVM 10 | { 11 | private void InitializeComponent() 12 | { 13 | Shown += dlgEditVM_Load; 14 | txtName.Changed += txtName_TextChanged; 15 | btnApply.Clicked += btnApply_Click; 16 | } 17 | 18 | [UI] private Entry txtName = null; 19 | [UI] private Entry txtDesc = null; 20 | [UI] private Label lblPath1 = null; 21 | [UI] private Button btnApply = null; 22 | } 23 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgEditVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Core; 3 | using _86BoxManager.Models; 4 | using _86BoxManager.Tools; 5 | using Gtk; 6 | using UI = Gtk.Builder.ObjectAttribute; 7 | using IOPath = System.IO.Path; 8 | 9 | namespace _86BoxManager.View 10 | { 11 | internal sealed partial class dlgEditVM : Dialog 12 | { 13 | public dlgEditVM() : this(new Builder("dlgEditVM.glade")) 14 | { 15 | InitializeComponent(); 16 | } 17 | 18 | private dlgEditVM(Builder builder) : base(builder.GetRawOwnedObject("dlgEditVM")) 19 | { 20 | builder.Autoconnect(this); 21 | DefaultResponse = ResponseType.Cancel; 22 | 23 | Response += Dialog_Response; 24 | } 25 | 26 | private void Dialog_Response(object o, ResponseArgs args) 27 | { 28 | Hide(); 29 | } 30 | 31 | private VM vm = null; //VM to be edited 32 | private string originalName; //Original name of the VM 33 | 34 | // Load the data for selected VM 35 | private void dlgEditVM_Load(object sender, EventArgs e) 36 | { 37 | vm = Program.Root.GetFocusedVm().Tag; 38 | originalName = vm.Name; 39 | txtName.Text = vm.Name; 40 | txtDesc.Text = vm.Desc; 41 | lblPath1.Text = vm.Path; 42 | } 43 | 44 | private void btnApply_Click(object sender, EventArgs e) 45 | { 46 | // Check if a VM with this name already exists 47 | if (!originalName.Equals(txtName.Text) && VMCenter.CheckIfExists(txtName.Text)) 48 | { 49 | Dialogs.ShowMessageBox("A virtual machine with this name already exists. Please pick a different name.", 50 | MessageType.Error, ButtonsType.Ok, "Error"); 51 | return; 52 | } 53 | 54 | if (txtName.Text.IndexOfAny(IOPath.GetInvalidFileNameChars()) >= 0) 55 | { 56 | Dialogs.ShowMessageBox("There are invalid characters in the name you specified. " + 57 | "You can't use the following characters: \\ / : * ? \" < > |", 58 | MessageType.Error, ButtonsType.Ok, "Error"); 59 | return; 60 | } 61 | 62 | VMCenter.Edit(txtName.Text, txtDesc.Text); 63 | 64 | Respond(ResponseType.Close); 65 | } 66 | 67 | private void txtName_TextChanged(object sender, EventArgs e) 68 | { 69 | // Check for empty strings etc. 70 | if (string.IsNullOrWhiteSpace(txtName.Text)) 71 | { 72 | btnApply.Sensitive = false; 73 | return; 74 | } 75 | 76 | var cfgPath = Program.Root.CfgPath; 77 | btnApply.Sensitive = true; 78 | lblPath1.Text = cfgPath + txtName.Text; 79 | lblPath1.SetToolTip(cfgPath + txtName.Text); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgEditVM.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | Edit a virtual machine 8 | False 9 | 320 10 | 260 11 | dialog 12 | 13 | 14 | True 15 | False 16 | 12 17 | 12 18 | 12 19 | 12 20 | vertical 21 | 22 | 23 | True 24 | False 25 | end 26 | 27 | 28 | Apply 29 | True 30 | True 31 | False 32 | 33 | 34 | True 35 | True 36 | 0 37 | 38 | 39 | 40 | 41 | Cancel 42 | True 43 | True 44 | True 45 | 46 | 47 | True 48 | True 49 | 1 50 | 51 | 52 | 53 | 54 | False 55 | False 56 | 0 57 | 58 | 59 | 60 | 61 | True 62 | False 63 | Name: 64 | 65 | 66 | False 67 | True 68 | 1 69 | 70 | 71 | 72 | 73 | True 74 | True 75 | 10 76 | 10 77 | True 78 | Enter name 79 | 80 | 81 | False 82 | True 83 | 2 84 | 85 | 86 | 87 | 88 | True 89 | False 90 | Description: 91 | 92 | 93 | False 94 | True 95 | 3 96 | 97 | 98 | 99 | 100 | True 101 | True 102 | 10 103 | 10 104 | True 105 | Enter description 106 | 107 | 108 | False 109 | True 110 | 4 111 | 112 | 113 | 114 | 115 | True 116 | False 117 | 20 118 | Path: <path goes here> 119 | 120 | 121 | False 122 | True 123 | 5 124 | 125 | 126 | 127 | 128 | 129 | btnCancel 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/dlgSettings.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Tools; 3 | using Gtk; 4 | using UI = Gtk.Builder.ObjectAttribute; 5 | using IOPath = System.IO.Path; 6 | 7 | namespace _86BoxManager.View 8 | { 9 | partial class dlgSettings 10 | { 11 | private void InitializeComponent() 12 | { 13 | aboutImg.Pixbuf = Program.LoadIcon(64); 14 | Shown += dlgSettings_Load; 15 | Close += dlgSettings_FormClosing; 16 | 17 | btnApply.Clicked += btnApply_Click; 18 | btnOK.Clicked += btnOK_Click; 19 | btnBrowse1.Clicked += btnBrowse1_Click; 20 | btnBrowse2.Clicked += btnBrowse2_Click; 21 | btnBrowse3.Clicked += btnBrowse3_Click; 22 | btnDefaults.Clicked += btnDefaults_Click; 23 | 24 | lnkGithub2.Clicked += lnkGithub2_LinkClicked; 25 | lnkGithub.Clicked += lnkGithub_LinkClicked; 26 | 27 | txtEXEdir.Changed += txt_TextChanged; 28 | txtCFGdir.Changed += txt_TextChanged; 29 | 30 | cbxShowConsole.Toggled += cbx_CheckedChanged; 31 | cbxMinimizeTray.Toggled += cbx_CheckedChanged; 32 | cbxCloseTray.Toggled += cbx_CheckedChanged; 33 | cbxMinimize.Toggled += cbx_CheckedChanged; 34 | cbxGrid.Toggled += cbx_CheckedChanged; 35 | cbxLogging.Toggled += cbxLogging_CheckedChanged; 36 | } 37 | 38 | [UI] private Label lbl86BoxVer1 = null; 39 | [UI] private Label lblVersion1 = null; 40 | [UI] private Button btnApply = null; 41 | [UI] private Button btnDefaults = null; 42 | [UI] private Button btnOK = null; 43 | [UI] private Entry txtEXEdir = null; 44 | [UI] private Entry txtCFGdir = null; 45 | [UI] private Entry txtLogPath = null; 46 | [UI] private CheckButton cbxLogging = null; 47 | [UI] private CheckButton cbxMinimize = null; 48 | [UI] private CheckButton cbxShowConsole = null; 49 | [UI] private CheckButton cbxMinimizeTray = null; 50 | [UI] private CheckButton cbxGrid = null; 51 | [UI] private CheckButton cbxCloseTray = null; 52 | [UI] private Button btnBrowse3 = null; 53 | [UI] private Button btnBrowse2 = null; 54 | [UI] private Button btnBrowse1 = null; 55 | [UI] private LinkButton lnkGithub = null; 56 | [UI] private LinkButton lnkGithub2 = null; 57 | [UI] private Image aboutImg = null; 58 | } 59 | } -------------------------------------------------------------------------------- /86BoxManager.Gtk/View/frmMain.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Tools; 3 | using Gtk; 4 | using UI = Gtk.Builder.ObjectAttribute; 5 | using IOPath = System.IO.Path; 6 | 7 | namespace _86BoxManager.View 8 | { 9 | partial class frmMain 10 | { 11 | private void InitializeComponent() 12 | { 13 | Shown += frmMain_Load; 14 | WindowStateEvent += Window_StateChanged; 15 | ResizeChecked += frmMain_Resize; 16 | 17 | btnAdd.Clicked += btnAdd_Click; 18 | btnEdit.Clicked += btnEdit_Click; 19 | btnStart.Clicked += btnStart_Click; 20 | btnSettings.Clicked += btnSettings_Click; 21 | btnConfigure.Clicked += btnConfigure_Click; 22 | btnCtrlAltDel.Clicked += btnCtrlAltDel_Click; 23 | btnReset.Clicked += btnReset_Click; 24 | btnPause.Clicked += btnPause_Click; 25 | btnDelete.Clicked += btnDelete_Click; 26 | 27 | lstVMs.Selection.Changed += lstVMs_SelectedIndexChanged; 28 | lstVMs.ButtonReleaseEvent += OnTreeButtonRelease; 29 | lstVMs.ButtonPressEvent += lstVMs_MouseDoubleClick; 30 | lstVMs.KeyReleaseEvent += lstVMs_KeyDown; 31 | 32 | clmName.Clicked += lstVMs_ColumnClick; 33 | clmStatus.Clicked += lstVMs_ColumnClick; 34 | clmDesc.Clicked += lstVMs_ColumnClick; 35 | clmPath.Clicked += lstVMs_ColumnClick; 36 | 37 | openConfigFileToolStripMenuItem.Clicked += openConfigFileToolStripMenuItem_Click; 38 | openConfigFileToolStripMenuItem.Sensitive = true; 39 | killToolStripMenuItem.Clicked += killToolStripMenuItem_Click; 40 | killToolStripMenuItem.Sensitive = true; 41 | wipeToolStripMenuItem.Clicked += wipeToolStripMenuItem_Click; 42 | wipeToolStripMenuItem.Sensitive = true; 43 | cloneToolStripMenuItem.Clicked += cloneToolStripMenuItem_Click; 44 | cloneToolStripMenuItem.Sensitive = true; 45 | pauseToolStripMenuItem.Clicked += pauseToolStripMenuItem_Click; 46 | pauseToolStripMenuItem.Sensitive = true; 47 | hardResetToolStripMenuItem.Clicked += hardResetToolStripMenuItem_Click; 48 | hardResetToolStripMenuItem.Sensitive = true; 49 | deleteToolStripMenuItem.Clicked += deleteToolStripMenuItem_Click; 50 | deleteToolStripMenuItem.Sensitive = true; 51 | editToolStripMenuItem.Clicked += editToolStripMenuItem_Click; 52 | editToolStripMenuItem.Sensitive = true; 53 | openFolderToolStripMenuItem.Clicked += openFolderToolStripMenuItem_Click; 54 | openFolderToolStripMenuItem.Sensitive = true; 55 | configureToolStripMenuItem.Clicked += configureToolStripMenuItem_Click; 56 | configureToolStripMenuItem.Sensitive = true; 57 | resetCTRLALTDELETEToolStripMenuItem.Clicked += resetCTRLALTDELETEToolStripMenuItem_Click; 58 | resetCTRLALTDELETEToolStripMenuItem.Sensitive = true; 59 | startToolStripMenuItem.Clicked += startToolStripMenuItem_Click; 60 | startToolStripMenuItem.Sensitive = true; 61 | createADesktopShortcutToolStripMenuItem.Clicked += createADesktopShortcutToolStripMenuItem_Click; 62 | createADesktopShortcutToolStripMenuItem.Sensitive = true; 63 | 64 | trayIcon.ApplyIcon(Program.LoadIcon()); 65 | trayIcon.PopupMenu += OnTrayPopup; 66 | trayIcon.ButtonPressEvent += trayIcon_MouseDoubleClick; 67 | open86BoxManagerToolStripMenuItem.Activated += open86BoxManagerToolStripMenuItem_Click; 68 | settingsToolStripMenuItem.Activated += settingsToolStripMenuItem_Click; 69 | exitToolStripMenuItem.Activated += exitToolStripMenuItem_Click; 70 | } 71 | 72 | [UI] private Button btnAdd = null; 73 | [UI] internal Button btnEdit = null; 74 | [UI] private Button btnSettings = null; 75 | [UI] internal Button btnPause = null; 76 | [UI] internal Button btnCtrlAltDel = null; 77 | [UI] internal Button btnReset = null; 78 | [UI] internal Button btnDelete = null; 79 | [UI] internal Button btnStart = null; 80 | [UI] internal Button btnConfigure = null; 81 | [UI] internal TreeView lstVMs = null; 82 | [UI] private PopoverMenu lstVMpop = null; 83 | [UI] internal Label lblVMCount = null; 84 | [UI] private TreeViewColumn clmName = null; 85 | [UI] private TreeViewColumn clmStatus = null; 86 | [UI] private TreeViewColumn clmDesc = null; 87 | [UI] private TreeViewColumn clmPath = null; 88 | 89 | [UI] private ModelButton configureToolStripMenuItem = null; 90 | [UI] internal ModelButton pauseToolStripMenuItem = null; 91 | [UI] internal ModelButton startToolStripMenuItem = null; 92 | [UI] private ModelButton resetCTRLALTDELETEToolStripMenuItem = null; 93 | [UI] private ModelButton wipeToolStripMenuItem = null; 94 | [UI] private ModelButton deleteToolStripMenuItem = null; 95 | [UI] private ModelButton killToolStripMenuItem = null; 96 | [UI] private ModelButton hardResetToolStripMenuItem = null; 97 | [UI] private ModelButton cloneToolStripMenuItem = null; 98 | [UI] private ModelButton editToolStripMenuItem = null; 99 | [UI] private ModelButton openFolderToolStripMenuItem = null; 100 | [UI] private ModelButton openConfigFileToolStripMenuItem = null; 101 | [UI] private ModelButton createADesktopShortcutToolStripMenuItem = null; 102 | 103 | [UI] private StatusIcon trayIcon = null; 104 | [UI] private Menu cmsTrayIcon = null; 105 | [UI] private MenuItem exitToolStripMenuItem = null; 106 | [UI] private MenuItem settingsToolStripMenuItem = null; 107 | [UI] private MenuItem open86BoxManagerToolStripMenuItem = null; 108 | } 109 | } -------------------------------------------------------------------------------- /86BoxManager.Linux/86BoxManager.Linux.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | _86BoxManager.Linux 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /86BoxManager.Linux/LinuxEnv.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using _86BoxManager.API; 4 | 5 | namespace _86BoxManager.Linux 6 | { 7 | public sealed class LinuxEnv : IEnv 8 | { 9 | public LinuxEnv() 10 | { 11 | MyComputer = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer); 12 | Desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 13 | UserProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 14 | 15 | var fakeDoc = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 16 | MyDocuments = Path.Combine(fakeDoc, "Documents"); 17 | 18 | ExeNames = new[] { "86Box" }; 19 | } 20 | 21 | public string[] ExeNames { get; } 22 | public string MyComputer { get; } 23 | public string UserProfile { get; } 24 | public string MyDocuments { get; } 25 | public string Desktop { get; } 26 | 27 | public string[] GetProgramFiles(string appName) 28 | { 29 | var folders = new[] 30 | { 31 | Path.Combine(UserProfile, "Portable", appName), 32 | Path.Combine("/opt", appName), 33 | "/usr/local/bin", 34 | "/usr/bin" 35 | }; 36 | return folders; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /86BoxManager.Linux/LinuxManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using _86BoxManager.API; 4 | using _86BoxManager.Common; 5 | using _86BoxManager.Unix; 6 | 7 | namespace _86BoxManager.Linux 8 | { 9 | public sealed class LinuxManager : UnixManager 10 | { 11 | public LinuxManager() : base(GetTmpDir()) { } 12 | 13 | public override IVerInfo GetBoxVersion(string exeDir) 14 | { 15 | if (string.IsNullOrWhiteSpace(exeDir) || !Directory.Exists(exeDir)) 16 | { 17 | // Not found! 18 | return null; 19 | } 20 | var info = new CommonVerInfo(); 21 | var appImage = Directory.GetFiles(exeDir, "86Box-*.AppImage").FirstOrDefault(); 22 | if (appImage != null) 23 | { 24 | var full = Path.GetFileNameWithoutExtension(appImage); 25 | var build = full.Split('-').LastOrDefault(); 26 | 27 | // HACK: Set version because we can't read the ELF version 28 | if (build == "b4311") 29 | { 30 | info.FilePrivatePart = int.Parse(build.TrimStart('b')); 31 | info.FileMinorPart = 11; 32 | info.FileMajorPart = 3; 33 | info.FileBuildPart = 0; 34 | } 35 | } 36 | return info; 37 | } 38 | 39 | public static string GetTmpDir() => "/tmp"; 40 | } 41 | } -------------------------------------------------------------------------------- /86BoxManager.Linux/LinuxShell.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using _86BoxManager.Common; 4 | 5 | namespace _86BoxManager.Linux 6 | { 7 | public sealed class LinuxShell : CommonShell 8 | { 9 | public override void CreateShortcut(string address, string name, string desc, string startup) 10 | { 11 | var fileName = address.Replace(".lnk", ".desktop"); 12 | var myExe = Path.Combine(startup, "86Manager"); 13 | var myIcon = Path.Combine(startup, "Resources", "86Box-gray.svg"); 14 | var lines = new[] 15 | { 16 | "[Desktop Entry]", 17 | "Version=1.0", 18 | "Type=Application", 19 | $"Name={name}", 20 | @$"Exec=""{myExe}"" -S ""{name}""", 21 | $"Icon={myIcon}", 22 | $"Comment={desc}", 23 | "Terminal=false", 24 | "Categories=Game;Emulator;", 25 | "StartupWMClass=86box-vm", 26 | "StartupNotify=true" 27 | }; 28 | var bom = new UTF8Encoding(false); 29 | File.WriteAllLines(fileName, lines, bom); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /86BoxManager.Mac/86BoxManager.Mac.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | _86BoxManager.Mac 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /86BoxManager.Mac/MacEnv.cs: -------------------------------------------------------------------------------- 1 | using _86BoxManager.API; 2 | using System; 3 | using System.IO; 4 | 5 | namespace _86BoxManager.Mac 6 | { 7 | public sealed class MacEnv : IEnv 8 | { 9 | public MacEnv() 10 | { 11 | MyComputer = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer); 12 | Desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 13 | UserProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 14 | 15 | var fakeDoc = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 16 | MyDocuments = Path.Combine(fakeDoc, "Documents"); 17 | 18 | ExeNames = new[] { "86Box" }; 19 | } 20 | 21 | public string[] ExeNames { get; } 22 | public string MyComputer { get; } 23 | public string UserProfile { get; } 24 | public string MyDocuments { get; } 25 | public string Desktop { get; } 26 | 27 | public string[] GetProgramFiles(string a) 28 | { 29 | var folders = new[] 30 | { 31 | Path.Combine(UserProfile, "Portable", a, a + ".app", "Contents", "MacOS"), 32 | Path.Combine(UserProfile, "Applications", a + ".app", "Contents", "MacOS"), 33 | Path.Combine("/Applications", a + ".app", "Contents", "MacOS"), 34 | Path.Combine("/opt", a), 35 | "/usr/local/bin", 36 | "/usr/bin" 37 | }; 38 | return folders; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /86BoxManager.Mac/MacManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using _86BoxManager.API; 5 | using _86BoxManager.Common; 6 | using _86BoxManager.Unix; 7 | using E = System.Environment; 8 | 9 | namespace _86BoxManager.Mac 10 | { 11 | public sealed class MacManager : UnixManager 12 | { 13 | public MacManager() : base(GetTmpDir()) { } 14 | 15 | public override IVerInfo GetBoxVersion(string exeDir) 16 | { 17 | var info = Path.Combine(exeDir, "..", "Info.plist"); 18 | if (!File.Exists(info)) 19 | { 20 | // Not found! 21 | return null; 22 | } 23 | var text = File.ReadAllText(info); 24 | var bip = text.Split("CFBundleVersion", 2); 25 | var bit = bip.Last().Split("", 2); 26 | var bi = bit.Last().Split(" E.GetEnvironmentVariable("TMPDIR"); 38 | } 39 | } -------------------------------------------------------------------------------- /86BoxManager.Mac/MacShell.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Text; 4 | using _86BoxManager.Common; 5 | 6 | namespace _86BoxManager.Mac 7 | { 8 | public sealed class MacShell : CommonShell 9 | { 10 | public override void CreateShortcut(string address, string name, string desc, string startup) 11 | { 12 | var fileName = address.Replace(".lnk", ".sh"); 13 | var myExe = Path.Combine(startup, "86Manager"); 14 | var lines = new[] 15 | { 16 | "#!/bin/sh", 17 | @$"echo ""Name : {name}""", 18 | @$"echo ""Comment : {desc}""", 19 | @$"""{myExe}"" -S ""{name}"" &" 20 | }; 21 | var bom = new UTF8Encoding(false); 22 | File.WriteAllLines(fileName, lines, bom); 23 | Process.Start(new ProcessStartInfo("chmod", @$"+x ""{fileName}""")); 24 | } 25 | 26 | public override void OpenFolder(string folder) 27 | { 28 | var start = new ProcessStartInfo("open"); 29 | start.ArgumentList.Add(folder); 30 | Process.Start(start); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /86BoxManager.Unix/86BoxManager.Unix.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | _86BoxManager.Unix 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /86BoxManager.Unix/UnixExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Net.Sockets; 6 | using _86BoxManager.API; 7 | using _86BoxManager.Common; 8 | 9 | namespace _86BoxManager.Unix 10 | { 11 | public sealed class UnixExecutor : CommonExecutor, IDisposable 12 | { 13 | private readonly string _tempDir; 14 | private readonly IDictionary _runningVm; 15 | 16 | public UnixExecutor(string tempDir) 17 | { 18 | _tempDir = tempDir; 19 | _runningVm = new Dictionary(); 20 | } 21 | 22 | public void Dispose() 23 | { 24 | foreach (var info in _runningVm.Values) 25 | info.Dispose(); 26 | _runningVm.Clear(); 27 | } 28 | 29 | ~UnixExecutor() 30 | { 31 | Dispose(); 32 | } 33 | 34 | public override ProcessStartInfo BuildStartInfo(IExecVars args) 35 | { 36 | var info = base.BuildStartInfo(args); 37 | 38 | var name = args.Vm.Name; 39 | var socketName = name + Environment.ProcessId; 40 | 41 | var server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); 42 | var socketPath = Path.Combine(_tempDir, socketName); 43 | server.Bind(new UnixDomainSocketEndPoint(socketPath)); 44 | server.Listen(); 45 | 46 | _runningVm[name] = new SocketInfo { Server = server }; 47 | args.Vm.OnExit = OnVmExit; 48 | 49 | var opEnv = info.Environment; 50 | opEnv["86BOX_MANAGER_SOCKET"] = socketName; 51 | 52 | if (server.IsBound) 53 | server.BeginAccept(OnSocketConnect, (server, name)); 54 | 55 | return info; 56 | } 57 | 58 | private void OnSocketConnect(IAsyncResult result) 59 | { 60 | var (server, name) = (ValueTuple)result.AsyncState!; 61 | try 62 | { 63 | var client = server.EndAccept(result); 64 | _runningVm[name].Client = client; 65 | } 66 | catch 67 | { 68 | // Simply ignore! 69 | } 70 | } 71 | 72 | private void OnVmExit(IVm vm) 73 | { 74 | var name = vm.Name; 75 | if (!_runningVm.TryGetValue(name, out var info)) 76 | return; 77 | info.Dispose(); 78 | _runningVm.Remove(name); 79 | } 80 | 81 | private sealed class SocketInfo : IDisposable 82 | { 83 | public Socket Server { get; set; } 84 | public Socket Client { get; set; } 85 | 86 | public void Dispose() 87 | { 88 | Server?.Dispose(); 89 | Client?.Dispose(); 90 | } 91 | } 92 | 93 | internal Socket GetClient(string name) 94 | { 95 | return _runningVm.TryGetValue(name, out var info) ? info.Client : null; 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /86BoxManager.Unix/UnixLoop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.API; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using Mono.Unix.Native; 8 | 9 | #pragma warning disable CA1416 10 | 11 | namespace _86BoxManager.Unix 12 | { 13 | public sealed class UnixLoop : IMessageLoop, IMessageSender, IDisposable 14 | { 15 | private readonly IMessageReceiver _callback; 16 | private readonly UnixExecutor _executor; 17 | private readonly PosixSignalRegistration _posix; 18 | 19 | public UnixLoop(IMessageReceiver callback, UnixExecutor executor) 20 | { 21 | _callback = callback; 22 | _executor = executor; 23 | _posix = PosixSignalRegistration.Create(PosixSignal.SIGCONT, OnPosixStop); 24 | } 25 | 26 | public void Dispose() 27 | { 28 | _posix.Dispose(); 29 | } 30 | 31 | ~UnixLoop() 32 | { 33 | Dispose(); 34 | } 35 | 36 | private void SendCommand(IVm vm, string message) 37 | { 38 | var vmName = vm.Name; 39 | var client = _executor.GetClient(vmName); 40 | if (client == null) 41 | return; 42 | var text = message + "\n"; 43 | var block = Encoding.UTF8.GetBytes(text); 44 | client.Send(block); 45 | } 46 | 47 | public void DoVmRequestStop(IVm vm) => SendCommand(vm, "shutdown"); 48 | public void DoVmForceStop(IVm vm) => SendCommand(vm, "shutdownnoprompt"); 49 | public void DoVmPause(IVm vm) => SendCommand(vm, "pause"); 50 | public void DoVmResume(IVm vm) => DoVmPause(vm); 51 | public void DoVmCtrlAltDel(IVm vm) => SendCommand(vm, "cad"); 52 | public void DoVmHardReset(IVm vm) => SendCommand(vm, "reset"); 53 | public void DoVmConfigure(IVm vm) => SendCommand(vm, "showsettings"); 54 | 55 | public IntPtr GetHandle() => new(Environment.ProcessId); 56 | 57 | private void OnPosixStop(PosixSignalContext obj) 58 | { 59 | obj.Cancel = true; 60 | var proc = Process.GetCurrentProcess(); 61 | var pid = Environment.ProcessId; 62 | var fileMsg = GetMsgFileName(proc, pid); 63 | if (!File.Exists(fileMsg)) 64 | return; 65 | var contents = File.ReadAllText(fileMsg, Encoding.UTF8); 66 | File.Delete(fileMsg); 67 | var vmName = contents.Trim(); 68 | _callback.OnManagerStartVm(vmName); 69 | } 70 | 71 | public void DoManagerStartVm(IntPtr hWnd, string vmName) 72 | { 73 | var pid = hWnd.ToInt32(); 74 | var proc = Process.GetProcessById(pid); 75 | var fileMsg = GetMsgFileName(proc, pid); 76 | var contents = string.Empty + vmName; 77 | File.WriteAllText(fileMsg, contents, Encoding.UTF8); 78 | Syscall.kill(pid, Signum.SIGCONT); 79 | } 80 | 81 | private static string GetMsgFileName(Process proc, int pid) 82 | { 83 | var mod = proc.MainModule!; 84 | var fileName = mod.FileName; 85 | var fileDir = Path.GetDirectoryName(fileName)!; 86 | var fileMsg = Path.Combine(fileDir, $"p_{pid}.tmp"); 87 | return fileMsg; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /86BoxManager.Unix/UnixManager.cs: -------------------------------------------------------------------------------- 1 | using _86BoxManager.API; 2 | using _86BoxManager.Common; 3 | 4 | namespace _86BoxManager.Unix 5 | { 6 | public abstract class UnixManager : CommonManager 7 | { 8 | private readonly UnixExecutor _exec; 9 | 10 | protected UnixManager(string tempDir) 11 | { 12 | _exec = new UnixExecutor(tempDir); 13 | } 14 | 15 | public override IMessageLoop GetLoop(IMessageReceiver callback) 16 | { 17 | var loop = new UnixLoop(callback, _exec); 18 | return loop; 19 | } 20 | 21 | public override IMessageSender GetSender() 22 | { 23 | var loop = new UnixLoop(null, _exec); 24 | return loop; 25 | } 26 | 27 | public override IExecutor GetExecutor() 28 | { 29 | return _exec; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/86BoxManager.Windows.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | _86BoxManager.Windows 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | lib\Interop.IWshRuntimeLibrary.dll 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /86BoxManager.Windows/Internal/Win32Imports.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace _86BoxManager.Windows.Internal 5 | { 6 | internal static class Win32Imports 7 | { 8 | //Win32 API imports 9 | //Posts a message to the window with specified handle - DOES NOT WAIT FOR THE RECIPIENT TO PROCESS THE MESSAGE!!! 10 | [DllImport("user32.dll")] 11 | public static extern int PostMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); 12 | 13 | //Focus a window 14 | [DllImport("user32.dll")] 15 | public static extern int SetForegroundWindow(IntPtr hwnd); 16 | 17 | [StructLayout(LayoutKind.Sequential)] 18 | public struct COPYDATASTRUCT 19 | { 20 | public IntPtr dwData; 21 | public int cbData; 22 | public IntPtr lpData; 23 | } 24 | 25 | //Registry key for accessing the settings and VM list 26 | // private static RegistryKey regkey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\86Box", true); 27 | 28 | internal enum ShowWindowEnum 29 | { 30 | Hide = 0, 31 | ShowNormal = 1, 32 | ShowMinimized = 2, 33 | ShowMaximized = 3, 34 | Maximize = 3, 35 | ShowNormalNoActivate = 4, 36 | Show = 5, 37 | Minimize = 6, 38 | ShowMinNoActivate = 7, 39 | ShowNoActivate = 8, 40 | Restore = 9, 41 | ShowDefault = 10, 42 | ForceMinimized = 11 43 | }; 44 | 45 | [DllImport("user32.dll")] 46 | [return: MarshalAs(UnmanagedType.Bool)] 47 | internal static extern bool ShowWindow(IntPtr hWnd, ShowWindowEnum flags); 48 | 49 | [DllImport("user32.dll")] 50 | public static extern IntPtr FindWindow(string className, string windowTitle); 51 | 52 | public const int WM_COPYDATA = 0x004A; 53 | 54 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 55 | public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, ref COPYDATASTRUCT lParam); 56 | 57 | [DllImport("shell32.dll", SetLastError = true)] 58 | internal static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID); 59 | 60 | [DllImport("user32.dll")] 61 | internal static extern bool SetProcessDPIAware(); 62 | 63 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 64 | public struct WNDCLASSEX 65 | { 66 | public int cbSize; 67 | public int style; 68 | public WndProc lpfnWndProc; 69 | public int cbClsExtra; 70 | public int cbWndExtra; 71 | public IntPtr hInstance; 72 | public IntPtr hIcon; 73 | public IntPtr hCursor; 74 | public IntPtr hbrBackground; 75 | public string lpszMenuName; 76 | public string lpszClassName; 77 | public IntPtr hIconSm; 78 | } 79 | 80 | public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); 81 | 82 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")] 83 | public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx); 84 | 85 | [DllImport("user32.dll", SetLastError = true)] 86 | public static extern IntPtr CreateWindowEx(int dwExStyle, uint lpClassName, string lpWindowName, uint dwStyle, 87 | int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); 88 | 89 | [DllImport("kernel32.dll")] 90 | public static extern IntPtr GetModuleHandle(string lpModuleName); 91 | 92 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] 93 | public static extern bool SetWindowText(IntPtr hwnd, string lpString); 94 | 95 | [DllImport("user32.dll", EntryPoint = "DefWindowProcW")] 96 | public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); 97 | } 98 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/Internal/WinExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using _86BoxManager.API; 3 | using _86BoxManager.Common; 4 | 5 | namespace _86BoxManager.Windows.Internal 6 | { 7 | internal sealed class WinExecutor : CommonExecutor 8 | { 9 | public override ProcessStartInfo BuildStartInfo(IExecVars args) 10 | { 11 | var info = base.BuildStartInfo(args); 12 | var ops = info.ArgumentList; 13 | if (args.Handle != null) 14 | { 15 | ops.Add("--hwnd"); 16 | var (idString, hWndHex) = args.Handle.Value; 17 | ops.Add($"{idString},{hWndHex}"); 18 | } 19 | return info; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/Internal/WinLoop.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System; 3 | using _86BoxManager.API; 4 | using static _86BoxManager.Windows.Internal.Win32Imports; 5 | using System.ComponentModel; 6 | 7 | // ReSharper disable NotAccessedField.Local 8 | 9 | namespace _86BoxManager.Windows.Internal 10 | { 11 | internal sealed class WinLoop : IMessageLoop, IMessageSender 12 | { 13 | private readonly IMessageReceiver _callback; 14 | private readonly IntPtr _hwnd; 15 | private readonly WndProc wndProcDelegate; 16 | 17 | public WinLoop(IMessageReceiver callback, string title = "86Box Manager Secret") 18 | { 19 | _callback = callback; 20 | if (_callback == null) 21 | return; 22 | _hwnd = CreateMessageWindow(title, wndProcDelegate = WndProc); 23 | } 24 | 25 | private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 26 | { 27 | var message = Message.Create(hWnd, msg, wParam, lParam); 28 | WndProc(ref message); 29 | return DefWindowProc(hWnd, msg, wParam, lParam); 30 | } 31 | 32 | // This function monitors received window messages 33 | private void WndProc(ref Message m) 34 | { 35 | // 0x8891 - Main window init complete, wparam = VM ID, lparam = VM window handle 36 | // 0x8895 - VM paused/resumed, wparam = 1: VM paused, wparam = 0: VM resumed 37 | // 0x8896 - Dialog opened/closed, wparam = 1: opened, wparam = 0: closed 38 | // 0x8897 - Shutdown confirmed 39 | 40 | if (m.Msg == 0x8891) 41 | { 42 | if (m.LParam != IntPtr.Zero && m.WParam.ToInt64() >= 0) 43 | { 44 | var vmId = (uint)m.WParam.ToInt32(); 45 | var hWnd = m.LParam; 46 | _callback.OnEmulatorInit(hWnd, vmId); 47 | } 48 | } 49 | 50 | if (m.Msg == 0x8895) 51 | { 52 | if (m.WParam.ToInt32() == 1) // VM was paused 53 | { 54 | var hWnd = m.LParam; 55 | _callback.OnVmPaused(hWnd); 56 | } 57 | else if (m.WParam.ToInt32() == 0) // VM was resumed 58 | { 59 | var hWnd = m.LParam; 60 | _callback.OnVmResumed(hWnd); 61 | } 62 | } 63 | 64 | if (m.Msg == 0x8896) 65 | { 66 | if (m.WParam.ToInt32() == 1) // A dialog was opened 67 | { 68 | var hWnd = m.LParam; 69 | _callback.OnDialogOpened(hWnd); 70 | } 71 | else if (m.WParam.ToInt32() == 0) // A dialog was closed 72 | { 73 | var hWnd = m.LParam; 74 | _callback.OnDialogClosed(hWnd); 75 | } 76 | } 77 | 78 | if (m.Msg == 0x8897) // Shutdown confirmed 79 | { 80 | var hWnd = m.LParam; 81 | _callback.OnEmulatorShutdown(hWnd); 82 | } 83 | 84 | // This is the WM_COPYDATA message, used here to pass command line args to an already running instance 85 | // NOTE: This code will have to be modified in case more command line arguments are added in the future. 86 | if (m.Msg == WM_COPYDATA) 87 | { 88 | // Get the VM name and find the associated LVI and VM object 89 | var ds = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT)); 90 | var vmName = Marshal.PtrToStringAnsi(ds.lpData, ds.cbData); 91 | 92 | _callback.OnManagerStartVm(vmName); 93 | } 94 | } 95 | 96 | public IntPtr GetHandle() 97 | { 98 | var native = _hwnd; 99 | return native; 100 | } 101 | 102 | public void DoVmRequestStop(IVm vm) 103 | { 104 | var hWnd = vm.hWnd; 105 | PostMessage(hWnd, 0x8893, IntPtr.Zero, IntPtr.Zero); 106 | SetForegroundWindow(hWnd); 107 | } 108 | 109 | public void DoVmForceStop(IVm vm) 110 | { 111 | var hWnd = vm.hWnd; 112 | PostMessage(hWnd, 0x8893, new IntPtr(1), IntPtr.Zero); 113 | } 114 | 115 | public void DoVmPause(IVm vm) 116 | { 117 | var hWnd = vm.hWnd; 118 | PostMessage(hWnd, 0x8890, IntPtr.Zero, IntPtr.Zero); 119 | } 120 | 121 | public void DoVmResume(IVm vm) 122 | { 123 | var hWnd = vm.hWnd; 124 | PostMessage(hWnd, 0x8890, IntPtr.Zero, IntPtr.Zero); 125 | } 126 | 127 | public void DoVmCtrlAltDel(IVm vm) 128 | { 129 | var hWnd = vm.hWnd; 130 | PostMessage(hWnd, 0x8894, IntPtr.Zero, IntPtr.Zero); 131 | } 132 | 133 | public void DoVmHardReset(IVm vm) 134 | { 135 | var hWnd = vm.hWnd; 136 | PostMessage(hWnd, 0x8892, IntPtr.Zero, IntPtr.Zero); 137 | SetForegroundWindow(hWnd); 138 | } 139 | 140 | public void DoVmConfigure(IVm vm) 141 | { 142 | var hWnd = vm.hWnd; 143 | PostMessage(hWnd, 0x8889, IntPtr.Zero, IntPtr.Zero); 144 | SetForegroundWindow(hWnd); 145 | } 146 | 147 | public void DoManagerStartVm(IntPtr hWnd, string vmName) 148 | { 149 | COPYDATASTRUCT cds; 150 | cds.dwData = IntPtr.Zero; 151 | cds.lpData = Marshal.StringToHGlobalAnsi(vmName); 152 | cds.cbData = vmName.Length; 153 | SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ref cds); 154 | } 155 | 156 | private static IntPtr CreateMessageWindow(string title, WndProc proc) 157 | { 158 | var wndClassEx = new WNDCLASSEX 159 | { 160 | cbSize = Marshal.SizeOf(), 161 | lpfnWndProc = proc, 162 | hInstance = GetModuleHandle(null), 163 | lpszClassName = title 164 | }; 165 | var atom = RegisterClassEx(ref wndClassEx); 166 | if (atom == 0) 167 | throw new Win32Exception(); 168 | var hwnd = CreateWindowEx(0, atom, null, 0, 0, 0, 0, 169 | 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); 170 | if (hwnd == IntPtr.Zero) 171 | throw new Win32Exception(); 172 | var ok = SetWindowText(hwnd, title); 173 | if (!ok) 174 | throw new Win32Exception(); 175 | return hwnd; 176 | } 177 | 178 | public struct Message 179 | { 180 | public IntPtr HWnd { get; set; } 181 | public uint Msg { get; set; } 182 | public IntPtr WParam { get; set; } 183 | public IntPtr LParam { get; set; } 184 | public IntPtr Result { get; set; } 185 | 186 | public object GetLParam(Type cls) => Marshal.PtrToStructure(LParam, cls); 187 | 188 | public static Message Create(IntPtr hWnd, uint msg, IntPtr wparam, IntPtr lparam) 189 | => new() { HWnd = hWnd, Msg = msg, WParam = wparam, LParam = lparam, Result = IntPtr.Zero }; 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/Internal/WinVerInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using _86BoxManager.API; 3 | 4 | namespace _86BoxManager.Windows.Internal 5 | { 6 | internal sealed class WinVerInfo : IVerInfo 7 | { 8 | private readonly FileVersionInfo _info; 9 | 10 | public WinVerInfo(FileVersionInfo info) 11 | { 12 | _info = info; 13 | } 14 | 15 | public int FilePrivatePart => _info.FilePrivatePart; 16 | public int FileMajorPart => _info.FileMajorPart; 17 | public int FileMinorPart => _info.FileMinorPart; 18 | public int FileBuildPart => _info.FileBuildPart; 19 | 20 | public override string ToString() => _info.ToString(); 21 | } 22 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/WinEnv.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using _86BoxManager.API; 4 | 5 | namespace _86BoxManager.Windows 6 | { 7 | public sealed class WinEnv : IEnv 8 | { 9 | public WinEnv() 10 | { 11 | MyComputer = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer); 12 | Desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 13 | UserProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 14 | 15 | MyDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 16 | 17 | ExeNames = new[] { "86Box.exe" }; 18 | } 19 | 20 | public string[] ExeNames { get; } 21 | public string MyComputer { get; } 22 | public string UserProfile { get; } 23 | public string MyDocuments { get; } 24 | public string Desktop { get; } 25 | 26 | public string[] GetProgramFiles(string appName) 27 | { 28 | var folders = new[] 29 | { 30 | Path.Combine(UserProfile, "Portable", appName), 31 | Path.Combine("C:\\Portable", appName), 32 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), appName), 33 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), appName) 34 | }; 35 | return folders; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/WinManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading; 5 | using _86BoxManager.API; 6 | using _86BoxManager.Common; 7 | using _86BoxManager.Windows.Internal; 8 | using static _86BoxManager.Windows.Internal.Win32Imports; 9 | 10 | namespace _86BoxManager.Windows 11 | { 12 | public sealed class WinManager : CommonManager, IManager 13 | { 14 | private static Mutex mutex = null; 15 | 16 | public override bool IsFirstInstance(string name) 17 | { 18 | //Use a mutex to check if this is the first instance of Manager 19 | mutex = new Mutex(true, name, out var firstInstance); 20 | return firstInstance; 21 | } 22 | 23 | public override IntPtr RestoreAndFocus(string windowTitle, string handleTitle) 24 | { 25 | //Finds the existing window, unhides it, restores it and sets focus to it 26 | var hWnd = FindWindow(null, windowTitle); 27 | ShowWindow(hWnd, ShowWindowEnum.Show); 28 | ShowWindow(hWnd, ShowWindowEnum.Restore); 29 | SetForegroundWindow(hWnd); 30 | 31 | hWnd = FindWindow(null, handleTitle); 32 | return hWnd; 33 | } 34 | 35 | public override IVerInfo GetBoxVersion(string exeDir) 36 | { 37 | var exePath = Path.Combine(exeDir, "86Box.exe"); 38 | if (!File.Exists(exePath)) 39 | { 40 | // Not found! 41 | return null; 42 | } 43 | var vi = FileVersionInfo.GetVersionInfo(exePath); 44 | return new WinVerInfo(vi); 45 | } 46 | 47 | public override IMessageLoop GetLoop(IMessageReceiver callback) 48 | { 49 | var loop = new WinLoop(callback); 50 | return loop; 51 | } 52 | 53 | public override IMessageSender GetSender() 54 | { 55 | var loop = new WinLoop(null); 56 | return loop; 57 | } 58 | 59 | public override IExecutor GetExecutor() 60 | { 61 | var exec = new WinExecutor(); 62 | return exec; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/WinShell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using _86BoxManager.Common; 4 | using IWshRuntimeLibrary; 5 | using static _86BoxManager.Windows.Internal.Win32Imports; 6 | 7 | namespace _86BoxManager.Windows 8 | { 9 | public sealed class WinShell : CommonShell 10 | { 11 | public override void CreateShortcut(string address, string name, string desc, string startup) 12 | { 13 | dynamic shell = new WshShell(); 14 | dynamic shortcut = (IWshShortcut)shell.CreateShortcut(address); 15 | shortcut.Description = desc; 16 | shortcut.IconLocation = $"{Path.Combine(startup, "86manager.exe")},0"; 17 | shortcut.TargetPath = Path.Combine(startup, "86manager.exe"); 18 | shortcut.Arguments = $@"-S ""{name}"""; 19 | shortcut.Save(); 20 | } 21 | 22 | public override void PushToForeground(IntPtr hWnd) 23 | { 24 | SetForegroundWindow(hWnd); 25 | } 26 | 27 | public override void PrepareAppId(string appId) 28 | { 29 | if (Environment.OSVersion.Version.Major >= 6) 30 | SetProcessDPIAware(); 31 | 32 | SetCurrentProcessExplicitAppUserModelID(appId); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /86BoxManager.Windows/lib/Interop.IWshRuntimeLibrary.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager.Windows/lib/Interop.IWshRuntimeLibrary.dll -------------------------------------------------------------------------------- /86BoxManager.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "86BoxManager", "86BoxManager\86BoxManager.csproj", "{559F81B9-D1A5-45FC-AA69-E98F3B3926BB}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.API", "86BoxManager.API\86BoxManager.API.csproj", "{CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.Common", "86BoxManager.Common\86BoxManager.Common.csproj", "{88B71811-8D21-4AD2-BB89-6B2E8913CB43}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.Gtk", "86BoxManager.Gtk\86BoxManager.Gtk.csproj", "{919E0F43-79BD-4A2A-8952-0792D7B5186C}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.Linux", "86BoxManager.Linux\86BoxManager.Linux.csproj", "{2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.Mac", "86BoxManager.Mac\86BoxManager.Mac.csproj", "{9DE359DC-835A-4E13-A3A4-4D668F6DB641}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.Unix", "86BoxManager.Unix\86BoxManager.Unix.csproj", "{3C8FE45D-8700-4C63-B737-E922E8AF5A66}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.Windows", "86BoxManager.Windows\86BoxManager.Windows.csproj", "{DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{FB9C017E-A47B-457A-B860-A22E607F7BDA}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Base", "Base", "{11633D7A-1FCE-4BA3-A616-EBD2A5742B1D}" 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cross", "Cross", "{FE40DF74-77EB-454F-9CFC-1474A694C634}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "86BoxManager.Core", "86BoxManager.Core\86BoxManager.Core.csproj", "{64F8FA04-E8AF-4787-9C70-68EF6A289D5A}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Debug|x86 = Debug|x86 34 | Release|Any CPU = Release|Any CPU 35 | Release|x86 = Release|x86 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Debug|x86.ActiveCfg = Debug|x86 41 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Debug|x86.Build.0 = Debug|x86 42 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Release|x86.ActiveCfg = Release|x86 45 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB}.Release|x86.Build.0 = Release|x86 46 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Debug|x86.Build.0 = Debug|Any CPU 50 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Release|x86.ActiveCfg = Release|Any CPU 53 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498}.Release|x86.Build.0 = Release|Any CPU 54 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Debug|x86.Build.0 = Debug|Any CPU 58 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Release|x86.ActiveCfg = Release|Any CPU 61 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43}.Release|x86.Build.0 = Release|Any CPU 62 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Debug|x86.Build.0 = Debug|Any CPU 66 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Release|x86.ActiveCfg = Release|Any CPU 69 | {919E0F43-79BD-4A2A-8952-0792D7B5186C}.Release|x86.Build.0 = Release|Any CPU 70 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Debug|x86.ActiveCfg = Debug|Any CPU 73 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Debug|x86.Build.0 = Debug|Any CPU 74 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Release|x86.ActiveCfg = Release|Any CPU 77 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4}.Release|x86.Build.0 = Release|Any CPU 78 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Debug|x86.Build.0 = Debug|Any CPU 82 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Release|x86.ActiveCfg = Release|Any CPU 85 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641}.Release|x86.Build.0 = Release|Any CPU 86 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Debug|x86.ActiveCfg = Debug|Any CPU 89 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Debug|x86.Build.0 = Debug|Any CPU 90 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Release|Any CPU.Build.0 = Release|Any CPU 92 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Release|x86.ActiveCfg = Release|Any CPU 93 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66}.Release|x86.Build.0 = Release|Any CPU 94 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 95 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 96 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Debug|x86.ActiveCfg = Debug|Any CPU 97 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Debug|x86.Build.0 = Debug|Any CPU 98 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 99 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Release|Any CPU.Build.0 = Release|Any CPU 100 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Release|x86.ActiveCfg = Release|Any CPU 101 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE}.Release|x86.Build.0 = Release|Any CPU 102 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 103 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU 104 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Debug|x86.ActiveCfg = Debug|Any CPU 105 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Debug|x86.Build.0 = Debug|Any CPU 106 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Release|Any CPU.Build.0 = Release|Any CPU 108 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Release|x86.ActiveCfg = Release|Any CPU 109 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A}.Release|x86.Build.0 = Release|Any CPU 110 | EndGlobalSection 111 | GlobalSection(SolutionProperties) = preSolution 112 | HideSolutionNode = FALSE 113 | EndGlobalSection 114 | GlobalSection(NestedProjects) = preSolution 115 | {559F81B9-D1A5-45FC-AA69-E98F3B3926BB} = {FB9C017E-A47B-457A-B860-A22E607F7BDA} 116 | {CFFBD1C4-08B0-4367-ACAC-9FFEB0E8C498} = {11633D7A-1FCE-4BA3-A616-EBD2A5742B1D} 117 | {88B71811-8D21-4AD2-BB89-6B2E8913CB43} = {11633D7A-1FCE-4BA3-A616-EBD2A5742B1D} 118 | {919E0F43-79BD-4A2A-8952-0792D7B5186C} = {FB9C017E-A47B-457A-B860-A22E607F7BDA} 119 | {2EEC0C04-8C2E-42B1-A7B1-195F1A6C52D4} = {FE40DF74-77EB-454F-9CFC-1474A694C634} 120 | {9DE359DC-835A-4E13-A3A4-4D668F6DB641} = {FE40DF74-77EB-454F-9CFC-1474A694C634} 121 | {3C8FE45D-8700-4C63-B737-E922E8AF5A66} = {FE40DF74-77EB-454F-9CFC-1474A694C634} 122 | {DB4AD9FA-D9D5-497A-B5DB-8BC75898F4EE} = {FE40DF74-77EB-454F-9CFC-1474A694C634} 123 | {64F8FA04-E8AF-4787-9C70-68EF6A289D5A} = {11633D7A-1FCE-4BA3-A616-EBD2A5742B1D} 124 | EndGlobalSection 125 | GlobalSection(ExtensibilityGlobals) = postSolution 126 | SolutionGuid = {65AD1D82-0EF3-4020-8253-8731951B1749} 127 | EndGlobalSection 128 | EndGlobal 129 | -------------------------------------------------------------------------------- /86BoxManager/86BoxManager.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net6.0 5 | true 6 | app.manifest 7 | true 8 | 9 | false 10 | _86BoxManager 11 | 86Manager 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Always 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /86BoxManager/App.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /86BoxManager/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.ViewModels; 3 | using _86BoxManager.Views; 4 | using Avalonia; 5 | using Avalonia.Controls.ApplicationLifetimes; 6 | using Avalonia.Markup.Xaml; 7 | 8 | namespace _86BoxManager 9 | { 10 | public partial class App : Application 11 | { 12 | public override void Initialize() 13 | { 14 | AvaloniaXamlLoader.Load(this); 15 | } 16 | 17 | public override void OnFrameworkInitializationCompleted() 18 | { 19 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 20 | { 21 | desktop.MainWindow = new frmMain 22 | { 23 | DataContext = new MainModel() 24 | }; 25 | } 26 | 27 | base.OnFrameworkInitializationCompleted(); 28 | } 29 | 30 | private void open86BoxManagerToolStripMenuItem_Click(object sender, EventArgs e) 31 | => Program.Root.open86BoxManagerToolStripMenuItem_Click(sender, e); 32 | 33 | private void settingsToolStripMenuItem_Click(object sender, EventArgs e) 34 | => Program.Root.settingsToolStripMenuItem_Click(sender, e); 35 | 36 | private void exitToolStripMenuItem_Click(object sender, EventArgs e) 37 | => Program.Root.exitToolStripMenuItem_Click(sender, e); 38 | 39 | private void trayIcon_MouseClick(object sender, EventArgs e) 40 | => Program.Root.trayIcon_MouseClick(sender, e); 41 | } 42 | } -------------------------------------------------------------------------------- /86BoxManager/Assets/86Box-gray.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Assets/86Box-gray.ico -------------------------------------------------------------------------------- /86BoxManager/Assets/86Box-gray.ma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Assets/86Box-gray.ma.png -------------------------------------------------------------------------------- /86BoxManager/Assets/86Box-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Assets/86Box-gray.png -------------------------------------------------------------------------------- /86BoxManager/Assets/86Box-gray.sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Assets/86Box-gray.sm.png -------------------------------------------------------------------------------- /86BoxManager/Core/VMWatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using _86BoxManager.Tools; 6 | using _86BoxManager.Models; 7 | using ButtonsType = MessageBox.Avalonia.Enums.ButtonEnum; 8 | using MessageType = MessageBox.Avalonia.Enums.Icon; 9 | using ResponseType = MessageBox.Avalonia.Enums.ButtonResult; 10 | 11 | // ReSharper disable InconsistentNaming 12 | 13 | namespace _86BoxManager.Core 14 | { 15 | public sealed class VMWatch 16 | { 17 | private readonly BackgroundWorker _bgw; 18 | 19 | public VMWatch(BackgroundWorker bgw) 20 | { 21 | _bgw = bgw; 22 | _bgw.DoWork += background_DoWork; 23 | _bgw.RunWorkerCompleted += background_RunCompleted; 24 | } 25 | 26 | public void Dispose() 27 | { 28 | _bgw.DoWork -= background_DoWork; 29 | _bgw.RunWorkerCompleted -= background_RunCompleted; 30 | _bgw.Dispose(); 31 | } 32 | 33 | // Wait for the associated window of a VM to close 34 | private void background_DoWork(object sender, DoWorkEventArgs e) 35 | { 36 | var vm = e.Argument as VM; 37 | try 38 | { 39 | // Find the process associated with the VM 40 | var p = Process.GetProcessById(vm.Pid); 41 | 42 | // Wait for it to exit 43 | p.WaitForExit(); 44 | } 45 | catch (Exception ex) 46 | { 47 | Dialogs.ShowMessageBox("An error has occurred. Please provide the following details" + 48 | $" to the developer:\n{ex.Message}\n{ex.StackTrace}", 49 | MessageType.Error, ButtonsType.Ok, "Error"); 50 | } 51 | e.Result = vm; 52 | } 53 | 54 | // Update the UI once the VM's window is closed 55 | private void background_RunCompleted(object sender, RunWorkerCompletedEventArgs e) 56 | { 57 | var ui = Program.Root; 58 | var lstVMs = ui.lstVMs; 59 | var vm = e.Result as VM; 60 | 61 | var allItems = lstVMs.GetAllItems(); 62 | var selected = lstVMs.GetSelItems(); 63 | 64 | // Go through the listview, find the item representing the VM and update things accordingly 65 | foreach (var item in allItems) 66 | { 67 | if (item.Tag.Equals(vm)) 68 | { 69 | vm.Status = VM.STATUS_STOPPED; 70 | vm.hWnd = IntPtr.Zero; 71 | item.SetStatus(vm.GetStatusString()); 72 | item.SetIcon(vm.Status); 73 | 74 | if (vm.OnExit != null) 75 | { 76 | vm.OnExit(vm); 77 | vm.OnExit = null; 78 | } 79 | 80 | if (selected.Count > 0 && selected[0].Equals(item)) 81 | { 82 | ui.btnEdit.IsEnabled = true; 83 | ui.btnDelete.IsEnabled = true; 84 | ui.btnStart.IsEnabled = true; 85 | ui.btnStart.Content = "Start"; 86 | ui.btnStart.SetToolTip("Start this virtual machine"); 87 | ui.btnConfigure.IsEnabled = true; 88 | ui.btnPause.IsEnabled = false; 89 | ui.btnPause.Content = "Pause"; 90 | ui.btnCtrlAltDel.IsEnabled = false; 91 | ui.btnReset.IsEnabled = false; 92 | } 93 | } 94 | } 95 | 96 | VMCenter.CountRefresh(); 97 | } 98 | 99 | public static bool TryWaitForInputIdle(Process process, int forceDelay) 100 | { 101 | try 102 | { 103 | return process.WaitForInputIdle(); 104 | } 105 | catch (InvalidOperationException) 106 | { 107 | Thread.Sleep(forceDelay); 108 | return false; 109 | } 110 | } 111 | 112 | public static uint GetTempId(VM vm) 113 | { 114 | /* This generates a VM ID on the fly from the VM path. The reason it's done this way is 115 | * it doesn't break existing VMs and doesn't require extensive modifications to this 116 | * legacy version for it to work with newer 86Box versions... 117 | * IDs also have to be unsigned for 86Box, but GetHashCode() returns signed and result 118 | * can be negative, so shift it up by int.MaxValue to ensure it's always positive. */ 119 | 120 | var tempid = vm.Path.GetHashCode(); 121 | uint id; 122 | 123 | if (tempid < 0) 124 | id = (uint)(tempid + int.MaxValue); 125 | else 126 | id = (uint)tempid; 127 | 128 | return id; 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /86BoxManager/Models/Cache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | using _86BoxManager.ViewModels; 5 | using Avalonia.Controls; 6 | 7 | namespace _86BoxManager.Models 8 | { 9 | internal static class Cache 10 | { 11 | public static IList GetSelItems(this DataGrid view) 12 | { 13 | var items = view.SelectedItems.OfType().ToList(); 14 | return items; 15 | } 16 | 17 | public static IList GetAllItems(this DataGrid view) 18 | { 19 | var model = (ObservableCollection)view.Items; 20 | return model; 21 | } 22 | 23 | public static void ClearSelect(this DataGrid view) 24 | { 25 | view.SelectedIndex = -1; 26 | } 27 | 28 | public static void ClearAll(this DataGrid view) 29 | { 30 | var model = (ObservableCollection)view.Items; 31 | model.Clear(); 32 | } 33 | 34 | public static VMRow Insert(this DataGrid view, string _, VM vm) 35 | { 36 | var model = (ObservableCollection)view.Items; 37 | 38 | var nv = new VMRow(vm); 39 | model.Add(nv); 40 | 41 | return nv; 42 | } 43 | 44 | public static void RemoveItem(this DataGrid view, VMRow item) 45 | { 46 | var model = (ObservableCollection)view.Items; 47 | model.Remove(item); 48 | } 49 | 50 | public static VMRow FindItemWithText(this DataGrid view, string vmName) 51 | { 52 | var rows = view.GetAllItems(); 53 | var row = rows.FirstOrDefault(r => r.Tag.Name.Equals(vmName)); 54 | return row; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /86BoxManager/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using _86BoxManager.Tools; 3 | using _86BoxManager.Views; 4 | using _86BoxManager.Xplat; 5 | using Avalonia; 6 | using Avalonia.ReactiveUI; 7 | using JetBrains.Annotations; 8 | using ButtonsType = MessageBox.Avalonia.Enums.ButtonEnum; 9 | using MessageType = MessageBox.Avalonia.Enums.Icon; 10 | using ResponseType = MessageBox.Avalonia.Enums.ButtonResult; 11 | 12 | namespace _86BoxManager 13 | { 14 | internal static class Program 15 | { 16 | //Get command line arguments 17 | public static string[] Args; 18 | 19 | //For grouping windows together in Win7+ taskbar 20 | private static readonly string AppId = "86Box.86Box"; 21 | 22 | internal static frmMain Root; 23 | 24 | [STAThread] 25 | private static int Main(string[] args) 26 | { 27 | Args = args; 28 | 29 | Platforms.Shell.PrepareAppId(AppId); 30 | var (_, startIt) = BuildAvaloniaApp(args); 31 | 32 | //Check if it is the very first and only instance running. 33 | //If it's not, we need to restore and focus the existing window, 34 | //as well as pass on any potential command line arguments 35 | if (CheckRunningManagerAndAbort(args)) 36 | return -1; 37 | 38 | //Then check if any instances of 86Box are already running and warn the user 39 | if (CheckRunningEmulatorAndAbort()) 40 | return -2; 41 | 42 | var code = startIt(); 43 | return code; 44 | } 45 | 46 | /// 47 | /// Used by visual designer 48 | /// 49 | [UsedImplicitly] 50 | public static AppBuilder BuildAvaloniaApp() 51 | => BuildAvaloniaApp(Args, false).builder; 52 | 53 | private static (AppBuilder builder, Func after) BuildAvaloniaApp(string[] args, bool withLife = true) 54 | { 55 | var bld = AppBuilder.Configure() 56 | .UsePlatformDetect() 57 | .LogToTrace() 58 | .UseReactiveUI(); 59 | return withLife ? bld.SetupWithClassicDesktopLifetime(args) : (bld, null); 60 | } 61 | 62 | private static bool CheckRunningManagerAndAbort(string[] args) 63 | { 64 | const string name = "86Box Manager"; 65 | const string handleName = "86Box Manager Secret"; 66 | 67 | var firstInstance = Platforms.Manager.IsFirstInstance(name); 68 | if (!firstInstance) 69 | { 70 | var hWnd = Platforms.Manager.RestoreAndFocus(name, handleName); 71 | 72 | // If this second instance comes from a VM shortcut, we need to pass on the 73 | // command line arguments so the VM will start in the existing instance. 74 | // NOTE: This code will have to be modified in case more 75 | // command line arguments are added in the future. 76 | if (GetVmArg(args, out var message)) 77 | { 78 | var sender = Platforms.Manager.GetSender(); 79 | sender.DoManagerStartVm(hWnd, message); 80 | } 81 | return true; 82 | } 83 | return false; 84 | } 85 | 86 | internal static bool GetVmArg(string[] args, out string vmName) 87 | { 88 | if (args.Length == 2 && args[0] == "-S" && args[1] != null) 89 | { 90 | vmName = args[1]; 91 | return true; 92 | } 93 | vmName = default; 94 | return false; 95 | } 96 | 97 | private static bool CheckRunningEmulatorAndAbort() 98 | { 99 | var isRunning = Platforms.Manager.IsProcessRunning("86box") || 100 | Platforms.Manager.IsProcessRunning("86Box"); 101 | if (isRunning) 102 | { 103 | var result = Dialogs.ShowMessageBox("At least one instance of 86Box is already running. It's\n" + 104 | "not recommended that you run 86Box directly outside of\n" + 105 | "Manager. Do you want to continue at your own risk?", 106 | MessageType.Warning, ButtonsType.YesNo, "Warning"); 107 | if (result == ResponseType.No) 108 | { 109 | return true; 110 | } 111 | } 112 | return false; 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /86BoxManager/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("86Box Manager")] 8 | [assembly: AssemblyDescription("A configuration manager for 86Box emulator")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("David Simunič and others")] 11 | [assembly: AssemblyProduct("86Box Manager")] 12 | [assembly: AssemblyCopyright("Copyright © 2018-2023 David Simunič and others")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("559f81b9-d1a5-45fc-aa69-e98f3b3926bb")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | 34 | [assembly: AssemblyVersion("1.7.6.0")] 35 | [assembly: AssemblyFileVersion("1.7.6.0")] -------------------------------------------------------------------------------- /86BoxManager/Resources/vm-paused.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Resources/vm-paused.png -------------------------------------------------------------------------------- /86BoxManager/Resources/vm-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Resources/vm-running.png -------------------------------------------------------------------------------- /86BoxManager/Resources/vm-stopped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Resources/vm-stopped.png -------------------------------------------------------------------------------- /86BoxManager/Resources/vm-waiting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetBox/86BoxManagerX/a04581080afe570a700c5bc7e10f9c23db21f388/86BoxManager/Resources/vm-waiting.png -------------------------------------------------------------------------------- /86BoxManager/Tools/Compat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Controls; 4 | using Avalonia.Controls.Primitives; 5 | using Avalonia.Input; 6 | using Avalonia.Media; 7 | 8 | namespace _86BoxManager.Tools 9 | { 10 | public static class Compat 11 | { 12 | public static bool IsActive(this ToggleButton toggle) 13 | => toggle.IsChecked == true; 14 | 15 | public static bool IsEditable(this TextBox box, bool value) 16 | { 17 | box.IsReadOnly = !value; 18 | return value; 19 | } 20 | 21 | public static void Iconify(this Window window) 22 | { 23 | window.WindowState = WindowState.Minimized; 24 | } 25 | 26 | public static void EnableGridLines(this DataGrid view, bool value) 27 | { 28 | view.GridLinesVisibility = value 29 | ? DataGridGridLinesVisibility.All 30 | : DataGridGridLinesVisibility.None; 31 | } 32 | 33 | public static void SetColorTxt(this ContentControl label, ISolidColorBrush color, 34 | FontWeight weight, string text) 35 | { 36 | label.Foreground = color; 37 | label.FontWeight = weight; 38 | label.Content = text; 39 | } 40 | 41 | public static void OnTextChanged(this TextBox txtBox, Action handler) 42 | { 43 | var observable = txtBox.GetObservable(TextBox.TextProperty); 44 | observable.Subscribe(text => handler(txtBox, new TextInputEventArgs { Text = text })); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /86BoxManager/Tools/Dialogs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using Avalonia; 7 | using Avalonia.Controls; 8 | using MessageBox.Avalonia; 9 | using MessageBox.Avalonia.Enums; 10 | using MessageBox.Avalonia.DTO; 11 | using System.Linq; 12 | using StartLoc = Avalonia.Controls.WindowStartupLocation; 13 | 14 | namespace _86BoxManager.Tools 15 | { 16 | internal static class Dialogs 17 | { 18 | public static ButtonResult ShowMessageBox(string msg, Icon icon, 19 | ButtonEnum buttons = ButtonEnum.Ok, string title = "Attention") 20 | { 21 | var parent = Program.Root; 22 | var loc = parent == null ? StartLoc.CenterScreen : StartLoc.CenterOwner; 23 | var opts = new MessageBoxStandardParams 24 | { 25 | ButtonDefinitions = buttons, 26 | ContentTitle = title, 27 | ContentMessage = msg, 28 | Icon = icon, 29 | CanResize = false, 30 | WindowStartupLocation = loc, 31 | SizeToContent = SizeToContent.WidthAndHeight 32 | }; 33 | var window = MessageBoxManager.GetMessageBoxStandardWindow(opts); 34 | var raw = parent != null ? window.ShowDialog(parent) : window.Show(); 35 | if (Application.Current is var app) 36 | { 37 | var flags = BindingFlags.NonPublic | BindingFlags.Instance; 38 | var windowField = window.GetType().GetField("_window", flags)!; 39 | var windowObj = (Window)windowField.GetValue(window)!; 40 | if (parent?.Icon is { } wi) 41 | windowObj.Icon = wi; 42 | app.Run(windowObj); 43 | } 44 | var res = raw.GetAwaiter().GetResult(); 45 | return res; 46 | } 47 | 48 | [SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")] 49 | public static async Task RunDialog(this Window parent, Window dialog, Action func = null) 50 | { 51 | dialog.WindowStartupLocation = StartLoc.CenterOwner; 52 | dialog.Icon = parent.Icon; 53 | 54 | var raw = dialog.ShowDialog(parent); 55 | await raw; 56 | func?.Invoke(); 57 | (dialog as IDisposable)?.Dispose(); 58 | } 59 | 60 | public static async Task SelectFolder(string title, string dir, Window parent) 61 | { 62 | var dialog = new OpenFolderDialog 63 | { 64 | Title = title, Directory = dir 65 | }; 66 | 67 | string result = null; 68 | var raw = dialog.ShowAsync(parent); 69 | var res = await raw; 70 | 71 | if (!string.IsNullOrWhiteSpace(res)) 72 | { 73 | result = res; 74 | } 75 | return result; 76 | } 77 | 78 | public static async Task SaveFile(string title, string dir, string filter, 79 | Window parent, string ext = null) 80 | { 81 | var dialog = new SaveFileDialog 82 | { 83 | Title = title, Directory = dir, DefaultExtension = ext 84 | }; 85 | 86 | if (filter != null) 87 | { 88 | var tmp = filter.Split('|', 2); 89 | dialog.Filters = new List 90 | { 91 | new() { Name = tmp.First(), Extensions = new List { tmp.Last() } } 92 | }; 93 | } 94 | 95 | string result = null; 96 | var raw = dialog.ShowAsync(parent); 97 | var res = await raw; 98 | 99 | if (!string.IsNullOrWhiteSpace(res)) 100 | { 101 | result = res; 102 | } 103 | return result; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /86BoxManager/Tools/Lifetimes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Controls; 4 | 5 | namespace _86BoxManager.Tools 6 | { 7 | internal static class Lifetimes 8 | { 9 | public static (T builder, Func after) SetupWithClassicDesktopLifetime( 10 | this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) 11 | where T : AppBuilderBase, new() 12 | { 13 | var lifetime = new ClassicDesktopStyleApplicationLifetime 14 | { 15 | Args = args, 16 | ShutdownMode = shutdownMode 17 | }; 18 | builder.SetupWithLifetime(lifetime); 19 | int After() => lifetime.Start(args); 20 | return (builder, After); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /86BoxManager/Tools/Resources.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Avalonia.Media.Imaging; 3 | 4 | namespace _86BoxManager.Tools 5 | { 6 | internal static class Resources 7 | { 8 | public static Bitmap LoadImage(Stream stream) 9 | { 10 | var bitmap = new Bitmap(stream); 11 | return bitmap; 12 | } 13 | 14 | public static Stream FindResource(string path) 15 | { 16 | const string n = $".{nameof(Resources)}"; 17 | var type = typeof(Program); 18 | var dll = type.Assembly; 19 | var prefix = type.FullName?.Replace(".Program", n); 20 | var fullName = prefix + path.Replace('/', '.'); 21 | var resource = dll.GetManifestResourceStream(fullName); 22 | return resource; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /86BoxManager/Tools/ToolTips.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace _86BoxManager.Tools 4 | { 5 | public static class ToolTips 6 | { 7 | public static void SetToolTip(this Control widget, string text) 8 | { 9 | ToolTip.SetTip(widget, text); 10 | } 11 | 12 | public static void UnsetToolTip(this Control widget) 13 | { 14 | ToolTip.SetTip(widget, null); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /86BoxManager/Tools/Trays.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Media.Imaging; 3 | 4 | namespace _86BoxManager.Tools 5 | { 6 | public static class Trays 7 | { 8 | public static void ApplyIcon(this TrayIcon trayIcon, Bitmap pix) 9 | { 10 | trayIcon.Icon = new WindowIcon(pix); 11 | } 12 | 13 | public static void MakeVisible(this TrayIcon trayIcon, bool value) 14 | { 15 | trayIcon.IsVisible = value; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /86BoxManager/ViewModels/MainModel.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace _86BoxManager.ViewModels 5 | { 6 | internal class MainModel : ReactiveObject 7 | { 8 | public ObservableCollection Machines { get; } = new(); 9 | 10 | private string _vmCount = "# of virtual machines:"; 11 | 12 | public string VmCount 13 | { 14 | get => _vmCount; 15 | set 16 | { 17 | _vmCount = value; 18 | this.RaisePropertyChanged(); 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /86BoxManager/ViewModels/VMRow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using _86BoxManager.Models; 3 | using Avalonia.Media.Imaging; 4 | using ReactiveUI; 5 | using static _86BoxManager.Tools.Resources; 6 | 7 | // ReSharper disable InconsistentNaming 8 | 9 | namespace _86BoxManager.ViewModels 10 | { 11 | internal sealed class VMRow : ReactiveObject 12 | { 13 | private static readonly IDictionary _icons; 14 | 15 | static VMRow() 16 | { 17 | var pause = LoadImage(FindResource("/vm-paused.png")); 18 | var wait = LoadImage(FindResource("/vm-waiting.png")); 19 | var run = LoadImage(FindResource("/vm-running.png")); 20 | var stop = LoadImage(FindResource("/vm-stopped.png")); 21 | 22 | _icons = new Dictionary 23 | { 24 | { VM.STATUS_PAUSED, pause }, 25 | { VM.STATUS_WAITING, wait }, 26 | { VM.STATUS_RUNNING, run }, 27 | { VM.STATUS_STOPPED, stop } 28 | }; 29 | } 30 | 31 | public VMRow(VM real) 32 | { 33 | Tag = real; 34 | } 35 | 36 | internal VM Tag { get; set; } 37 | 38 | public bool Focused 39 | { 40 | set => Selected = value; 41 | } 42 | 43 | public bool Selected 44 | { 45 | set 46 | { 47 | var ui = Program.Root; 48 | if (value) 49 | ui.lstVMs.SelectedItems.Add(this); 50 | else 51 | ui.lstVMs.SelectedItems.Remove(this); 52 | } 53 | } 54 | 55 | public Bitmap Icon => _icons[Tag.Status]; 56 | public string Name => Tag.Name; 57 | public string Status => Tag.GetStatusString(); 58 | public string Desc => Tag.Desc; 59 | public string Path => Tag.Path; 60 | 61 | public void SetStatus(string _) 62 | { 63 | // NO OP 64 | } 65 | 66 | public void SetIcon(int status) 67 | { 68 | Tag.Status = status; 69 | this.RaisePropertyChanged(nameof(Icon)); 70 | this.RaisePropertyChanged(nameof(Status)); 71 | this.RaisePropertyChanged(nameof(Tag)); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /86BoxManager/Views/dlgAddVM.axaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /86BoxManager/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following individuals or organizations have contributed at least some code to the 86Box Manager project: 2 | 3 | David Simunič (@daviunic) 4 | David Lee (@DL444) 5 | FolderSelectDialog class by ErikE from stackOverflow 6 | David Hrdlička (@dhrdlicka) 7 | Teemu Korhonen (@ts-korhonen) 8 | Port by @xafero 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2022 David Simunič 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 86Box Manager 2 | **86Box Manager** is an optional configuration manager for the [86Box emulator](https://github.com/86Box/86Box). It's released under the MIT license, so it can be freely distributed with 86Box. See the `LICENSE` file for license information and `AUTHORS` for a complete list of contributors and authors. 3 | 4 | It's written in C# with Avalonia. Please see the [wiki](https://github.com/86Box/86BoxManager/wiki) for additional information. 5 | 6 | ## Features 7 | * Powerful, lightweight and completely optional 8 | * Create multiple isolated virtual machines 9 | * Give each virtual machine a unique name and an optional description 10 | * Run multiple virtual machines at the same time 11 | * Control virtual machines from the Manager (pause, reset, etc.) 12 | * A tray icon so the Manager window doesn't get in your way 13 | 14 | ## System requirements 15 | System requirements are the same as for 86Box. Additionally, the following is required: 16 | 17 | * [86Box 3.0](https://github.com/86Box/86Box/releases) or later (earlier builds don't support all the features the Manager expects) 18 | * [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0) 19 | 20 | ## Support 21 | If you have any issues, questions, suggestions, etc., please follow the [troubleshooting steps](https://github.com/86Box/86BoxManager/wiki/Troubleshooting-steps) or visit the official 86Box support channels on IRC and Discord (see the main 86Box repo for links). Lead developer, daviunic, is often idling there under the name Overdoze. 22 | 23 | ## How to use 24 | 1. Download the desired build [here](https://github.com/86Box/86BoxManager/releases) 25 | 2. Run `86Manager.exe` 26 | 3. Go to Settings, choose the folder where `86Box.exe` is located (along with the roms folder) and a folder where your virtual machines will be located (for configs, nvr folders, etc.) 27 | 4. Start creating new virtual machines and enjoy 28 | 29 | ## How to build 30 | 1. Clone the repo 31 | 2. Open `86BoxManager.sln` solution file in Visual Studio 32 | 3. Make your changes 33 | 4. Choose the `Release` configuration and `x86` platform/CPU 34 | 5. Build the solution 35 | 6. `86Manager.exe` is now in `Bin\x86\Release\` 36 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir dist 3 | dotnet publish 86BoxManager -r win-x64 -c Release --self-contained true -o dist/win 4 | dotnet publish 86BoxManager -r linux-x64 -c Release --self-contained true -o dist/lin 5 | dotnet publish 86BoxManager -r osx-x64 -c Release --self-contained true -o dist/osx 6 | mkdir pub 7 | zip -q -r pub/86BoxManager_win.zip dist/win 8 | tar -czf pub/86BoxManager_linux.tar.gz dist/lin 9 | tar -czf pub/86BoxManager_mac.tar.gz dist/osx 10 | --------------------------------------------------------------------------------