├── .gitignore ├── Screenshot └── Screenshot1.png ├── Source ├── Yava │ ├── Resources │ │ └── gnome-joystick.ico │ ├── Assembly │ │ └── AssemblyInfo.cs │ ├── Controls │ │ └── DoubleBufferedListView.cs │ ├── Program.cs │ ├── FoldersFile │ │ ├── FolderFile.cs │ │ ├── FoldersFileReadError.cs │ │ ├── FoldersFileReaderFolder.cs │ │ ├── Folder.cs │ │ └── FoldersFileReader.cs │ ├── YavaSettings.cs │ ├── Util.cs │ ├── Yava.csproj │ ├── Libraries │ │ ├── mINI │ │ │ └── mINI.cs │ │ └── LowKey │ │ │ └── LowKey.cs │ └── Yava.cs └── Yava.sln ├── Documentation ├── Changelog └── License ├── Tools ├── create-icon.bat └── gnome-joystick.svg ├── Extra └── Folders.ini └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Source/Yava/bin 2 | Source/Yava/obj 3 | Source/*.suo 4 | 5 | -------------------------------------------------------------------------------- /Screenshot/Screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Beluki/Yava/HEAD/Screenshot/Screenshot1.png -------------------------------------------------------------------------------- /Source/Yava/Resources/gnome-joystick.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Beluki/Yava/HEAD/Source/Yava/Resources/gnome-joystick.ico -------------------------------------------------------------------------------- /Documentation/Changelog: -------------------------------------------------------------------------------- 1 | 2 | CHANGELOG 3 | 4 | * 2016/02/04: 5 | 6 | - Revised. Working on VS2015 Community Edition and latest .NET Framework. 7 | 8 | * 2015/09/28: 9 | 10 | - First complete version. 11 | 12 | -------------------------------------------------------------------------------- /Source/Yava/Assembly/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System.Reflection; 7 | 8 | 9 | [assembly: AssemblyTitle("Yava Launcher")] 10 | [assembly: AssemblyProduct("Yava")] 11 | [assembly: AssemblyVersion("2016.04.02")] 12 | [assembly: AssemblyFileVersion("2016.04.02")] 13 | 14 | -------------------------------------------------------------------------------- /Source/Yava/Controls/DoubleBufferedListView.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.Windows.Forms; 8 | 9 | 10 | namespace Yava.Controls 11 | { 12 | [System.ComponentModel.DesignerCategory("")] 13 | internal class DoubleBufferedListView : ListView 14 | { 15 | public DoubleBufferedListView() 16 | { 17 | DoubleBuffered = true; 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Documentation/License: -------------------------------------------------------------------------------- 1 | 2 | LICENSE 3 | 4 | Permission is hereby granted, free of charge, to anyone 5 | obtaining a copy of this document and accompanying files, 6 | to do whatever they want with them without any restriction, 7 | including, but not limited to, copying, modification and redistribution. 8 | 9 | NO WARRANTY OF ANY KIND IS PROVIDED. 10 | 11 | 12 | As an exception, the following files: 13 | 14 | 15 | 16 | are distributed under the GNU Lesser General Public License 17 | because they are based on the GNOME version of Nuvola: 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/Yava.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yava", "Yava\Yava.csproj", "{38504426-B0C1-4204-98CE-D60B0F1EB8C1}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {38504426-B0C1-4204-98CE-D60B0F1EB8C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {38504426-B0C1-4204-98CE-D60B0F1EB8C1}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {38504426-B0C1-4204-98CE-D60B0F1EB8C1}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {38504426-B0C1-4204-98CE-D60B0F1EB8C1}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /Source/Yava/Program.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.IO; 8 | using System.Windows.Forms; 9 | 10 | 11 | namespace Yava 12 | { 13 | internal class Program 14 | { 15 | /// 16 | /// The main entry point for the application. 17 | /// 18 | [STAThread] 19 | private static void Main() 20 | { 21 | // default path for the settings and the folders file: 22 | String currentFolder = Util.ApplicationFolder; 23 | 24 | String settingsFilepath = Path.Combine(currentFolder, "Yava.dat"); 25 | String foldersFilepath = Path.Combine(currentFolder, "Folders.ini"); 26 | 27 | Application.EnableVisualStyles(); 28 | Application.SetCompatibleTextRenderingDefault(false); 29 | Application.Run(new Yava(settingsFilepath, foldersFilepath)); 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Source/Yava/FoldersFile/FolderFile.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | 8 | 9 | namespace Yava.FoldersFile 10 | { 11 | internal class FolderFile 12 | { 13 | /// 14 | /// The folder this file belongs to. 15 | /// 16 | public readonly Folder Folder; 17 | 18 | /// 19 | /// File path in the filesystem. 20 | /// 21 | public readonly String Path; 22 | 23 | /// 24 | /// A file specification. 25 | /// Folder.EnumerateFiles() generates them from the filesystem. 26 | /// 27 | /// The folder this file belongs to. 28 | /// File path in the filesystem. 29 | public FolderFile(Folder folder, String path) 30 | { 31 | this.Folder = folder; 32 | this.Path = path; 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Tools/create-icon.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM PATH to the Imagemagick convert.exe program: 4 | set imconvert=C:\Imagemagick\convert.exe 5 | 6 | 7 | %imconvert% -density 500 -background none gnome-joystick.svg -resize 16x16 icon16px.png 8 | %imconvert% -density 500 -background none gnome-joystick.svg -resize 24x24 icon24px.png 9 | %imconvert% -density 500 -background none gnome-joystick.svg -resize 32x32 icon32px.png 10 | %imconvert% -density 500 -background none gnome-joystick.svg -resize 48x48 icon48px.png 11 | %imconvert% -density 500 -background none gnome-joystick.svg -resize 64x64 icon64px.png 12 | %imconvert% -density 500 -background none gnome-joystick.svg -resize 128x128 icon128px.png 13 | %imconvert% -density 500 -background none gnome-joystick.svg -resize 256x256 icon256px.png 14 | 15 | %imconvert% icon16px.png ^ 16 | icon24px.png ^ 17 | icon32px.png ^ 18 | icon48px.png ^ 19 | icon64px.png ^ 20 | icon128px.png ^ 21 | icon256px.png ^ 22 | gnome-joystick.ico 23 | 24 | del *.png 25 | 26 | -------------------------------------------------------------------------------- /Source/Yava/FoldersFile/FoldersFileReadError.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | 8 | 9 | namespace Yava.FoldersFile 10 | { 11 | internal class FoldersFileReadError : Exception 12 | { 13 | /// 14 | /// Path to the file that triggered the error. 15 | /// 16 | public readonly String FilePath; 17 | 18 | /// 19 | /// Text for the incorrect line. 20 | /// 21 | public readonly String Line; 22 | 23 | /// 24 | /// Line number where the error happened. 25 | /// 26 | public readonly Int32 LineNumber; 27 | 28 | /// 29 | /// Raised by FoldersFileReader on a reading error. 30 | /// 31 | /// Error message. 32 | /// Path to the file that triggered the error. 33 | /// Text for the incorrect line. 34 | /// Line number where the error happened. 35 | public FoldersFileReadError(String message, String filepath, String line, Int32 linenumber) : base(message) 36 | { 37 | FilePath = filepath; 38 | Line = line; 39 | LineNumber = linenumber; 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Source/Yava/YavaSettings.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | 10 | using Yava.FoldersFile; 11 | 12 | 13 | namespace Yava 14 | { 15 | [Serializable] 16 | internal class YavaSettings 17 | { 18 | /// 19 | /// Last form width. 20 | /// 21 | public Int32 LastYavaFormWidth; 22 | 23 | /// 24 | /// Last form height. 25 | /// 26 | public Int32 LastYavaFormHeight; 27 | 28 | /// 29 | /// Last folders listview width. 30 | /// 31 | public Int32 LastFoldersListViewWidth; 32 | 33 | /// 34 | /// Last files listview width. 35 | /// 36 | public Int32 LastFilesListViewWidth; 37 | 38 | /// 39 | /// Last selected folder name in the folders listview. 40 | /// 41 | public String LastSelectedFolderName; 42 | 43 | /// 44 | /// A map from folder names to their last selected file path. 45 | /// 46 | public Dictionary FolderNameToLastSelectedFilePath; 47 | 48 | /// 49 | /// Stores program settings. 50 | /// 51 | public YavaSettings() 52 | { 53 | LastYavaFormWidth = 640; 54 | LastYavaFormHeight = 480; 55 | LastFoldersListViewWidth = 200; 56 | LastFilesListViewWidth = 400; 57 | LastSelectedFolderName = null; 58 | FolderNameToLastSelectedFilePath = new Dictionary(); 59 | } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /Source/Yava/FoldersFile/FoldersFileReaderFolder.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | 10 | namespace Yava.FoldersFile 11 | { 12 | internal class FoldersFileReaderFolder 13 | { 14 | /// 15 | /// Folder name (section in the ini). 16 | /// 17 | public String Name; 18 | 19 | /// 20 | /// Folder path in the filesystem. 21 | /// 22 | public String Path; 23 | 24 | /// 25 | /// Application to start. 26 | /// 27 | public String Executable; 28 | 29 | /// 30 | /// Extensions to include when searching for files. 31 | /// Can be null when unspecified in the ini file. 32 | /// 33 | public HashSet Extensions; 34 | 35 | /// 36 | /// Command-line arguments to use when launching the application. 37 | /// Can be null when unspecified in the ini file. 38 | /// 39 | public String Parameters; 40 | 41 | /// 42 | /// Initial directory for the application to be started. 43 | /// Can be null when unspecified in the ini file. 44 | /// 45 | public String WorkingDirectory; 46 | 47 | /// 48 | /// A folder specification. 49 | /// Used inside FoldersFileReader to gather the data for each folder. 50 | /// Unlike the Folder class this one initializes everything to null and has mutable fields. 51 | /// 52 | public FoldersFileReaderFolder() 53 | { 54 | this.Name = null; 55 | this.Path = null; 56 | this.Executable = null; 57 | this.Extensions = null; 58 | this.Parameters = null; 59 | this.WorkingDirectory = null; 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /Extra/Folders.ini: -------------------------------------------------------------------------------- 1 | 2 | # My folders file with all the emulators that I actually use. 3 | # Should be a good example or starting point to create your own. 4 | 5 | [Gameboy] 6 | path = Romset\Gameboy 7 | executable = Emulators\VBA-M 1229\Play\VisualBoyAdvance-M.exe 8 | extensions = zip 9 | 10 | [Gameboy Advance] 11 | path = Romset\Gameboy Advance 12 | executable = Emulators\VBA-M 1229\Play\VisualBoyAdvance-M.exe 13 | extensions = zip 14 | 15 | [Gameboy Color] 16 | path = Romset\Gameboy Color 17 | executable = Emulators\VBA-M 1229\Play\VisualBoyAdvance-M.exe 18 | extensions = zip 19 | 20 | [Genesis] 21 | path = Romset\Genesis 22 | executable = Emulators\Kega Fusion 3.64\Play\Fusion.exe 23 | extensions = zip 24 | 25 | [Mame 0.72] 26 | path = Romset\Mame 0.72 27 | executable = Emulators\Mame 0.72\Play\Mame32.exe 28 | extensions = zip 29 | 30 | [Mame 0.149] 31 | path = Romset\Mame 0.149 32 | executable = Emulators\Mame 0.149\Play\Mameui64.exe 33 | extensions = zip 34 | 35 | [Nes] 36 | path = Romset\Nes 37 | executable = Emulators\Fceux 2.2.2\Play\fceux.exe 38 | extensions = zip 39 | 40 | [Nintendo 64] 41 | path = Romset\Nintendo 64 42 | executable = Emulators\BizHawk 1.7.4\Play\EmuHawk.exe 43 | extensions = zip 44 | 45 | [Nintendo DS] 46 | path = Romset\Nintendo DS 47 | executable = Emulators\DesMuMe 0.9.10\Play\DeSmuME_0.9.10_x86.exe 48 | extensions = nds 49 | 50 | [Playstation 2] 51 | path = Romset\Playstation 2 52 | executable = Emulators\PCSX2 1.2.1\Play\pcsx2-r5875.exe 53 | parameters = --nogui "%FILEPATH%" 54 | extensions = iso 55 | 56 | [PSP] 57 | path = Romset\PSP 58 | executable = Emulators\PPSSPP 0.9.9\Play\PPSSPPWindows.exe 59 | extensions = cso, iso 60 | 61 | [Super Nintendo] 62 | path = Romset\Super Nintendo 63 | executable = Emulators\Snes 9x 1.53\Play\snes9x.exe 64 | extensions = zip 65 | 66 | [Wii] 67 | path = Romset\Wii 68 | executable = Emulators\Dolphin 4.0.3\Play\Dolphin.exe 69 | parameters = --batch --exec "%FILEPATH%" 70 | extensions = iso, wbfs 71 | 72 | [Wii (Wii Ware)] 73 | path = Romset\Wii (Wii Ware) 74 | executable = Emulators\Dolphin 4.0.3\Play\Dolphin.exe 75 | parameters = --batch --exec "%FILEPATH%" 76 | extensions = wad 77 | 78 | -------------------------------------------------------------------------------- /Source/Yava/Util.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.Drawing; 8 | using System.IO; 9 | using System.Reflection; 10 | using System.Runtime.Serialization; 11 | using System.Runtime.Serialization.Formatters.Binary; 12 | using System.Windows.Forms; 13 | 14 | 15 | namespace Yava 16 | { 17 | internal static class Util 18 | { 19 | /// 20 | /// IO 21 | /// 22 | 23 | /// 24 | /// Serialize an object to a binary file. 25 | /// 26 | /// Object to serialize. 27 | /// Destination path. 28 | public static void Serialize(Object value, String filepath) 29 | { 30 | BinaryFormatter formatter = new BinaryFormatter(); 31 | using (FileStream fs = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write)) 32 | { 33 | formatter.Serialize(fs, value); 34 | } 35 | } 36 | 37 | /// 38 | /// Deserialize an object from a binary file. 39 | /// 40 | /// File path. 41 | public static Object Deserialize(String filepath) 42 | { 43 | BinaryFormatter formatter = new BinaryFormatter(); 44 | using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read)) 45 | { 46 | return formatter.Deserialize(fs); 47 | } 48 | } 49 | 50 | /// 51 | /// MessageBoxes 52 | /// 53 | 54 | /// 55 | /// Show a MessageBox with Yes and No buttons. 56 | /// Return true when Yes is clicked, false otherwise. 57 | /// 58 | /// MessageBox text. 59 | /// MessageBox caption. 60 | public static Boolean MessageBoxYesNo(String text, String caption) 61 | { 62 | return MessageBox.Show(text, caption, MessageBoxButtons.YesNo) == DialogResult.Yes; 63 | } 64 | 65 | /// 66 | /// OS information 67 | /// 68 | 69 | /// 70 | /// Get the path for the directory that contains 71 | /// the current application executable. 72 | /// 73 | public static String ApplicationFolder 74 | { 75 | get 76 | { 77 | return Path.GetDirectoryName(Application.ExecutablePath); 78 | } 79 | } 80 | 81 | /// 82 | /// Resources 83 | /// 84 | 85 | /// 86 | /// Load an embedded resource as an icon. 87 | /// 88 | /// Resource name, including namespace. 89 | public static Icon ResourceAsIcon(String resource) 90 | { 91 | Assembly assembly = Assembly.GetExecutingAssembly(); 92 | using (Stream stream = assembly.GetManifestResourceStream(resource)) 93 | { 94 | return new Icon(stream); 95 | } 96 | } 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Source/Yava/FoldersFile/Folder.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | 10 | 11 | namespace Yava.FoldersFile 12 | { 13 | internal class Folder 14 | { 15 | /// 16 | /// Folder name (section in the ini). 17 | /// 18 | public readonly String Name; 19 | 20 | /// 21 | /// Folder path in the filesystem. 22 | /// 23 | public readonly String Path; 24 | 25 | /// 26 | /// Application to start. 27 | /// 28 | public readonly String Executable; 29 | 30 | /// 31 | /// Extensions to include when searching for files. 32 | /// Can be null when unspecified in the ini file. 33 | /// 34 | public readonly HashSet Extensions; 35 | 36 | /// 37 | /// Command-line arguments to use when launching the application. 38 | /// Can be null when unspecified in the ini file. 39 | /// 40 | public readonly String Parameters; 41 | 42 | /// 43 | /// Initial directory for the application to be started. 44 | /// Can be null when unspecified in the ini file. 45 | /// 46 | public readonly String WorkingDirectory; 47 | 48 | /// 49 | /// A folder specification. 50 | /// FoldersFileReader generates them when reading the folders ini file. 51 | /// 52 | /// Folder name. 53 | /// Folder path in the filesystem. 54 | /// Application to start. 55 | /// Extensions to include when searching for files. 56 | /// Command-line arguments to use when launching the application. 57 | /// Initial directory for the application to be started. 58 | public Folder(String name, String path, String executable, HashSet extensions, String parameters, String workingdirectory) 59 | { 60 | this.Name = name; 61 | this.Path = path; 62 | this.Executable = executable; 63 | this.Extensions = extensions; 64 | this.Parameters = parameters; 65 | this.WorkingDirectory = workingdirectory; 66 | } 67 | 68 | /// 69 | /// Search this folder path and return the matching files. 70 | /// 71 | public IEnumerable EnumerateFiles() 72 | { 73 | // no extensions specified: 74 | if (Extensions == null) 75 | { 76 | foreach (String filepath in Directory.EnumerateFiles(Path)) 77 | { 78 | yield return new FolderFile(this, filepath); 79 | } 80 | } 81 | // filter by the specified extensions: 82 | else 83 | { 84 | foreach (String filepath in Directory.EnumerateFiles(Path)) 85 | { 86 | String extension = System.IO.Path.GetExtension(filepath); 87 | if (Extensions.Contains(extension)) 88 | { 89 | yield return new FolderFile(this, filepath); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /Source/Yava/Yava.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {38504426-B0C1-4204-98CE-D60B0F1EB8C1} 9 | WinExe 10 | Properties 11 | Yava 12 | Yava 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | true 41 | bin\Debug\ 42 | DEBUG;TRACE 43 | full 44 | AnyCPU 45 | prompt 46 | false 47 | false 48 | true 49 | 50 | 51 | bin\Release\ 52 | TRACE 53 | true 54 | pdbonly 55 | AnyCPU 56 | prompt 57 | false 58 | false 59 | true 60 | 61 | 62 | Resources\gnome-joystick.ico 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 97 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## About 3 | 4 | Yava is a small emulator/game launcher for Windows. In short: it's a convenient 5 | menu to run all your games from a single place. It's also portable, meaning you 6 | can put Yava and all your emulators/roms in a usb key and carry them anywhere. 7 | 8 | Here is a screenshot: 9 | 10 | ![Screenshot1](Screenshot/Screenshot1.png) 11 | 12 | Features: 13 | 14 | * Simple, fast, lightweight, robust. 15 | 16 | * Easy to configure through an INI file. The file can be reloaded from the GUI. 17 | 18 | * It doesn't try manage your roms or impose any particular organization. 19 | In fact, it doesn't even keep an internal database, just uses folders and files. 20 | 21 | * It can launch anything, with any command-line parameters. Emulators tested 22 | include Bizhawk, DesMuMe, Dolphin, Fceux, Kega, Mame, PCSX2, DosBox... 23 | 24 | * Provides hotkeys to close the launched emulators, both properly and forcefully. 25 | 26 | * Remembers the last selected file for each folder. 27 | 28 | * You can "search as you type" like in Windows Explorer. Multiple folders can be 29 | selected, so it's possible to search the entire rom collection. 30 | 31 | * A single exe with no dependencies other than the .NET Framework 4.0. 32 | 33 | ## The folders file 34 | 35 | Yava is configured using a INI file, named "Folders.ini". This file is located in 36 | the same folder as Yava.exe and contains everything Yava needs to know about the 37 | folders and files it will launch. 38 | 39 | Here is an example: 40 | 41 | ```ini 42 | [Genesis] 43 | path = Romset\Genesis 44 | executable = Emulators\Kega Fusion\Fusion.exe 45 | 46 | [Super Nintendo] 47 | path = Romset\Super Nintendo 48 | executable = Emulators\Snes 9x\snes9x.exe 49 | ``` 50 | 51 | Each section in the INI file represents a folder in Yava's left panel. 52 | 53 | Here are all the available specifications for a section: 54 | 55 | * `path`: Where the files for this folder are located in the filesystem. Required. 56 | 57 | * `executable`: What program to use to launch the files. Required. 58 | 59 | * `extensions`: A comma-separated list of extensions to filter the files with. 60 | Optional: Yava will display all files by default. Example: `zip, 7z, smc` 61 | 62 | * `parameters`: Additional command-line arguments to add to the executable. 63 | Optional: `"%FILEPATH%"` by default. Example: `"%FILEPATH%" --video fullscreen` 64 | 65 | * `workingdirectory`: the startup path for the executable. 66 | Optional: Yava will use the same folder where the executable is located by default. 67 | 68 | All the paths can be either absolute or relative. 69 | 70 | When specifying `executable`, `parameters` and `workingdirectory`, you can use `%FILEPATH%` 71 | and `%FOLDERPATH%` to refer to the current file being launched and the folder it belongs to 72 | respectively. 73 | 74 | Here is a more complete example, for the Dolphin emulator: 75 | 76 | ```ini 77 | [Wii] 78 | path = Romset\Wii 79 | executable = Emulators\Dolphin 4.0.3\Play\Dolphin.exe 80 | parameters = --batch --exec "%FILEPATH%" 81 | extensions = iso, wbfs 82 | ``` 83 | 84 | A complete [Folders.ini][] (the one I use) is available in the [Extra][] folder 85 | in the repository. 86 | 87 | [Folders.ini]: Extra/Folders.ini 88 | [Extra]: Extra 89 | 90 | ## Compiling and installation 91 | 92 | Building Yava is a matter of opening the included Visual Studio 2010 93 | solution and clicking the build button (or using msbuild). The source code 94 | has no dependencies other than the [.NET Framework][] 4.0+. 95 | 96 | There are binaries for the latest version in the [Releases][] tab above. 97 | 98 | Yava doesn't need to be installed. It can run from any folder and doesn't 99 | write to the Windows registry. It's possible to run it from an usb stick 100 | provided the .NET Framework is available on the target machine. 101 | 102 | [.NET Framework]: http://www.microsoft.com/en-us/download/details.aspx?id=30653 103 | [Releases]: https://github.com/Beluki/Yava/releases 104 | 105 | ## Keyboard shortcuts 106 | 107 | In Yava itself: 108 | 109 | Key | Use 110 | :--------: | :---------------------------------------------------------- 111 | Tab | Change between the left and right panel. 112 | F5 | Reload the Folders.ini file. 113 | 114 | While running a game: 115 | 116 | Key | Use 117 | :-------------------------: | :----------------------------------------------------------- 118 | Control + Shift + C | Close the game, properly (equivalent to closing the window). 119 | Control + Shift + K | Close the game, forcefully (kills the game process). 120 | 121 | ## Portability 122 | 123 | Yava is tested on Windows 7 and 8, using the .NET Framework 4.0+. 124 | [Mono][] is not supported. 125 | 126 | The folders file encoding is UTF-8 with or without a BOM signature. Notepad 127 | will work, although I suggest something better such as [Notepad2][] or 128 | [Sublime Text][]. 129 | 130 | [Mono]: http://mono-project.com 131 | [Notepad2]: http://www.flos-freeware.ch/notepad2.html 132 | [Sublime Text]: http://www.sublimetext.com 133 | 134 | ## Status 135 | 136 | This program is finished! 137 | 138 | Yava is feature-complete and has no known bugs. Unless issues are reported 139 | I plan no further development on it other than maintenance. 140 | 141 | ## License 142 | 143 | Like all my hobby projects, this is Free Software. See the [Documentation][] 144 | folder for more information. No warranty though. 145 | 146 | [Documentation]: Documentation 147 | 148 | -------------------------------------------------------------------------------- /Source/Yava/Libraries/mINI/mINI.cs: -------------------------------------------------------------------------------- 1 | 2 | // mINI. 3 | // A minimal, customizable INI reader for .NET in a single abstract class. 4 | 5 | 6 | using System; 7 | 8 | 9 | namespace mINI 10 | { 11 | public abstract class INIReader 12 | { 13 | /// 14 | /// Called when the current line is empty. 15 | /// 16 | protected virtual void OnEmpty() {} 17 | 18 | /// 19 | /// Called when the current line is a comment. 20 | /// 21 | /// Comment text, prefix (; or #) included. 22 | protected virtual void OnComment(String text) {} 23 | 24 | /// 25 | /// Called when the current line is a section, 26 | /// before reading subsections. 27 | /// 28 | /// 29 | /// Complete section name, regardless of subsections 30 | /// and inner whitespace. Example: "a/b /c/ d". 31 | /// 32 | protected virtual void OnSection(String section) {} 33 | 34 | /// 35 | /// Called when a section name is empty, not including subsections. 36 | /// This method is called before calling OnSection. 37 | /// 38 | protected virtual void OnSectionEmpty() {} 39 | 40 | /// 41 | /// Called each time a subsection is found in a section line. 42 | /// 43 | /// Example: for a line such as: [a/b/c], this method 44 | /// is called 3 times with the following arguments: 45 | /// 46 | /// OnSubSection("a", "a") 47 | /// OnSubSection("b", "a/b") 48 | /// OnSubSection("c", "a/b/c") 49 | /// 50 | /// Subsection name. 51 | /// Subsection path, including parents. 52 | protected virtual void OnSubSection(String subsection, String path) {} 53 | 54 | /// 55 | /// Called when a subsection name is empty. 56 | /// This method is called before calling OnSubSection. 57 | /// 58 | /// Subsection path, including parents. 59 | protected virtual void OnSubSectionEmpty(String path) {} 60 | 61 | /// 62 | /// Called when the current line is a key=value pair. 63 | /// 64 | /// Key. 65 | /// Value associated with the key. 66 | protected virtual void OnKeyValue(String key, String value) {} 67 | 68 | /// 69 | /// Called when the key is empty in a key=value pair. 70 | /// This method is called before calling OnKeyValue. 71 | /// 72 | /// Value associated with the key. 73 | protected virtual void OnKeyEmpty(String value) {} 74 | 75 | /// 76 | /// Called when the value is empty in a key=value pair. 77 | /// This method is called before calling OnKeyValue. 78 | /// 79 | /// Key specified for the value. 80 | protected virtual void OnValueEmpty(String key) {} 81 | 82 | /// 83 | /// Called when the reader is unable to read the current line. 84 | /// 85 | /// Complete input line, not trimmed. 86 | protected virtual void OnUnknown(String line) {} 87 | 88 | /// 89 | /// Try to read an empty line. 90 | /// 91 | /// Input line, trimmed. 92 | private Boolean ReadEmpty(String line) 93 | { 94 | if (line != String.Empty) 95 | return false; 96 | 97 | OnEmpty(); 98 | return true; 99 | } 100 | 101 | /// 102 | /// Try to read a comment. 103 | /// 104 | /// Input line, trimmed. 105 | private Boolean ReadComment(String line) 106 | { 107 | if (!(line.StartsWith("#") || line.StartsWith(";"))) 108 | return false; 109 | 110 | OnComment(line); 111 | return true; 112 | } 113 | 114 | /// 115 | /// Try to read a (possibly nested) section. 116 | /// 117 | /// Input line, trimmed. 118 | private Boolean ReadSection(String line) 119 | { 120 | if (!(line.StartsWith("[") && line.EndsWith("]"))) 121 | return false; 122 | 123 | String section = line.Substring(1, line.Length - 2).Trim(); 124 | 125 | if (section == String.Empty) 126 | OnSectionEmpty(); 127 | 128 | OnSection(section); 129 | ReadSubSections(section); 130 | return true; 131 | } 132 | 133 | /// 134 | /// Read subsections in a given section. 135 | /// 136 | /// Section name, trimmed. 137 | private void ReadSubSections(String section) 138 | { 139 | String[] subsections = section.Split('/'); 140 | 141 | // first subsection is special, no separator, name/path identical: 142 | String path = subsections[0].Trim(); 143 | 144 | if (path == String.Empty) 145 | OnSubSectionEmpty(path); 146 | 147 | OnSubSection(path, path); 148 | 149 | // accumulate path: 150 | for (Int32 i = 1; i < subsections.Length; i++) 151 | { 152 | String subsection = subsections[i].Trim(); 153 | path += "/" + subsection; 154 | 155 | if (subsection == String.Empty) 156 | OnSubSectionEmpty(path); 157 | 158 | OnSubSection(subsection, path); 159 | } 160 | } 161 | 162 | /// 163 | /// Try to read a key=value pair. 164 | /// 165 | /// Input line, trimmed. 166 | private Boolean ReadKeyValue(String line) 167 | { 168 | if (!line.Contains("=")) 169 | return false; 170 | 171 | String[] pair = line.Split(new Char[] { '=' }, 2); 172 | String key = pair[0].Trim(); 173 | String value = pair[1].Trim(); 174 | 175 | if (key == String.Empty) 176 | OnKeyEmpty(value); 177 | 178 | if (value == String.Empty) 179 | OnValueEmpty(key); 180 | 181 | OnKeyValue(key, value); 182 | return true; 183 | } 184 | 185 | /// 186 | /// Read an INI line. 187 | /// 188 | /// Input line. 189 | public void ReadLine(String line) 190 | { 191 | String trimmed_line = line.Trim(); 192 | 193 | if (ReadEmpty(trimmed_line) 194 | || ReadComment(trimmed_line) 195 | || ReadSection(trimmed_line) 196 | || ReadKeyValue(trimmed_line)) 197 | return; 198 | 199 | // not trimmed: 200 | OnUnknown(line); 201 | } 202 | } 203 | } 204 | 205 | -------------------------------------------------------------------------------- /Source/Yava/FoldersFile/FoldersFileReader.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | 10 | using mINI; 11 | 12 | 13 | namespace Yava.FoldersFile 14 | { 15 | internal class FoldersFileReader : INIReader 16 | { 17 | private String filepath; 18 | private IList folders; 19 | 20 | private HashSet seenFolderNames; 21 | private FoldersFileReaderFolder currentFolder; 22 | 23 | private Int32 currentLineNumber; 24 | private String currentLine; 25 | 26 | /// 27 | /// An INIReader that reads lines from a folders file 28 | /// adding sections and key=value pairs to a collection 29 | /// as folder names/options. 30 | /// 31 | public FoldersFileReader() 32 | { 33 | filepath = null; 34 | folders = null; 35 | 36 | seenFolderNames = new HashSet(); 37 | currentFolder = null; 38 | 39 | currentLineNumber = 0; 40 | currentLine = String.Empty; 41 | } 42 | 43 | /// 44 | /// Clear internal state. 45 | /// 46 | private void ResetState() 47 | { 48 | filepath = null; 49 | folders = null; 50 | 51 | seenFolderNames.Clear(); 52 | currentFolder = null; 53 | 54 | currentLineNumber = 0; 55 | currentLine = String.Empty; 56 | } 57 | 58 | /// 59 | /// Concise helper to create FoldersFileReadError exceptions. 60 | /// 61 | /// Error message. 62 | private FoldersFileReadError ReadError(String message) 63 | { 64 | return new FoldersFileReadError( 65 | message, 66 | filepath, 67 | currentLine, 68 | currentLineNumber 69 | ); 70 | } 71 | 72 | /// 73 | /// Add the current folder to the list and move on to the next. 74 | /// 75 | private void AddCurrentFolder() 76 | { 77 | if (currentFolder != null) 78 | { 79 | // check required options: 80 | if (currentFolder.Name == null) 81 | { 82 | throw ReadError("Folder without name."); 83 | } 84 | 85 | if (currentFolder.Path == null) 86 | { 87 | throw ReadError("Expected 'path = value' option for folder: " + currentFolder.Name); 88 | } 89 | 90 | if (currentFolder.Executable == null) 91 | { 92 | throw ReadError("Expected 'executable = value' option for folder: " + currentFolder.Name); 93 | } 94 | 95 | // create and add the new folder: 96 | Folder folder = new Folder( 97 | currentFolder.Name, 98 | currentFolder.Path, 99 | currentFolder.Executable, 100 | currentFolder.Extensions, 101 | currentFolder.Parameters, 102 | currentFolder.WorkingDirectory 103 | ); 104 | 105 | folders.Add(folder); 106 | currentFolder = null; 107 | } 108 | } 109 | 110 | /// 111 | /// Do not accept folders with no name. 112 | /// 113 | protected override void OnSectionEmpty() 114 | { 115 | throw ReadError("Empty folder name."); 116 | } 117 | 118 | /// 119 | /// Do not accept options with no name. 120 | /// 121 | protected override void OnKeyEmpty(String value) 122 | { 123 | throw ReadError("Empty folder option name."); 124 | } 125 | 126 | /// 127 | /// Options without value are fine. 128 | /// (each option validates its own value in OnKeyValue) 129 | /// 130 | protected override void OnValueEmpty(String key) 131 | { 132 | 133 | } 134 | 135 | /// 136 | /// Syntax errors. 137 | /// 138 | protected override void OnUnknown(String line) 139 | { 140 | throw ReadError("Invalid syntax."); 141 | } 142 | 143 | /// 144 | /// On an empty line, add the current folder to the collection 145 | /// and move on to the next one. 146 | /// 147 | protected override void OnEmpty() 148 | { 149 | AddCurrentFolder(); 150 | } 151 | 152 | /// 153 | /// On a new section, add the current folder to the collection 154 | /// and create the next one. 155 | /// 156 | protected override void OnSection(String section) 157 | { 158 | AddCurrentFolder(); 159 | 160 | // folder names must be unique: 161 | String name = section; 162 | 163 | if (seenFolderNames.Contains(name)) 164 | { 165 | throw ReadError("Duplicate folder name: " + name); 166 | } 167 | 168 | seenFolderNames.Add(name); 169 | currentFolder = new FoldersFileReaderFolder(); 170 | currentFolder.Name = name; 171 | } 172 | 173 | /// 174 | /// Try to parse a folder path option. 175 | /// 176 | /// Path to parse. 177 | private String ReadFolderPath(String value) 178 | { 179 | // path cannot be empty: 180 | if (value == String.Empty) 181 | { 182 | throw ReadError("Option 'path' cannot be empty for folder: " + currentFolder.Name); 183 | } 184 | 185 | // path must be syntactically valid: 186 | try 187 | { 188 | Path.GetFullPath(value); 189 | return value; 190 | } 191 | catch (Exception exception) 192 | { 193 | throw ReadError("Unable to read 'path' option: " + exception.Message); 194 | } 195 | } 196 | 197 | /// 198 | /// Try to parse a folder executable option. 199 | /// 200 | /// Path to parse. 201 | private String ReadFolderExecutable(String value) 202 | { 203 | // executable cannot be empty: 204 | if (value == String.Empty) 205 | { 206 | throw ReadError("Option 'executable' cannot be empty for folder: " + currentFolder.Name); 207 | } 208 | 209 | return value; 210 | } 211 | 212 | /// 213 | /// Try to parse a folder extensions option. 214 | /// 215 | /// Comma separated extensions. 216 | private HashSet ReadFolderExtensions(String value) 217 | { 218 | // extensions cannot be empty: 219 | if (value == String.Empty) 220 | { 221 | throw ReadError("Option 'extensions' cannot be empty for folder: " + currentFolder.Name); 222 | } 223 | 224 | String[] extensions = value.Split(','); 225 | 226 | // trim spaces around each extension 227 | // and add a leading dot where needed: 228 | for (Int32 i = 0; i < extensions.Length; i++) 229 | { 230 | String extension = extensions[i].Trim(); 231 | 232 | if (!extension.StartsWith(".")) 233 | { 234 | extension = "." + extension; 235 | } 236 | 237 | extensions[i] = extension; 238 | } 239 | 240 | return new HashSet(extensions); 241 | } 242 | 243 | /// 244 | /// Try to parse folder parameters option. 245 | /// 246 | /// Parameters to parse. 247 | private String ReadFolderParameters(String value) 248 | { 249 | return value; 250 | } 251 | 252 | /// 253 | /// Try to parse folder workingdirectory option. 254 | /// 255 | /// Path to parse. 256 | private String ReadFolderWorkingDirectory(String value) 257 | { 258 | // workingdirectory cannot be empty: 259 | if (value == String.Empty) 260 | { 261 | throw ReadError("Option 'workingdirectory' cannot be empty for folder: " + currentFolder.Name); 262 | } 263 | 264 | return value; 265 | } 266 | 267 | /// 268 | /// Set key=value pairs as options to the current folder. 269 | /// 270 | protected override void OnKeyValue(String key, String value) 271 | { 272 | if (currentFolder != null) 273 | { 274 | switch (key.ToLower()) 275 | { 276 | case "path": 277 | currentFolder.Path = ReadFolderPath(value); 278 | break; 279 | 280 | case "executable": 281 | currentFolder.Executable = ReadFolderExecutable(value); 282 | break; 283 | 284 | case "extensions": 285 | currentFolder.Extensions = ReadFolderExtensions(value); 286 | break; 287 | 288 | case "parameters": 289 | currentFolder.Parameters = ReadFolderParameters(value); 290 | break; 291 | 292 | case "workingdirectory": 293 | currentFolder.WorkingDirectory = ReadFolderWorkingDirectory(value); 294 | break; 295 | 296 | default: 297 | throw ReadError("Unknown folder option: " + key); 298 | } 299 | } 300 | } 301 | 302 | /// 303 | /// Read a folders file adding each folder with its options to a collection. 304 | /// 305 | /// Path to the file to read lines from. 306 | /// Target list to add folders to. 307 | public void Read(String filepath, IList folders) 308 | { 309 | this.filepath = filepath; 310 | this.folders = folders; 311 | 312 | try 313 | { 314 | foreach (String line in File.ReadLines(filepath)) 315 | { 316 | currentLineNumber++; 317 | currentLine = line; 318 | ReadLine(line); 319 | } 320 | 321 | // add the last folder: 322 | AddCurrentFolder(); 323 | } 324 | finally 325 | { 326 | ResetState(); 327 | } 328 | } 329 | } 330 | } 331 | 332 | -------------------------------------------------------------------------------- /Source/Yava/Libraries/LowKey/LowKey.cs: -------------------------------------------------------------------------------- 1 | 2 | // LowKey. 3 | // A simple low-level keyboard hooker for .NET. 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.ComponentModel; 9 | using System.Diagnostics; 10 | using System.Runtime.InteropServices; 11 | using System.Windows.Forms; 12 | using System.Windows.Threading; 13 | 14 | 15 | namespace LowKey 16 | { 17 | /// 18 | /// All the exceptions that KeyboardHook raises are of this type. 19 | /// 20 | public class KeyboardHookException : Exception 21 | { 22 | public KeyboardHookException(String message) : base(message) 23 | { 24 | 25 | } 26 | } 27 | 28 | /// 29 | /// Gives information about a hotkey when an event is fired. 30 | /// 31 | public class KeyboardHookEventArgs : EventArgs 32 | { 33 | public readonly Keys Key; 34 | public readonly Keys Modifiers; 35 | public readonly String Name; 36 | 37 | /// 38 | /// Information about the current hotkey pressed. 39 | /// 40 | /// 41 | /// Hotkey name. 42 | /// 43 | /// 44 | /// Base key that was pressed when the event was fired. 45 | /// 46 | /// 47 | /// Modifiers pressed. 48 | /// 49 | public KeyboardHookEventArgs(String name, Keys key, Keys modifiers) 50 | { 51 | Key = key; 52 | Modifiers = modifiers; 53 | Name = name; 54 | } 55 | } 56 | 57 | /// 58 | /// The LowKey keyboard hooker. 59 | /// 60 | public class KeyboardHook : IDisposable 61 | { 62 | /// 63 | /// Events 64 | /// 65 | 66 | /// 67 | /// Fired when a registered hotkey is released. 68 | /// 69 | public event EventHandler HotkeyUp; 70 | 71 | /// 72 | /// Fired when a registered hotkey is pressed. 73 | /// 74 | public event EventHandler HotkeyDown; 75 | 76 | /// 77 | /// Helpers 78 | /// 79 | 80 | /// 81 | /// Retrieve the last Windows error as a readable string. 82 | /// 83 | private static String LastWin32Error() 84 | { 85 | return new Win32Exception(Marshal.GetLastWin32Error()).Message; 86 | } 87 | 88 | /// 89 | /// Determine which modifiers (Keys.Alt, Keys.Control, Keys.Shift) 90 | /// are currently pressed. 91 | /// 92 | private static Keys PressedModifiers 93 | { 94 | get 95 | { 96 | Keys modifiers = Keys.None; 97 | 98 | if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) 99 | modifiers |= Keys.Alt; 100 | 101 | if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) 102 | modifiers |= Keys.Control; 103 | 104 | if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) 105 | modifiers |= Keys.Shift; 106 | 107 | return modifiers; 108 | } 109 | } 110 | 111 | /// 112 | /// Private data 113 | /// 114 | 115 | private struct Hotkey 116 | { 117 | public readonly Keys Key; 118 | public readonly Keys Modifiers; 119 | 120 | /// 121 | /// Represents a combination of a base key and additional modifiers. 122 | /// 123 | /// 124 | /// Base key. 125 | /// 126 | /// 127 | /// A bitwise combination of additional modifiers 128 | /// e.g: Keys.Control | Keys.Alt. 129 | /// 130 | public Hotkey(Keys key, Keys modifiers = Keys.None) 131 | { 132 | Key = key; 133 | Modifiers = modifiers; 134 | } 135 | } 136 | 137 | /// 138 | /// Virtual key code -> set of modifiers for all the hotkeys. 139 | /// 140 | private readonly Dictionary> hotkeys; 141 | 142 | /// 143 | /// A map from hotkeys to names. 144 | /// 145 | private readonly Dictionary hotkeysToNames; 146 | 147 | /// 148 | /// A map from names to hotkeys. 149 | /// 150 | private readonly Dictionary namesToHotkeys; 151 | 152 | /// 153 | /// A map from hotkeys to a boolean indicating whether 154 | /// we should forward the keypress to further applications. 155 | /// 156 | private readonly Dictionary hotkeysForward; 157 | 158 | /// 159 | /// Current dispatcher. 160 | /// 161 | private readonly Dispatcher dispatcher; 162 | 163 | /// 164 | /// Hook ID. 165 | /// Will be IntPtr.Zero when not currently hooked. 166 | /// 167 | private IntPtr hookID; 168 | 169 | /// 170 | /// Needed to avoid the delegate being garbage-collected. 171 | /// 172 | private static HOOKPROC hookedCallback; 173 | 174 | /// 175 | /// Hooker instance. 176 | /// 177 | private static KeyboardHook instance; 178 | 179 | /// 180 | /// Create a new keyboard hooker instance. 181 | /// 182 | private KeyboardHook() 183 | { 184 | hotkeys = new Dictionary>(); 185 | 186 | hotkeysToNames = new Dictionary(); 187 | namesToHotkeys = new Dictionary(); 188 | hotkeysForward = new Dictionary(); 189 | 190 | dispatcher = Dispatcher.CurrentDispatcher; 191 | hookID = IntPtr.Zero; 192 | hookedCallback = Callback; 193 | } 194 | 195 | /// 196 | /// Public interface 197 | /// 198 | 199 | /// 200 | /// Get the hooker instance. 201 | /// 202 | public static KeyboardHook Hooker 203 | { 204 | get 205 | { 206 | if (instance == null) 207 | { 208 | instance = new KeyboardHook(); 209 | } 210 | 211 | return instance; 212 | } 213 | } 214 | 215 | /// 216 | /// Dispose the hooker. 217 | /// 218 | public void Dispose() 219 | { 220 | if (hookID != IntPtr.Zero) 221 | { 222 | Unhook(); 223 | } 224 | 225 | instance = null; 226 | } 227 | 228 | /// 229 | /// Add the specified hotkey to the hooker. 230 | /// 231 | /// 232 | /// Hotkey name. 233 | /// 234 | /// 235 | /// Base key. 236 | /// 237 | /// 238 | /// A bitwise combination of additional modifiers 239 | /// e.g: Keys.Control | Keys.Alt. 240 | /// 241 | /// 242 | /// Whether the keypress should be forwarded to 243 | /// other applications. 244 | /// 245 | public void Add(String name, Keys key, Keys modifiers = Keys.None, Boolean forward = false) 246 | { 247 | // check name: 248 | if (name == null) 249 | throw new KeyboardHookException("Invalid hotkey name."); 250 | 251 | if (namesToHotkeys.ContainsKey(name)) 252 | throw new KeyboardHookException(String.Format("Duplicate hotkey name: {0}.", name)); 253 | 254 | // check key code and modifiers: 255 | Int32 vkCode = (Int32) key; 256 | 257 | // known base key: 258 | if (hotkeys.ContainsKey(vkCode)) 259 | { 260 | // check that modifiers are new: 261 | HashSet currentModifiers = hotkeys[vkCode]; 262 | if (currentModifiers.Contains(modifiers)) 263 | { 264 | Hotkey previousHotkey = new Hotkey(key, modifiers); 265 | throw new KeyboardHookException( 266 | String.Format( 267 | "Hotkey: {0} already registered as: {1}.", 268 | name, 269 | hotkeysToNames[previousHotkey] 270 | ) 271 | ); 272 | } 273 | 274 | currentModifiers.Add(modifiers); 275 | } 276 | // new base key: 277 | else 278 | { 279 | hotkeys[vkCode] = new HashSet() { modifiers }; 280 | } 281 | 282 | // add it to the lookup dicts: 283 | Hotkey hotkey = new Hotkey(key, modifiers); 284 | 285 | hotkeysToNames[hotkey] = name; 286 | namesToHotkeys[name] = hotkey; 287 | hotkeysForward[hotkey] = forward; 288 | } 289 | 290 | /// 291 | /// Remove the specified hotkey. 292 | /// 293 | /// 294 | /// Hotkey name that was specified when calling Add(). 295 | /// 296 | public void Remove(String name) 297 | { 298 | // check the name: 299 | if (name == null) 300 | throw new KeyboardHookException("Invalid hotkey name."); 301 | 302 | if (!namesToHotkeys.ContainsKey(name)) 303 | throw new KeyboardHookException(String.Format("Unknown hotkey name: {0}.", name)); 304 | 305 | Hotkey hotkey = namesToHotkeys[name]; 306 | 307 | // remove from all dicts: 308 | Int32 vkCode = (Int32) hotkey.Key; 309 | Keys modifiers = hotkey.Modifiers; 310 | 311 | hotkeys[vkCode].Remove(modifiers); 312 | hotkeysToNames.Remove(hotkey); 313 | namesToHotkeys.Remove(name); 314 | hotkeysForward.Remove(hotkey); 315 | } 316 | 317 | /// 318 | /// Remove all the registered hotkeys. 319 | /// 320 | public void Clear() 321 | { 322 | hotkeys.Clear(); 323 | hotkeysToNames.Clear(); 324 | namesToHotkeys.Clear(); 325 | hotkeysForward.Clear(); 326 | } 327 | 328 | /// 329 | /// Modify a hotkey binding. 330 | /// 331 | /// 332 | /// Hotkey name that was specified when calling Add(). 333 | /// 334 | /// 335 | /// New base key. 336 | /// 337 | /// 338 | /// New modifiers. 339 | /// 340 | /// 341 | /// Whether the keypress should be forwarded to 342 | /// other applications. 343 | /// 344 | public void Rebind(String name, Keys key, Keys modifiers = Keys.None, Boolean forward = false) 345 | { 346 | Remove(name); 347 | Add(name, key, modifiers, forward); 348 | } 349 | 350 | /// 351 | /// Start looking for key presses. 352 | /// 353 | public void Hook() 354 | { 355 | // don't hook twice: 356 | if (hookID != IntPtr.Zero) 357 | { 358 | throw new KeyboardHookException("Keyboard hook already active. Call Unhook() first."); 359 | } 360 | 361 | using (Process process = Process.GetCurrentProcess()) 362 | { 363 | using (ProcessModule module = process.MainModule) 364 | { 365 | IntPtr hMod = GetModuleHandle(module.ModuleName); 366 | hookID = SetWindowsHookEx(WH_KEYBOARD_LL, hookedCallback, hMod, 0); 367 | 368 | // when SetWindowsHookEx fails, the result is NULL: 369 | if (hookID == IntPtr.Zero) 370 | { 371 | throw new KeyboardHookException("SetWindowsHookEx() failed: " + LastWin32Error()); 372 | } 373 | } 374 | } 375 | } 376 | 377 | /// 378 | /// Stop looking for key presses. 379 | /// 380 | public void Unhook() 381 | { 382 | // not hooked: 383 | if (hookID == IntPtr.Zero) 384 | { 385 | throw new KeyboardHookException("Keyboard hook not currently active. Call Hook() first."); 386 | } 387 | 388 | // when UnhookWindowsHookEx fails, the result is false: 389 | if (!UnhookWindowsHookEx(hookID)) 390 | { 391 | throw new KeyboardHookException("UnhookWindowsHookEx() failed: " + LastWin32Error()); 392 | } 393 | 394 | hookID = IntPtr.Zero; 395 | } 396 | 397 | /// 398 | /// Determine whether the hook is currently active. 399 | /// 400 | public Boolean IsHooked 401 | { 402 | get 403 | { 404 | return hookID != IntPtr.Zero; 405 | } 406 | } 407 | 408 | /// 409 | /// Actual hooker callback 410 | /// 411 | 412 | /// 413 | /// Callback that intercepts key presses. 414 | /// 415 | private IntPtr Callback(Int32 nCode, IntPtr wParam, IntPtr lParam) 416 | { 417 | // assume the hotkey won't match and will be forwarded: 418 | Boolean forward = true; 419 | 420 | if (nCode >= 0) 421 | { 422 | Int32 msg = wParam.ToInt32(); 423 | 424 | // we care about keyup/keydown messages: 425 | if ((msg == WM_KEYUP) || (msg == WM_SYSKEYUP) || (msg == WM_KEYDOWN) || (msg == WM_SYSKEYDOWN)) 426 | { 427 | // the virtual key code is the first KBDLLHOOKSTRUCT member: 428 | Int32 vkCode = Marshal.ReadInt32(lParam); 429 | 430 | // base key matches? 431 | if (hotkeys.ContainsKey(vkCode)) 432 | { 433 | Keys modifiers = PressedModifiers; 434 | 435 | // modifiers match? 436 | if (hotkeys[vkCode].Contains(modifiers)) 437 | { 438 | Keys key = (Keys) vkCode; 439 | Hotkey hotkey = new Hotkey(key, modifiers); 440 | String name = hotkeysToNames[hotkey]; 441 | 442 | // override forward with the current hotkey option: 443 | forward = hotkeysForward[hotkey]; 444 | 445 | KeyboardHookEventArgs e = new KeyboardHookEventArgs(name, key, modifiers); 446 | 447 | // call the appropriate event handler using the current dispatcher: 448 | if (msg == WM_KEYUP || msg == WM_SYSKEYUP) 449 | { 450 | if (HotkeyUp != null) 451 | { 452 | dispatcher.BeginInvoke(HotkeyUp, new Object[] { instance, e }); 453 | } 454 | } 455 | else 456 | { 457 | if (HotkeyDown != null) 458 | { 459 | dispatcher.BeginInvoke(HotkeyDown, new Object[] { instance, e }); 460 | } 461 | } 462 | } 463 | } 464 | } 465 | } 466 | 467 | // forward or return a dummy value other than 0: 468 | if (forward) 469 | { 470 | return CallNextHookEx(hookID, nCode, wParam, lParam); 471 | } 472 | else 473 | { 474 | return new IntPtr(1); 475 | } 476 | } 477 | 478 | /// 479 | /// Private Windows API declarations 480 | /// 481 | 482 | private const Int32 VK_SHIFT = 0x10; 483 | private const Int32 VK_CONTROL = 0x11; 484 | private const Int32 VK_MENU = 0x12; 485 | 486 | private const Int32 WH_KEYBOARD_LL = 13; 487 | 488 | private const Int32 WM_SYSKEYDOWN = 0x0104; 489 | private const Int32 WM_SYSKEYUP = 0x0105; 490 | private const Int32 WM_KEYDOWN = 0x0100; 491 | private const Int32 WM_KEYUP = 0x0101; 492 | 493 | private delegate IntPtr HOOKPROC(Int32 nCode, IntPtr wParam, IntPtr lParam); 494 | 495 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 496 | private static extern IntPtr SetWindowsHookEx(Int32 idHook, HOOKPROC lpfn, IntPtr hMod, UInt32 dwThreadId); 497 | 498 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 499 | [return: MarshalAs(UnmanagedType.Bool)] 500 | private static extern Boolean UnhookWindowsHookEx(IntPtr hhk); 501 | 502 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 503 | private static extern IntPtr CallNextHookEx(IntPtr hhk, Int32 nCode, IntPtr wParam, IntPtr lParam); 504 | 505 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 506 | private static extern IntPtr GetModuleHandle(String lpModuleName); 507 | 508 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 509 | private static extern Int16 GetAsyncKeyState(Int32 vKey); 510 | } 511 | } 512 | 513 | -------------------------------------------------------------------------------- /Source/Yava/Yava.cs: -------------------------------------------------------------------------------- 1 | 2 | // Yava. 3 | // A simple, portable game/emulator launcher. 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Drawing; 10 | using System.IO; 11 | using System.Windows.Forms; 12 | 13 | using Yava.Controls; 14 | using Yava.FoldersFile; 15 | 16 | using LowKey; 17 | 18 | 19 | namespace Yava 20 | { 21 | [System.ComponentModel.DesignerCategory("")] 22 | internal class Yava : Form 23 | { 24 | // gui components: 25 | private readonly DoubleBufferedListView foldersListView; 26 | private readonly DoubleBufferedListView filesListView; 27 | 28 | // settings: 29 | private readonly String settingsFilepath; 30 | private readonly YavaSettings settings; 31 | 32 | // folders: 33 | private readonly String foldersFilepath; 34 | private readonly FoldersFileReader foldersFileReader; 35 | 36 | // remembering selected folders and files: 37 | private String lastSelectedFolderName; 38 | private Dictionary folderNameToLastSelectedFilePath; 39 | 40 | // executing files: 41 | private Process currentFileProcess; 42 | 43 | /// 44 | /// Yava implementation. 45 | /// 46 | /// 47 | /// Path to the settings file to use. 48 | /// 49 | /// 50 | /// Path to the folders file to use. 51 | /// 52 | public Yava(String settingsFilepath, String foldersFilepath) 53 | { 54 | // form: 55 | this.DoubleBuffered = true; 56 | this.Icon = Util.ResourceAsIcon("Yava.Resources.gnome-joystick.ico"); 57 | this.MinimumSize = new Size(640, 480); 58 | this.Text = "Yava Launcher"; 59 | 60 | // gui components: 61 | foldersListView = new DoubleBufferedListView(); 62 | foldersListView.Columns.Add("Folders"); 63 | foldersListView.Dock = DockStyle.Left; 64 | foldersListView.Font = new Font("Verdana", 9); 65 | foldersListView.HideSelection = false; 66 | foldersListView.MultiSelect = true; 67 | foldersListView.FullRowSelect = true; 68 | foldersListView.ShowItemToolTips = true; 69 | foldersListView.View = View.Details; 70 | foldersListView.Width = 200; 71 | 72 | filesListView = new DoubleBufferedListView(); 73 | filesListView.Columns.Add("Files"); 74 | filesListView.Dock = DockStyle.Fill; 75 | filesListView.Font = new Font("Verdana", 9); 76 | filesListView.HideSelection = false; 77 | filesListView.FullRowSelect = true; 78 | filesListView.MultiSelect = false; 79 | filesListView.ShowItemToolTips = true; 80 | filesListView.View = View.Details; 81 | filesListView.Width = 400; 82 | 83 | Splitter splitter = new Splitter(); 84 | splitter.Dock = DockStyle.Left; 85 | 86 | Controls.Add(filesListView); 87 | Controls.Add(splitter); 88 | Controls.Add(foldersListView); 89 | 90 | // settings: 91 | this.settingsFilepath = settingsFilepath; 92 | this.settings = SettingsLoad(); 93 | 94 | // folders: 95 | this.foldersFilepath = foldersFilepath; 96 | this.foldersFileReader = new FoldersFileReader(); 97 | 98 | // remembering selected folders and files: 99 | this.lastSelectedFolderName = null; 100 | this.folderNameToLastSelectedFilePath = new Dictionary(); 101 | 102 | // executing files: 103 | this.currentFileProcess = null; 104 | 105 | // apply settings before wiring events: 106 | this.Width = settings.LastYavaFormWidth; 107 | this.Height = settings.LastYavaFormHeight; 108 | this.foldersListView.Width = settings.LastFoldersListViewWidth; 109 | this.filesListView.Width = settings.LastFilesListViewWidth; 110 | this.lastSelectedFolderName = settings.LastSelectedFolderName; 111 | this.folderNameToLastSelectedFilePath = settings.FolderNameToLastSelectedFilePath; 112 | 113 | // wire events - form: 114 | this.FormClosing += OnFormClosing; 115 | this.ResizeEnd += OnResizeEnd; 116 | 117 | // wire events - listviews: 118 | foldersListView.ItemSelectionChanged += OnFoldersListViewItemSelectionChanged; 119 | filesListView.ItemSelectionChanged += OnFilesListViewItemSelectionChanged; 120 | 121 | // wire events - splitter: 122 | splitter.SplitterMoved += OnSplitterMoved; 123 | 124 | // wire events - keyboard: 125 | foldersListView.KeyDown += OnFoldersListViewKeyDown; 126 | filesListView.KeyDown += OnFilesListViewKeyDown; 127 | 128 | // wire events - keyboard global hotkeys: 129 | KeyboardHook.Hooker.Add("Kill Process", Keys.K, Keys.Control | Keys.Shift); 130 | KeyboardHook.Hooker.Add("Close Process", Keys.C, Keys.Control | Keys.Shift); 131 | KeyboardHook.Hooker.HotkeyDown += OnHotkeyDown; 132 | 133 | // wire events - mouse: 134 | filesListView.MouseDoubleClick += OnFilesListViewMouseDoubleClick; 135 | 136 | // initialize content and hotkeys 137 | // (both can raise exceptions, appropriate messageboxes will be displayed): 138 | LoadContent(); 139 | GlobalHotkeysHook(); 140 | } 141 | 142 | /// 143 | /// Settings file 144 | /// 145 | 146 | /// 147 | /// Load the settings from our settings filepath if it exists. 148 | /// Return default settings otherwise. 149 | /// 150 | private YavaSettings SettingsLoad() 151 | { 152 | try 153 | { 154 | if (File.Exists(settingsFilepath)) 155 | { 156 | return (YavaSettings) Util.Deserialize(settingsFilepath); 157 | } 158 | } 159 | catch (Exception exception) 160 | { 161 | String text = String.Format( 162 | "Unable to load settings: \n" + 163 | "{0} \n\n" + 164 | "This usually means that the file is corrupt, empty \n" + 165 | "or incompatible with the current Yava version. \n\n" + 166 | "Exception message: \n" + 167 | "{1} \n", 168 | settingsFilepath, 169 | exception.Message 170 | ); 171 | 172 | String caption = "Error loading settings file"; 173 | MessageBox.Show(text, caption); 174 | } 175 | 176 | // unable to load or doesn't exist, use defaults: 177 | return new YavaSettings(); 178 | } 179 | 180 | /// 181 | /// Save the current settings. 182 | /// 183 | private void SettingsSave() 184 | { 185 | try 186 | { 187 | Util.Serialize(settings, settingsFilepath); 188 | } 189 | catch (Exception exception) 190 | { 191 | String text = String.Format( 192 | "Unable to save settings: \n" + 193 | "{0} \n\n" + 194 | "Exception message: \n" + 195 | "{1} \n", 196 | settingsFilepath, 197 | exception.Message 198 | ); 199 | 200 | String caption = "Error saving settings file"; 201 | MessageBox.Show(text, caption); 202 | } 203 | } 204 | 205 | /// 206 | /// Global hotkeys hooking 207 | /// 208 | 209 | /// 210 | /// Start the global hotkeys hook. 211 | /// 212 | private void GlobalHotkeysHook() 213 | { 214 | try 215 | { 216 | KeyboardHook.Hooker.Hook(); 217 | } 218 | catch (KeyboardHookException exception) 219 | { 220 | String text = exception.Message; 221 | String caption = "Error hooking global hotkeys"; 222 | MessageBox.Show(text, caption, MessageBoxButtons.OK); 223 | } 224 | } 225 | 226 | /// 227 | /// Stop the global hotkeys hook. 228 | /// 229 | /// Ignore exceptions instead of showing a message. 230 | private void GlobalHotkeysUnhook(Boolean quiet = false) 231 | { 232 | try 233 | { 234 | KeyboardHook.Hooker.Unhook(); 235 | } 236 | catch (KeyboardHookException exception) 237 | { 238 | if (!quiet) 239 | { 240 | String text = exception.Message; 241 | String caption = "Error unhooking global hotkeys"; 242 | MessageBox.Show(text, caption, MessageBoxButtons.OK); 243 | } 244 | } 245 | } 246 | 247 | /// 248 | /// Updating listviews 249 | /// 250 | 251 | /// 252 | /// Resize the folder ListView first column according to the content. 253 | /// 254 | private void ListViewFoldersResize() 255 | { 256 | foldersListView.BeginUpdate(); 257 | foldersListView.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.None); 258 | foldersListView.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); 259 | foldersListView.Columns[0].Width = Math.Max(foldersListView.Columns[0].Width, 150); 260 | foldersListView.EndUpdate(); 261 | } 262 | 263 | /// 264 | /// Resize the files ListView first column according to the content. 265 | /// 266 | private void ListViewFilesResize() 267 | { 268 | filesListView.BeginUpdate(); 269 | filesListView.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.None); 270 | filesListView.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); 271 | filesListView.Columns[0].Width = Math.Max(filesListView.Columns[0].Width, 150); 272 | filesListView.EndUpdate(); 273 | } 274 | 275 | /// 276 | /// Remembering selected items in both listviews 277 | /// 278 | 279 | /// 280 | /// Remember the currently selected folder name. 281 | /// Used in combination with ListViewFoldersSelectLastSelectedFolder() 282 | /// to set the selected folder after reloading the listview. 283 | /// 284 | private void ListViewFoldersRememberSelectedFolder() 285 | { 286 | // we remember the last selected folder 287 | // but not for multiple selections: 288 | if (foldersListView.SelectedItems.Count == 1) 289 | { 290 | Folder folder = foldersListView.SelectedItems[0].Tag as Folder; 291 | String foldername = folder.Name; 292 | 293 | lastSelectedFolderName = foldername; 294 | } 295 | // no selection or multiple selections: 296 | else 297 | { 298 | lastSelectedFolderName = null; 299 | } 300 | } 301 | 302 | /// 303 | /// Try to select the folder that was last selected on the folders listview. 304 | /// 305 | private void ListViewFoldersSelectLastSelectedFolder() 306 | { 307 | if (lastSelectedFolderName != null) 308 | { 309 | // find the item: 310 | foreach (ListViewItem item in foldersListView.Items) 311 | { 312 | Folder test = item.Tag as Folder; 313 | if (test.Name.Equals(lastSelectedFolderName)) 314 | { 315 | // do not trigger the selection event: 316 | foldersListView.ItemSelectionChanged -= OnFoldersListViewItemSelectionChanged; 317 | { 318 | foldersListView.EnsureVisible(item.Index); 319 | item.Focused = true; 320 | item.Selected = true; 321 | } 322 | foldersListView.ItemSelectionChanged += OnFoldersListViewItemSelectionChanged; 323 | break; 324 | } 325 | } 326 | } 327 | } 328 | 329 | /// 330 | /// Remember the current folder selected file path. 331 | /// Used in combination with ListViewFilesSelectLastSelectedFile() 332 | /// to set the selected file after reloading the listview. 333 | /// 334 | private void ListViewFilesRememberSelectedFile() 335 | { 336 | // we remember the last selected file for each single folder 337 | // but not for multiple selections: 338 | if (foldersListView.SelectedItems.Count == 1) 339 | { 340 | Folder folder = foldersListView.SelectedItems[0].Tag as Folder; 341 | String foldername = folder.Name; 342 | 343 | if (filesListView.SelectedItems.Count == 1) 344 | { 345 | FolderFile file = filesListView.SelectedItems[0].Tag as FolderFile; 346 | String filepath = file.Path; 347 | 348 | folderNameToLastSelectedFilePath[foldername] = filepath; 349 | } 350 | else 351 | { 352 | // note: Dictionary.Remove(key) does nothing when the key is not found: 353 | folderNameToLastSelectedFilePath.Remove(foldername); 354 | } 355 | } 356 | } 357 | 358 | /// 359 | /// Try to select the file that was last selected on the files listview. 360 | /// 361 | private void ListViewFilesSelectLastSelectedFile() 362 | { 363 | // we remember the last selected file for each single folder 364 | // but not for multiple selections: 365 | if (foldersListView.SelectedItems.Count == 1) 366 | { 367 | Folder folder = foldersListView.SelectedItems[0].Tag as Folder; 368 | String foldername = folder.Name; 369 | 370 | if (folderNameToLastSelectedFilePath.ContainsKey(foldername)) 371 | { 372 | String filepath = folderNameToLastSelectedFilePath[foldername]; 373 | 374 | // find the item: 375 | foreach (ListViewItem item in filesListView.Items) 376 | { 377 | FolderFile test = item.Tag as FolderFile; 378 | if (test.Path.Equals(filepath)) 379 | { 380 | // do not trigger the selection event: 381 | filesListView.ItemSelectionChanged -= OnFilesListViewItemSelectionChanged; 382 | { 383 | filesListView.EnsureVisible(item.Index); 384 | item.Focused = true; 385 | item.Selected = true; 386 | } 387 | filesListView.ItemSelectionChanged += OnFilesListViewItemSelectionChanged; 388 | break; 389 | } 390 | } 391 | } 392 | } 393 | } 394 | 395 | /// 396 | /// Executing files 397 | /// 398 | 399 | /// 400 | /// Open the folders file with the default program 401 | /// associated to the extension. 402 | /// 403 | private void FoldersFileOpen() 404 | { 405 | try 406 | { 407 | Process.Start(foldersFilepath); 408 | } 409 | catch (Exception exception) 410 | { 411 | String text = exception.Message; 412 | String caption = "Error openning folders file"; 413 | MessageBox.Show(text, caption); 414 | } 415 | } 416 | 417 | /// 418 | /// Execute a file from the files listview. 419 | /// 420 | /// File to execute. 421 | private void ListViewFilesExecuteFile(FolderFile file) 422 | { 423 | // bail out if there's a file already running: 424 | if ((currentFileProcess != null) && (!currentFileProcess.HasExited)) 425 | { 426 | return; 427 | } 428 | 429 | Folder folder = file.Folder; 430 | 431 | // step 1: fill the startup information: 432 | ProcessStartInfo psi = null; 433 | 434 | // initial values as specified in the folder options: 435 | String executable = folder.Executable; 436 | String parameters = folder.Parameters; 437 | String workingdirectory = folder.WorkingDirectory; 438 | 439 | try 440 | { 441 | // variables to expand as absolute paths: 442 | String FILEPATH = Path.GetFullPath(file.Path); 443 | String FOLDERPATH = Path.GetFullPath(folder.Path); 444 | 445 | // executable: 446 | executable = executable.Replace("%FILEPATH%", FILEPATH); 447 | executable = executable.Replace("%FOLDERPATH%", FOLDERPATH); 448 | executable = Path.GetFullPath(executable); 449 | 450 | // parameters: 451 | // when none specified, use the file path: 452 | parameters = parameters ?? '"' + "%FILEPATH%" + '"'; 453 | parameters = parameters.Replace("%FILEPATH%", FILEPATH); 454 | parameters = parameters.Replace("%FOLDERPATH%", FOLDERPATH); 455 | 456 | // working directory: 457 | // when none specified, use the executable folder: 458 | workingdirectory = workingdirectory ?? Path.GetDirectoryName(executable); 459 | workingdirectory = workingdirectory.Replace("%FILEPATH%", FILEPATH); 460 | workingdirectory = workingdirectory.Replace("%FOLDERPATH%", FOLDERPATH); 461 | workingdirectory = Path.GetFullPath(workingdirectory); 462 | 463 | // everything ok: 464 | psi = new ProcessStartInfo(); 465 | psi.FileName = executable; 466 | psi.Arguments = parameters; 467 | psi.WorkingDirectory = workingdirectory; 468 | } 469 | 470 | // path error (from Path.GetFullPath) 471 | // show the exception message: 472 | catch (Exception exception) 473 | { 474 | String text = String.Format( 475 | "Executable: \n" + 476 | "{0} \n\n" + 477 | "Parameters: \n" + 478 | "{1} \n\n" + 479 | "Working Directory: \n" + 480 | "{2} \n\n" + 481 | "Exception message: \n" + 482 | "{3}", 483 | executable, 484 | parameters ?? " (use file path)", 485 | workingdirectory ?? " (use executable folder)", 486 | exception.Message 487 | ); 488 | 489 | String caption = "Error setting up process"; 490 | MessageBox.Show(text, caption, MessageBoxButtons.OK); 491 | } 492 | 493 | // unable to setup, bail out: 494 | if (psi == null) 495 | { 496 | return; 497 | } 498 | 499 | // step 2: run the process: 500 | try 501 | { 502 | Process process = Process.Start(psi); 503 | 504 | // this check is needed because Process.Start(...) returns null 505 | // when reusing a process: 506 | if (process != null) 507 | { 508 | currentFileProcess = process; 509 | currentFileProcess.PriorityBoostEnabled = true; 510 | currentFileProcess.PriorityClass = ProcessPriorityClass.AboveNormal; 511 | } 512 | } 513 | 514 | // process error (from Process.Start) 515 | // show the exception message: 516 | catch (Exception exception) 517 | { 518 | String text = String.Format( 519 | "Executable: \n" + 520 | "{0} \n\n" + 521 | "Parameters: \n" + 522 | "{1} \n\n" + 523 | "Working Directory: \n" + 524 | "{2} \n\n" + 525 | "Exception message: \n" + 526 | "{3}", 527 | psi.FileName, 528 | psi.Arguments, 529 | psi.WorkingDirectory, 530 | exception.Message 531 | ); 532 | 533 | String caption = "Error executing file"; 534 | MessageBox.Show(text, caption); 535 | } 536 | } 537 | 538 | /// 539 | /// Run the currently selected file on the files listview. 540 | /// 541 | private void ListViewFilesExecuteSelectedFile() 542 | { 543 | if (filesListView.SelectedItems.Count == 1) 544 | { 545 | FolderFile file = filesListView.SelectedItems[0].Tag as FolderFile; 546 | ListViewFilesExecuteFile(file); 547 | } 548 | } 549 | 550 | /// 551 | /// Try closing the current file process by sending a close message 552 | /// to its main window. Do nothing if there is no process running 553 | /// or it can't be closed. 554 | /// 555 | private void TryClosingCurrentFileProcess() 556 | { 557 | if ((currentFileProcess != null) && (!currentFileProcess.HasExited)) 558 | { 559 | try 560 | { 561 | currentFileProcess.CloseMainWindow(); 562 | } 563 | catch (Exception) 564 | { 565 | 566 | } 567 | } 568 | } 569 | 570 | /// 571 | /// Try to kill the current file process immediately. 572 | /// Do nothing if there is no process running 573 | /// or it can't be killed. 574 | /// 575 | private void TryKillingCurrentFileProcess() 576 | { 577 | if ((currentFileProcess != null) && (!currentFileProcess.HasExited)) 578 | { 579 | try 580 | { 581 | currentFileProcess.Kill(); 582 | } 583 | catch (Exception) 584 | { 585 | 586 | } 587 | } 588 | } 589 | 590 | /// 591 | /// Loading folders and files into the listviews 592 | /// 593 | 594 | /// 595 | /// Open the folders file and parse the content 596 | /// adding each folder to the folders listview. 597 | /// On errors, show a MessageBox with details. 598 | /// 599 | private void LoadFolders() 600 | { 601 | List folders = new List(); 602 | Boolean success = false; 603 | 604 | try 605 | { 606 | foldersFileReader.Read(foldersFilepath, folders); 607 | success = true; 608 | } 609 | 610 | // syntax or parsing error 611 | // show details and ask the user to edit: 612 | catch (FoldersFileReadError exception) 613 | { 614 | String text = String.Format( 615 | "{0} \n" + 616 | "Error at line {1}: {2} \n\n" + 617 | "{3} \n\n" + 618 | "Edit the file and press F5 to refresh. \n" + 619 | "Do you want to open the folders file now?", 620 | exception.FilePath, 621 | exception.LineNumber, 622 | exception.Message, 623 | exception.Line 624 | ); 625 | 626 | String caption = "Error reading folders file"; 627 | if (Util.MessageBoxYesNo(text, caption)) 628 | { 629 | FoldersFileOpen(); 630 | } 631 | } 632 | 633 | // io error 634 | // show the exception message: 635 | catch (Exception exception) 636 | { 637 | String caption = "Error opening folders file"; 638 | String text = exception.Message; 639 | 640 | MessageBox.Show(text, caption, MessageBoxButtons.OK); 641 | } 642 | 643 | // clear previous content: 644 | foldersListView.BeginUpdate(); 645 | foldersListView.Items.Clear(); 646 | 647 | // add the folders when needed: 648 | if (success) 649 | { 650 | ListViewItem[] items = new ListViewItem[folders.Count]; 651 | 652 | Int32 index = 0; 653 | foreach (Folder folder in folders) 654 | { 655 | ListViewItem item = new ListViewItem(); 656 | 657 | item.Tag = folder; 658 | item.Text = folder.Name; 659 | item.ToolTipText = folder.Path; 660 | 661 | items[index] = item; 662 | index++; 663 | } 664 | 665 | foldersListView.Items.AddRange(items); 666 | } 667 | 668 | // done: 669 | foldersListView.EndUpdate(); 670 | ListViewFoldersResize(); 671 | } 672 | 673 | /// 674 | /// Populate the files listview using the selected folders files. 675 | /// On errors, show a MessageBox with details. 676 | /// 677 | private void LoadFiles() 678 | { 679 | List items = new List(); 680 | Boolean success = false; 681 | 682 | try 683 | { 684 | foreach (ListViewItem selectedItem in foldersListView.SelectedItems) 685 | { 686 | Folder folder = selectedItem.Tag as Folder; 687 | foreach (FolderFile file in folder.EnumerateFiles()) 688 | { 689 | ListViewItem item = new ListViewItem(); 690 | 691 | item.Tag = file; 692 | item.Text = Path.GetFileName(file.Path); 693 | item.ToolTipText = file.Path; 694 | 695 | items.Add(item); 696 | } 697 | } 698 | 699 | success = true; 700 | } 701 | 702 | // io error 703 | // show the exception message: 704 | catch (Exception exception) 705 | { 706 | String caption = "Error loading folder files"; 707 | String text = exception.Message; 708 | 709 | MessageBox.Show(text, caption, MessageBoxButtons.OK); 710 | } 711 | 712 | // clear previous content: 713 | filesListView.BeginUpdate(); 714 | filesListView.Items.Clear(); 715 | 716 | // add the files when needed: 717 | if (success) 718 | { 719 | filesListView.Items.AddRange(items.ToArray()); 720 | } 721 | 722 | // done: 723 | filesListView.EndUpdate(); 724 | ListViewFilesResize(); 725 | } 726 | 727 | /// 728 | /// Loads both folders and files, selecting the last 729 | /// selected ones when possible. 730 | /// 731 | private void LoadContent() 732 | { 733 | LoadFolders(); 734 | ListViewFoldersSelectLastSelectedFolder(); 735 | 736 | LoadFiles(); 737 | ListViewFilesSelectLastSelectedFile(); 738 | } 739 | 740 | /// 741 | /// Events: form 742 | /// 743 | 744 | /// 745 | /// When the form is closed, save settings, unhook hotkeys. 746 | /// 747 | private void OnFormClosing(Object sender, FormClosingEventArgs e) 748 | { 749 | settings.LastYavaFormWidth = this.Width; 750 | settings.LastYavaFormHeight = this.Height; 751 | settings.LastFoldersListViewWidth = foldersListView.Width; 752 | settings.LastFilesListViewWidth = filesListView.Width; 753 | settings.LastSelectedFolderName = this.lastSelectedFolderName; 754 | settings.FolderNameToLastSelectedFilePath = this.folderNameToLastSelectedFilePath; 755 | 756 | SettingsSave(); 757 | GlobalHotkeysUnhook(true); 758 | } 759 | 760 | /// 761 | /// When the form is resized, auto-resize the listviews. 762 | /// 763 | private void OnResizeEnd(Object sender, EventArgs e) 764 | { 765 | ListViewFoldersResize(); 766 | ListViewFilesResize(); 767 | } 768 | 769 | /// 770 | /// Events: listviews 771 | /// 772 | 773 | /// 774 | /// When the folder selection changes, load the appropriate files list 775 | /// and try to select the last known selected file. 776 | /// 777 | private void OnFoldersListViewItemSelectionChanged(Object sender, ListViewItemSelectionChangedEventArgs e) 778 | { 779 | LoadFiles(); 780 | ListViewFilesSelectLastSelectedFile(); 781 | ListViewFoldersRememberSelectedFolder(); 782 | } 783 | 784 | /// 785 | /// When the file selection changes 786 | /// remember the new one as the last selected file. 787 | /// 788 | private void OnFilesListViewItemSelectionChanged(Object sender, ListViewItemSelectionChangedEventArgs e) 789 | { 790 | ListViewFilesRememberSelectedFile(); 791 | } 792 | 793 | /// 794 | /// Events: splitter 795 | /// 796 | 797 | /// 798 | /// When the spliiter is moved, auto-resize the listviews. 799 | /// 800 | private void OnSplitterMoved(Object sender, EventArgs e) 801 | { 802 | ListViewFoldersResize(); 803 | ListViewFilesResize(); 804 | } 805 | 806 | /// 807 | /// Events: keyboard 808 | /// 809 | 810 | /// 811 | /// Folders listview keyboard shortcuts. 812 | /// 813 | private void OnFoldersListViewKeyDown(Object sender, KeyEventArgs e) 814 | { 815 | switch (e.KeyData) 816 | { 817 | case Keys.F5: 818 | LoadContent(); 819 | foldersListView.Focus(); 820 | e.Handled = true; 821 | break; 822 | } 823 | } 824 | 825 | /// 826 | /// Files listview keyboard shortcuts. 827 | /// 828 | private void OnFilesListViewKeyDown(Object sender, KeyEventArgs e) 829 | { 830 | switch (e.KeyData) 831 | { 832 | case Keys.F5: 833 | LoadContent(); 834 | filesListView.Focus(); 835 | e.Handled = true; 836 | break; 837 | case Keys.Return: 838 | ListViewFilesExecuteSelectedFile(); 839 | e.Handled = true; 840 | break; 841 | } 842 | } 843 | 844 | /// 845 | /// Events: keyboard global hotkeys 846 | /// 847 | 848 | /// 849 | /// Global keyboard hotkeys. 850 | /// 851 | private void OnHotkeyDown(Object sender, KeyboardHookEventArgs e) 852 | { 853 | switch (e.Name) 854 | { 855 | case "Kill Process": 856 | TryKillingCurrentFileProcess(); 857 | break; 858 | 859 | case "Close Process": 860 | TryClosingCurrentFileProcess(); 861 | break; 862 | } 863 | } 864 | 865 | /// 866 | /// Events: mouse 867 | /// 868 | 869 | /// 870 | /// On double click, run the currently selected file. 871 | /// 872 | private void OnFilesListViewMouseDoubleClick(Object sender, MouseEventArgs e) 873 | { 874 | if (e.Button == MouseButtons.Left) 875 | { 876 | ListViewFilesExecuteSelectedFile(); 877 | } 878 | } 879 | } 880 | } 881 | 882 | -------------------------------------------------------------------------------- /Tools/gnome-joystick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 17 | 24 | 26 | 27 | 34 | 38 | 42 | 45 | 48 | 51 | 52 | 56 | 57 | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 91 | 94 | 97 | 100 | 103 | 106 | 109 | 112 | 115 | 118 | 121 | 122 | 126 | 127 | 134 | 138 | 142 | 146 | 150 | 153 | 156 | 159 | 162 | 165 | 168 | 171 | 172 | 176 | 177 | 185 | 189 | 193 | 197 | 200 | 203 | 206 | 209 | 212 | 213 | 217 | 218 | 225 | 229 | 233 | 237 | 240 | 243 | 246 | 249 | 252 | 253 | 257 | 261 | 262 | 269 | 273 | 277 | 281 | 284 | 287 | 290 | 293 | 296 | 297 | 301 | 302 | 309 | 313 | 317 | 320 | 323 | 326 | 327 | 331 | 332 | 340 | 344 | 348 | 352 | 356 | 360 | 363 | 366 | 369 | 372 | 375 | 378 | 381 | 384 | 387 | 388 | 394 | 396 | 397 | 404 | 408 | 412 | 415 | 418 | 421 | 422 | 426 | 427 | 434 | 438 | 442 | 446 | 449 | 452 | 455 | 458 | 461 | 462 | 466 | 473 | 474 | 481 | 485 | 489 | 493 | 496 | 499 | 502 | 505 | 508 | 509 | 516 | 517 | 518 | 519 | --------------------------------------------------------------------------------