Compare commits

...

57 commits

Author SHA1 Message Date
168a4dd30b
[fix]: updated version number and copyright
Some checks failed
Create release on tag push / Create mod release (push) Failing after 28s
2024-05-03 14:33:14 +02:00
bb0f4608bf
[docs]: updated for 5.0 and dropped legacy support 2024-05-03 14:19:57 +02:00
35120ad6b8
[fix] fix for new file naming convention
Some checks failed
Create release on tag push / Create mod release (push) Failing after 52s
Also drops legacy support.

fixes #42
2024-05-03 14:05:46 +02:00
a865d1b356
chore: get rid of auto pull request workflow 2024-02-18 23:22:28 +01:00
211812427b
chore: forgejo workflows 2024-02-18 23:19:14 +01:00
alterNERDtive
75e8388ba1
Merge pull request #35 from alterNERDtive/feature/auto-pull-request
Some checks failed
Pull Request on Branch Push / Open pull request (push) Failing after 17s
2022-07-13 22:20:08 +02:00
8b189fb658
workflows: open pr on branch push
Some checks failed
Pull Request on Branch Push / Open pull request (push) Failing after 27s
2022-07-13 22:18:54 +02:00
4cbff59b33 fixed gh-pages workflow 2022-07-11 12:10:35 +02:00
5dfd9f18b4 Merge branch 'release' into develop 2022-07-04 01:34:04 +02:00
2ff49dd98a added ko-fi / sponsors 2022-07-04 01:33:53 +02:00
bbd7f443d3 dependabot 2022-06-01 09:03:21 +02:00
96bea9a127 auto-deploy gh-pages on release 2022-06-01 09:03:15 +02:00
495320c420 4.2.2! 2022-05-31 19:03:54 +02:00
bdf3c58241 docs: clarified that the default layout will work for most keys out of the box
fixes #32
2022-05-30 23:55:41 +02:00
4fb8cc0bcc one more stylecop concession 2022-05-30 23:52:44 +02:00
e845a32cd8 first naïve version of github actions for automated releases 2022-05-30 20:46:08 +02:00
57fdc0a2b7 fixed setting version variable 2022-05-29 23:41:13 +02:00
31894129c7 stylecop has joined the server 2022-05-29 23:32:31 +02:00
514f70ddb2 docs: now mentions the reports profile in the “Using Binds in Commands” section 2022-05-29 10:38:35 +02:00
27ba1a1c96 updated CHANGELOG
because I’m a dum dum and forgot
2022-01-12 16:11:03 +01:00
175d04c129 added info about generally not having to invoke the plugin manually to invalid context error message 2022-01-12 16:08:05 +01:00
80c23b3fb5 added specific error message for invoking without context
fixes #30
2022-01-12 16:07:34 +01:00
ef25bcd7bf Added support for Odyssey’s new StartPreset.4.start file
fixes #29
2021-12-24 17:50:34 +01:00
c83848c0e3 README: typo fixed 2021-10-11 22:08:40 +02:00
4412c1e59e README: typo fixed 2021-09-08 08:41:22 +02:00
8f8eb81fbd Merge branch 'develop' into release 2021-08-13 22:00:22 +02:00
61cd2d14fd 4.2! 2021-08-13 21:43:24 +02:00
b177fa4e1c nullable preset in DetectBindsFile() doesn’t make sense 2021-07-18 19:57:25 +02:00
856b95974c now correctly loads presets with regex special characters
fixes #28
2021-07-18 19:56:36 +02:00
b1455beddc no longer shipping old ReadMe.txt with plugin releases 2021-07-04 21:12:22 +02:00
11e4ac1575 incremented version number, because I’m a buffoon 2021-07-04 21:08:05 +02:00
83460ac9b8 docs: added link to CHANGELOG on Github 2021-07-04 20:59:36 +02:00
65534330d9 added error message if multiple presets are used
fixes #26
2021-07-04 20:59:18 +02:00
69f3a43590 docs: clarification on starting Elite first time after VA 2021-06-14 12:40:57 +02:00
f46f40abb1 docs: added actual usage instructions
fixes #24
2021-06-14 11:57:05 +02:00
9d2c5355d8 sorted out the utf-8 crap … 2021-06-14 11:46:57 +02:00
3a8d8ff620 docs: now with docs™!
also fixes #21
2021-06-14 11:32:14 +02:00
58bfdd65f1 .editorconfig: default to utf-8 2021-06-14 11:22:45 +02:00
09767d493d added diagnostics context and profile
Should help with troubleshooting.
2021-06-14 09:38:14 +02:00
3fc7e73fe8 Merge branch 'develop' into release 2021-05-23 20:22:39 +02:00
8cbf4874d7 updated .editorconfig 2021-05-23 20:22:05 +02:00
b0edff39de 4.1! 2021-05-22 18:42:19 +02:00
0652fc84ab now correctly prioritizing binds files
4.0 > 3.0 > plain
2021-05-22 18:40:50 +02:00
b99c857ccc updated preset detection for Odyssey
For now will only work with the first line; which means that Odyssey users will have to use the same preset for everything.

fixes #15
2021-05-22 18:16:28 +02:00
df5366ec4d README: updated instructions for changing Horizons binds 2021-05-22 18:09:14 +02:00
ed8ba6244c README: updated with prerequisites
You need to load the game at least once, you need to have changed at least a single bind.

fixes #19
2021-05-22 18:02:00 +02:00
d431e899bd added ~bindsFile optional parameter to loadbinds context
fixes #18
2021-05-22 17:55:34 +02:00
dadb6c8f2e Merge branch 'develop' into release 2021-05-19 12:33:24 +02:00
e63ff1bbab 4.0! 2021-05-19 12:32:01 +02:00
4cf8788049 removed deprecated empty / binds file contexts 2021-05-19 12:25:20 +02:00
bd3c5ec96f pretty printerino 2021-05-19 12:20:59 +02:00
c8b76472bb Odyssey compatibility
fixes #14
2021-05-19 12:13:14 +02:00
0736d29682 Odyssey support, quick & dirty
Just takes the last changed file of `<preset>.binds`, `<preset>.3.0.binds` or `<preset>.4.0.binds`.
2021-05-19 10:10:33 +02:00
2bb3bd947f README: added pointer to the release page.
fixes #11
2021-02-22 21:35:50 +01:00
b4059992ca README: added pointer to the release page.
fixes #11
2021-02-22 21:35:10 +01:00
47a9fd20e6 fixed version number 2021-01-30 14:36:08 +01:00
399db2b53a CHANGELOG: typo 2021-01-29 15:25:03 +01:00
25 changed files with 1494 additions and 890 deletions

View file

@ -1,7 +1,14 @@
# All files
[*]
[*]
guidelines = 80
end_of_line = lf
insert_final_newline = true
charset = utf-8-bom
# C# or VB files
[*.{cs,vb}]
guidelines = 80, 120
[*.cs]
guidelines = 80, 120
# IDE0021: Use block body for constructors
csharp_style_expression_bodied_constructors = when_on_single_line
# IDE0024: Use block body for operators
csharp_style_expression_bodied_operators = when_on_single_line

View file

@ -0,0 +1,33 @@
name: Create release on tag push
on:
push:
tags:
- 'release/*'
jobs:
build:
name: Create mod release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Get release body
run: |
echo "release_body=$(cat CHANGELOG.md)" >> "$GITHUB_ENV"
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Draft release
uses: https://gitea.com/actions/release-action@main
with:
body: ${{ env.release_body }}
draft: true
api_key: '${{ secrets.RELEASE_TOKEN }}'

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
github: alterNERDtive
ko_fi: alterNERDtive

12
.github/dependabot.yaml vendored Normal file
View file

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/"
target-branch: "develop"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "develop"
schedule:
interval: "daily"

30
.github/workflows/create-release.yaml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Create release on tag push
on:
push:
tags:
- 'release/*'
jobs:
build:
name: Build bindED
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
- name: Build
run: msbuild -t:build -p:configuration=release
- name: Draft release
uses: ncipollo/release-action@v1
with:
artifacts: "bindEDplugin.zip"
bodyFile: "CHANGELOG.md"
draft: true

21
.github/workflows/gh-pages.yaml vendored Normal file
View file

@ -0,0 +1,21 @@
name: Deploy github pages on tag push
on:
push:
tags:
- 'release/*'
jobs:
build:
name: Deploy documentation
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@nomaterial
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REQUIREMENTS: requirements.txt

3
.gitignore vendored
View file

@ -1,5 +1,6 @@
.vs/**
obj/**
bin/**
bin/**
site/**
bindEDplugin.csproj.user
*.zip

View file

@ -1,58 +1,174 @@
## 3.1 (2021-01-29)
## Changed
* Invoking the `loadbinds` context will now force reset everything and reload
from scratch. (#5)
## Added
* The current layouts key map file is now monitored for changes. Should make
adding support for new layouts slightly less annoying. (#4)
-----
# 3.0 (2020-11-12)
I did a complete refactoring of everything to prepare for some juicy new
features! Sadly that also meant breaking backwards compatibility. On the plus
side, the things that no longer work like they did in Garys initial release
should basically never be used anyway.
## Removed
* You can no longer specify binding files to use by linking them into the plugin
directory.
* You can no longer specify binding files by using them as the plugin context.
## Changed
* Invoking the plugin with no context or with a binds file as context is now
deprecated and will be removed in a future version. Use the `loadbinds`
context instead.
## Added
* `en-gb` key map. Thank you A.Cyprus for the work on that!
* Bindings are now automagically read when VoiceAttack loads and when
`bindED.layout#` is changed.
* After the initial reading of bindings the plugin will monitor the bindings
directory for changes to a) the `StartPreset.start` file (preset has changed)
and b) the binds file(s) corresponding to the current preset. Changes are
automatically applied. (#3)
* The `listbinds` context will set the text variable `~bindED.bindsList` to a
list of bindings present in the current bindings file. (#1)
* The `missingbinds` context will create a report of missing binds (anything
that doesnt have keyboard binds) and save it to `~bindED.missingBinds`. (#2)
* The included `bindED-reports` profile runs a missing binds report and a binds
list report when you load it and saves them to your Desktop.
-----
# 2.0 (2020-09-23)
## Added
* Support for non-US keyboard layouts. `de-neo2` is included (because thats
what Im using), others can be added ([see the wiki for
instructions](https://github.com/alterNERDtive/bindED/wiki/Keyboard-Layouts)).
# 5.0.0 (2024-05-03)
## Removed
* Will no longer copy Odyssey (live) binds to Horizons (legacy) binds. I doubt
anyone still plays the latter; if you do, dont upgrade the pulgin.
## Fixed
* Will now find binds files again for the latest Elite update which changed
the format to `<name>.4.1.binds`.
# 4.2.2 (2022-05-31)
## Fixed
* Added specific error message for invoking the plugin without context (#30).
* Clarified that the default `en-US` layout will wrok for most keys on most
layouts. (#32)
-----
# 4.2.1 (2021-12-24)
## Fixed
* Added support for Odysseys new `StartPreset.4.start` file; the preset
selected in the Odyssey client will take precedence, so make sure you use the
same preset for Horizons (#29).
-----
# 4.2 (2021-08-13)
## Added
* Pretty printed documentation! You can find it at
https://alterNERDtive.github.io/bindED.
* Troubleshooting guide! You can find it at
https://alterNERDtive.github.io/bindED/troubleshooting.
* `diagnostics` plugin context: writes current plugin state to the log for
troubleshooting.
* `bindED-diagnostics` profile: runs the `diagnostics` plugin context on load.
Should be the first troubleshooting step.
* Error message if multiple control presets are in use (Odyssey only).
## Fixed
* Now correctly loads presets that contain regex special characters (#28).
-----
# 4.1 (2021-05-22)
## Added
* optional `~bindsFile` parameter for the `loadbinds` plugin context: use that
to specify a binds file instead of auto-detecting it from the currently active
preset. (#18)
## Changed
* Updated the README to reflect that you need to load the game once, and that
you need to have changed at least a single bind. (#19)
* Updated the README to reflect that you have to use a single preset for all
sections if youre playing Odyssey.
## Fixed
* Now only reading the first line of `startPreset.start` to work correctly with
Odyssey. (#15)
* Now correctly prioritizing `.4.0.binds` > `.3.0.binds` > `.binds`. (#20)
-----
# 4.0 (2021-05-19)
**Note**: If you do not own Odyssey, everything will work just as before!
I, too, do not own Odyssey. So while I have tried testing various things with
mock Odyssey binds files, please keep an eye out for bugs and [file an
issue](https://github.com/alterNERDtive/bindED/issues/new/) if you
encounter any. And check back for a potential 4.0.1 soon. TYVM!
**IMPORTANT**: Please backup your binds files before installing this release,
just in case. You can find them in
`%localappdata%\Frontier Developments\Elite Dangerous\Options\Bindings`.
Sadly for the time being Odyssey and Horizons will basically be separate games.
That also means they have separate binds files.
BindED will by default always use the last edited file, be that the base preset,
Horizons or Odyssey.
To keep hassle to a minimum, the recommended way to change binds is to do it
from Odyssey. When a change to the Odyssey file is detected, the plugin will
by default overwrite Horizons binds with it. To prevent that and keep entirely
separate binds, you can set `bindED.disableHorizonsSync#` (yes, including the
pound sign) to `true` in your VoiceAttack profile.
## Added
* Odyssey binds file support (`*.4.0.binds`). (#14)
* `bindED.disableHorizonsSync#` configuration option: Set this (to `true`) in
your VoiceAttack profile to disable automatically syncing Odyssey binds
changes to Horizons binds.
## Removed
* empty plugin context: Invoking the plugin without context no longer gives a
deprecation warning and will instead fail.
* binds file as plugin context: Invoking the plugin with a binds file as context
no longer gives a deprecation warning and will instead fail.
-----
# 3.1 (2021-01-29)
## Changed
* Invoking the `loadbinds` context will now force reset everything and reload
from scratch. (#5)
## Added
* The current layouts key map file is now monitored for changes. Should make
adding support for new layouts slightly less annoying. (#4)
-----
# 3.0 (2020-11-12)
I did a complete refactoring of everything to prepare for some juicy new
features! Sadly that also meant breaking backwards compatibility. On the plus
side, the things that no longer work like they did in Garys initial release
should basically never be used anyway.
## Removed
* You can no longer specify binding files to use by linking them into the plugin
directory.
* You can no longer specify binding files by using them as the plugin context.
## Changed
* Invoking the plugin with no context or with a binds file as context is now
deprecated and will be removed in a future version. Use the `loadbinds`
context instead.
## Added
* `en-gb` key map. Thank you A.Cyprus for the work on that!
* Bindings are now automagically read when VoiceAttack loads and when
`bindED.layout#` is changed.
* After the initial reading of bindings the plugin will monitor the bindings
directory for changes to a) the `StartPreset.start` file (preset has changed)
and b) the binds file(s) corresponding to the current preset. Changes are
automatically applied. (#3)
* The `listbinds` context will set the text variable `~bindED.bindsList` to a
list of bindings present in the current bindings file. (#1)
* The `missingbinds` context will create a report of missing binds (anything
that doesnt have keyboard binds) and save it to `~bindED.missingBinds`. (#2)
* The included `bindED-reports` profile runs a missing binds report and a binds
list report when you load it and saves them to your Desktop.
-----
# 2.0 (2020-09-23)
## Added
* Support for non-US keyboard layouts. `de-neo2` is included (because thats
what Im using), others can be added ([see the wiki for
instructions](https://github.com/alterNERDtive/bindED/wiki/Keyboard-Layouts)).

25
Directory.build.props Normal file
View file

@ -0,0 +1,25 @@
<Project>
<PropertyGroup>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- StyleCop Analyzers configuration -->
<PropertyGroup>
<CodeAnalysisRuleSet>$(SolutionDir)StyleCop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers.Error" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.CSharp.Async.Rules" Version="6.1.41">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<AdditionalFiles Include="$(SolutionDir)stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

26
GlobalSuppressions.cs Normal file
View file

@ -0,0 +1,26 @@
// <copyright file="GlobalSuppressions.cs" company="alterNERDtive">
// Copyright 20202022 alterNERDtive.
//
// This file is part of bindED VoiceAttack plugin.
//
// bindED VoiceAttack plugin is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// bindED VoiceAttack plugin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with bindED VoiceAttack plugin. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:bindEDplugin")]

7
Makefile Normal file
View file

@ -0,0 +1,7 @@
.PHONY: docs deploy-docs
docs:
mkdocs build -c
deploy-docs:
mkdocs gh-deploy -cs

108
README.md
View file

@ -1,78 +1,30 @@
# bindED
This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as
VoiceAttack variables. It has originally been written by Gary (the developer of
VoiceAttack) [and published on the VoiceAttack
forums](https://forum.voiceattack.com/smf/index.php?topic=564.0).
You can find the [original README here](ReadMe.txt).
I have taken the original source code and added automatic detection of the
correct bindings file and support for non-US keyboard layouts (see below for
details).
## Migrating from the Old Plugin
If you use this as a drop-in replacement for the initial version all commands
invoking the plugin will throw an error message. Gary has asked me to change the
plugins GUID, and the plugin with the old one will no longer be found.
_That is irrelevant in basically all cases and can safely be ignored_. Binds
will be read automatically when VoiceAttack starts, and when they change.
## Usage
You dont have to do anything! When VoiceAttack loads, bindED will automatically
detect your bindings. It will also keep a watchful eye on Elites bindings
folder and reload them when there is a change!
If something goes awry, you can still manually call the `loadbinds` plugin
context to force a refresh.
If you are not using a US QWERTY keyboard layout, see below.
## Support for non-US Keyboard Layouts
Shipped layouts:
* en-US
* en-GB
* de-neo2
If you are using any non-US layout you might have noticed that some binds dont
work. Elite internally uses keycode values (a number assigned to each key on the
keyboard) for its bindings but for some reason both displays and saves them as
keysyms (the label on the key), according to the UK QWERTY keyboard layout. That
means VoiceAttack cant just send the keysym it reads from a binding, it has to
translate it into the corresponding keycode.
The original plugin contained a `EDMap.txt` file that contains information on
that conversion _for the US keyboard layout_. If you are using any other layout
that information will be incorrect for any symbols that are on a different key
than they are on the US layout.
I have added the option to use maps for other keyboard layouts. In order to do
so you will have to set a text variable in VoiceAttack called `bindED.layout#`
to the layout you want to use. BindED will be notified of the variable changing
and reload your bindings with the appropriate key map. If the variable is not
set it will defaut to “en-us”, leaving the original behaviour intact.
I have included a map file for [Neo2](https://neo-layout.org)
(`EDMap-de-neo2.txt`) which is the layout that I am using personally. If you are
on a different layout, you will have to create a corresponding map file yourself
or prod me to add it. E.g. for the french AZERTY it would be `EDMap-fr-fr.text`
and set `bindED.layout#` to “fr-fr”. For US Dvorak, `EDmap-en-us-dvorak` and
“en-us-dvorak”. You can see where this is going.
For more information on [creating new supported keyboard layouts see the
Wiki](https://github.com/alterNERDtive/bindED/wiki/Keyboard-Layouts).
## Troubleshooting
If you run into any kinds of trouble with missing bindings the first step should
be to import and load the included `bindED-reports` profile. It will generate
both a list of bind names used by Elite and a report of binds that do not have a
keyboard shortcut assigned, and put them on your Desktop.
Need help beyond that? Please [file an
issue](https://github.com/alterNERDtive/bindED/issues/new) or [hop into
Discord](https://discord.gg/YeXh2s5UC6).
# bindED
This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as
VoiceAttack variables. It was originally written by Gary (the developer of
VoiceAttack) [and published on the VoiceAttack
forums](https://forum.voiceattack.com/smf/index.php?topic=564.0). You can find
the [original README here](https://alterNERDtive.github.io/bindED/ReadMe.txt)
for reference.
I have basically done a complete rewrite of the original source code at this point
and added a lot of features including automatic detection of the correct
bindings file and support for non-US keyboard layouts (see below for details).
## Documentation & Installation Guide
You can find [comprehensive documentation on Github
Pages](https://alterNERDtive.github.io/bindED).
## Need Help / Want to Contribute?
Have a look at [the troubleshooting
guide](https://alterNERDtive.github.io/bindED/troubleshooting). If your problem
persists, please [file an
issue](https://github.com/alterNERDtive/bindED/issues/new). Thanks! :)
You can also [say “Hi” on Discord](https://discord.gg/YeXh2s5UC6) if that is
your thing.
[![GitHub Sponsors](https://img.shields.io/github/sponsors/alterNERDtive?style=for-the-badge)](https://github.com/sponsors/alterNERDtive)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S1DLYBS)

0
StyleCop.ruleset Normal file
View file

BIN
VoiceAttack.exe Normal file

Binary file not shown.

Binary file not shown.

916
bindED.cs
View file

@ -1,396 +1,520 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
namespace bindEDplugin
{
public class bindEDPlugin
{
private static dynamic? _VA;
private static string? _pluginPath;
private static readonly string _bindingsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Frontier Developments\Elite Dangerous\Options\Bindings");
private static readonly Dictionary<string, int> _fileEventCount = new Dictionary<string, int>();
private static FileSystemWatcher BindsWatcher
{
get
{
if (_bindsWatcher == null)
{
_bindsWatcher = new FileSystemWatcher(_bindingsDir);
_bindsWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
_bindsWatcher.Changed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_bindsWatcher.Created += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_bindsWatcher.Renamed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
}
return _bindsWatcher!;
}
}
private static FileSystemWatcher? _bindsWatcher;
private static FileSystemWatcher MapWatcher
{
get
{
if (_mapWatcher == null)
{
_mapWatcher = new FileSystemWatcher(_pluginPath);
_mapWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
_mapWatcher.Changed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_mapWatcher.Created += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_mapWatcher.Renamed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
}
return _mapWatcher!;
}
}
private static FileSystemWatcher? _mapWatcher;
private static string? Layout
{
get => _layout ??= _VA?.GetText("bindED.layout#") ?? "en-us";
set
{
_layout = value;
KeyMap = null;
}
}
private static string? _layout;
private static Dictionary<string, int>? KeyMap
{
get => _keyMap ??= LoadKeyMap(Layout!);
set => _keyMap = value;
}
private static Dictionary<string, int>? _keyMap;
private static string? Preset
{
get => _preset ??= DetectPreset();
set
{
_preset = value;
Binds = null;
}
}
private static string? _preset;
private static Dictionary<string, List<string>>? Binds
{
get => _binds ??= ReadBinds(DetectBindsFile(Preset));
set => _binds = value;
}
private static Dictionary<string, List<string>>? _binds;
public static string VERSION = "3.0";
public static string VA_DisplayName() => $"bindED Plugin v{VERSION}-alterNERDtive";
public static string VA_DisplayInfo() => "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n20202021 alterNERDtive";
public static Guid VA_Id() => new Guid("{524B4B9A-3965-4045-A39A-A239BF6E2838}");
public static void VA_Init1(dynamic vaProxy)
{
_VA = vaProxy;
_VA.TextVariableChanged += new Action<string, string, string, Guid?>(TextVariableChanged);
_pluginPath = Path.GetDirectoryName(_VA.PluginPath());
_VA.SetText("bindED.version", VERSION);
_VA.SetText("bindED.fork", "alterNERDtive");
try
{
LoadBinds(Binds);
}
catch (Exception e)
{
LogError(e.Message);
}
finally
{
BindsWatcher.EnableRaisingEvents = true;
MapWatcher.EnableRaisingEvents = true;
}
}
public static void VA_Invoke1(dynamic vaProxy)
{
_VA = vaProxy;
try
{
string context = _VA.Context.ToLower();
if (context == "listbinds")
{
ListBinds(Binds, _VA.GetText("bindED.separator") ?? "\r\n");
}
else if (context == "loadbinds")
{
// force reset everything
Layout = null;
Preset = null;
LoadBinds(Binds);
}
else if (context == "missingbinds")
{
MissingBinds(Binds);
}
else
{
LogWarn("Invoking the plugin with no context / a .binds file as context is deprecated and will be removed in a future version. Please invoke the 'loadbinds' context instead.");
LogWarn("Bindings are also read automatically on VoiceAttack start and there should be no need to do it explicitly.");
LoadBinds(Binds);
}
}
catch (Exception e)
{
LogError(e.Message);
}
}
public static void VA_StopCommand() { }
public static void VA_Exit1(dynamic vaProxy) { }
public static void TextVariableChanged(string name, string from, string to, Guid? internalID)
{
if (name == "bindED.layout#")
{
LogInfo($"Keyboard layout changed to '{to}', reloading …");
Layout = to;
try
{
LoadBinds(Binds);
}
catch (Exception e)
{
LogError(e.Message);
}
}
}
private static void LogError(string message)
{
_VA!.WriteToLog($"ERROR | bindED: {message}", "red");
}
private static void LogInfo(string message)
{
_VA!.WriteToLog($"INFO | bindED: {message}", "blue");
}
private static void LogWarn(string message)
{
_VA!.WriteToLog($"WARN | bindED: {message}", "yellow");
}
public static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
{
_VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys));
LogInfo("List of Elite binds saved to TXT variable '~bindED.bindsList'.");
}
private static void LoadBinds(Dictionary<string, List<string>>? binds)
{
foreach (KeyValuePair<string, List<string>> bind in binds!)
{
string value = string.Empty;
bool valid = true;
if (bind.Value.Count == 0)
{
//LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …");
}
else
{
foreach (string key in bind.Value)
{
if (KeyMap!.ContainsKey(key))
{
value += $"[{KeyMap[key]}]";
}
else
{
valid = false;
LogError($"No valid key code for '{key}' found, skipping bind for '{bind.Key}' …");
}
}
if (valid)
{
_VA!.SetText(bind.Key, value);
}
}
}
LogInfo($"Elite binds '{Preset}' for layout '{Layout}' loaded successfully.");
}
private static void MissingBinds(Dictionary<string, List<string>>? binds)
{
List<string> missing = new List<string>(256);
foreach (KeyValuePair<string, List<string>> bind in binds!)
{
if (bind.Value.Count == 0)
{
missing.Add(bind.Key);
}
}
if (missing.Count > 0)
{
_VA!.SetText("~bindED.missingBinds", string.Join("\r\n", missing));
_VA!.SetBoolean("~bindED.missingBinds", true);
LogInfo("List of missing Elite binds saved to TXT variable '~bindED.missingBinds'.");
}
else
{
LogInfo($"No missing keyboard binds found.");
}
}
private static Dictionary<String, int> LoadKeyMap(string layout)
{
string mapFile = Path.Combine(_pluginPath, $"EDMap-{layout.ToLower()}.txt");
if (!File.Exists(mapFile))
{
throw new FileNotFoundException($"No map file for layout '{layout}' found.");
}
Dictionary<string, int> map = new Dictionary<string, int>(256);
foreach (String line in File.ReadAllLines(mapFile, System.Text.Encoding.UTF8))
{
String[] arItem = line.Split(";".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries);
if ((arItem.Count() == 2) && (!String.IsNullOrWhiteSpace(arItem[0])) && (!map.ContainsKey(arItem[0])))
{
ushort iKey;
if (ushort.TryParse(arItem[1], out iKey))
{
if (iKey > 0 && iKey < 256)
map.Add(arItem[0].Trim(), iKey);
}
}
}
if (map.Count == 0)
{
throw new Exception($"Map file for {layout} does not contain any elements.");
}
return map;
}
private static string DetectPreset()
{
string startFile = Path.Combine(_bindingsDir, "StartPreset.start");
if (!File.Exists(startFile))
{
throw new FileNotFoundException("No 'StartPreset.start' file found. Please run Elite: Dangerous at least once, then restart VoiceAttack.");
}
return File.ReadAllText(startFile);
}
private static string DetectBindsFile(string? preset)
{
DirectoryInfo dirInfo = new DirectoryInfo(_bindingsDir);
FileInfo[] bindFiles = dirInfo.GetFiles().Where(i => Regex.Match(i.Name, $@"^{preset}(\.3\.0)?\.binds$").Success).OrderByDescending(p => p.LastWriteTime).ToArray();
if (bindFiles.Count() == 0)
{
throw new FileNotFoundException($"No bindings file found for preset '{preset}'. If this is a default preset, please change anything in Elites controls options.");
}
return bindFiles[0].FullName;
}
private static Dictionary<string, List<string>> ReadBinds(string file)
{
XElement rootElement;
rootElement = XElement.Load(file);
Dictionary<string, List<string>> binds = new Dictionary<string, List<string>>(512);
if (rootElement != null)
{
foreach (XElement c in rootElement.Elements().Where(i => i.Elements().Count() > 0))
{
List<string> keys = new List<string>();
foreach (var element in c.Elements().Where(i => i.HasAttributes))
{
if (element.Name == "Primary")
{
if (element.Attribute("Device").Value == "Keyboard" && !String.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
{
foreach (var modifier in element.Elements().Where(i => i.Name.LocalName == "Modifier"))
{
keys.Add(modifier.Attribute("Key").Value);
}
keys.Add(element.Attribute("Key").Value);
}
}
if (keys.Count == 0 && element.Name == "Secondary") //nothing found in primary... look in secondary
{
if (element.Attribute("Device").Value == "Keyboard" && !String.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
{
foreach (var modifier in element.Elements().Where(i => i.Name.LocalName == "Modifier"))
{
keys.Add(modifier.Attribute("Key").Value);
}
keys.Add(element.Attribute("Key").Value);
}
}
}
binds.Add($"ed{c.Name.LocalName}", keys);
}
}
return binds;
}
private static void FileChangedHandler(string name)
{
// so apparently these events all fire twice … lets make sure we only handle it once.
if (_fileEventCount.ContainsKey(name))
{
_fileEventCount[name] += 1;
}
else
{
_fileEventCount.Add(name, 1);
}
if (_fileEventCount[name] % 2 == 0)
{
try
{
// lets make semi-sure that the file isnt locked …
// FIXXME: solve this properly
Thread.Sleep(500);
// Going by name only is a bit naïve given were watching 2
// separate directories, but hey … worst case if something
// is doing unintended things is unnecessarily reloading the
// binds.
if (name == $"EDMap-{Layout!.ToLower()}.txt")
{
LogInfo($"Key map for layout '{Layout}' has changed, reloading …");
KeyMap = null;
LoadBinds(Binds);
}
else if (name == "StartPreset.start")
{
LogInfo("Controls preset has changed, reloading …");
Preset = null;
LoadBinds(Binds);
}
else if (Regex.Match(name, $@"{_preset}(\.3\.0)?\.binds$").Success)
{
LogInfo($"Bindings file '{name}' has changed, reloading …");
Binds = null;
LoadBinds(Binds);
}
}
catch (Exception e)
{
LogError(e.Message);
}
}
}
}
}
// <copyright file="bindED.cs" company="alterNERDtive">
// Copyright 20202024 alterNERDtive.
//
// This file is part of bindED VoiceAttack plugin.
//
// bindED VoiceAttack plugin is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// bindED VoiceAttack plugin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with bindED VoiceAttack plugin. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
namespace bindEDplugin
{
/// <summary>
/// This VoiceAttack plugin reads Elite Dangerous .binds files for keyboard
/// bindings and makes them available in VoiceAttack variables.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "historic, grandfathered in")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "historic, grandfathered in")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "historic, grandfathered in")]
public class bindEDPlugin
{
private static readonly Version VERSION = new ("5.0.1");
private static readonly string BindingsDir = Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData),
@"Frontier Developments\Elite Dangerous\Options\Bindings");
private static readonly Dictionary<string, int> FileEventCount = new ();
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "just cause")]
private static dynamic? VA;
private static string? pluginPath;
private static FileSystemWatcher? bindsWatcher;
private static FileSystemWatcher? mapWatcher;
private static string? layout;
private static Dictionary<string, int>? keyMap;
private static string? preset;
private static Dictionary<string, List<string>>? binds;
private static FileSystemWatcher BindsWatcher
{
get
{
if (bindsWatcher == null)
{
bindsWatcher = new FileSystemWatcher(BindingsDir);
bindsWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
bindsWatcher.Changed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
bindsWatcher.Created += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
bindsWatcher.Renamed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
}
return bindsWatcher!;
}
}
private static FileSystemWatcher MapWatcher
{
get
{
if (mapWatcher == null)
{
mapWatcher = new FileSystemWatcher(pluginPath);
mapWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
mapWatcher.Changed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
mapWatcher.Created += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
mapWatcher.Renamed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
}
return mapWatcher!;
}
}
private static string? Layout
{
get => layout ??= VA?.GetText("bindED.layout#") ?? "en-us";
set
{
layout = value;
KeyMap = null;
}
}
private static Dictionary<string, int>? KeyMap
{
get => keyMap ??= LoadKeyMap(Layout!);
set => keyMap = value;
}
private static string? Preset
{
get => preset ??= DetectPreset();
set
{
preset = value;
Binds = null;
}
}
private static Dictionary<string, List<string>>? Binds
{
get => binds ??= ReadBinds(DetectBindsFile(Preset!));
set => binds = value;
}
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName() => $"bindED Plugin v{VERSION}-alterNERDtive";
/// <summary>
/// The plugins description, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The description.</returns>
public static string VA_DisplayInfo() => "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n20202024 alterNERDtive";
/// <summary>
/// The plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public static Guid VA_Id() => new ("{524B4B9A-3965-4045-A39A-A239BF6E2838}");
/// <summary>
/// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
VA.TextVariableChanged += new Action<string, string, string, Guid?>(TextVariableChanged);
pluginPath = Path.GetDirectoryName(VA.PluginPath());
VA.SetText("bindED.version", VERSION.ToString());
VA.SetText("bindED.fork", "alterNERDtive");
try
{
LoadBinds(Binds);
}
catch (Exception e)
{
LogError(e.Message);
}
finally
{
BindsWatcher.EnableRaisingEvents = true;
MapWatcher.EnableRaisingEvents = true;
}
}
/// <summary>
/// The Invoke method, as required by the VoiceAttack plugin API.
/// Runs whenever a plugin context is invoked.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Invoke1(dynamic vaProxy)
{
VA = vaProxy;
try
{
string context = VA.Context.ToLower();
if (context == "diagnostics")
{
LogInfo($"current keybord layout: {Layout}");
LogInfo($"current preset: {Preset}");
LogInfo($"detected binds file: {new FileInfo(DetectBindsFile(Preset!)).Name}");
LogInfo($"detected binds file (full path): {DetectBindsFile(Preset!)}");
}
else if (context == "listbinds")
{
ListBinds(Binds, VA.GetText("bindED.separator") ?? "\r\n");
}
else if (context == "loadbinds")
{
// force reset everything
Layout = null;
Preset = null;
if (!string.IsNullOrWhiteSpace(VA.GetText("~bindsFile")))
{
Binds = ReadBinds(Path.Combine(BindingsDir, VA.GetText("~bindsFile")));
}
LoadBinds(Binds);
}
else if (context == "missingbinds")
{
MissingBinds(Binds);
}
else
{
if (string.IsNullOrWhiteSpace(context))
{
LogError("Empty plugin context.");
}
else
{
LogError($"Invalid plugin context '{context}'.");
}
LogError("You generally do not need to invoke the plugin manually.");
}
}
catch (Exception e)
{
LogError(e.Message);
}
}
/// <summary>
/// The Exit method, as required by the VoiceAttack plugin API.
/// Runs when VoiceAttack is shut down.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
public static void VA_Exit1(dynamic vaProxy)
{
}
/// <summary>
/// The StopCommand method, as required by the VoiceAttack plugin API.
/// Runs whenever all commands are stopped using the “Stop All Commands”
/// button or action.
/// </summary>
public static void VA_StopCommand()
{
}
private static void TextVariableChanged(string name, string from, string to, Guid? internalID = null)
{
if (name == "bindED.layout#")
{
LogInfo($"Keyboard layout changed to '{to}', reloading …");
Layout = to;
try
{
LoadBinds(Binds);
}
catch (Exception e)
{
LogError(e.Message);
}
}
}
private static void LogError(string message)
{
VA!.WriteToLog($"ERROR | bindED: {message}", "red");
}
private static void LogInfo(string message)
{
VA!.WriteToLog($"INFO | bindED: {message}", "blue");
}
private static void LogWarn(string message)
{
VA!.WriteToLog($"WARN | bindED: {message}", "yellow");
}
private static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
{
VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys));
LogInfo("List of Elite binds saved to TXT variable '~bindED.bindsList'.");
}
private static void LoadBinds(Dictionary<string, List<string>>? binds)
{
foreach (KeyValuePair<string, List<string>> bind in binds!)
{
string value = string.Empty;
bool valid = true;
if (bind.Value.Count == 0)
{
// LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …");
}
else
{
foreach (string key in bind.Value)
{
if (KeyMap!.ContainsKey(key))
{
value += $"[{KeyMap[key]}]";
}
else
{
valid = false;
LogError($"No valid key code for '{key}' found, skipping bind for '{bind.Key}' …");
}
}
if (valid)
{
VA!.SetText(bind.Key, value);
}
}
}
LogInfo($"Elite binds '{(string.IsNullOrWhiteSpace(VA!.GetText("~bindsFile")) ? Preset : VA!.GetText("~bindsFile"))}' for layout '{Layout}' loaded successfully.");
}
private static void MissingBinds(Dictionary<string, List<string>>? binds)
{
List<string> missing = new (256);
foreach (KeyValuePair<string, List<string>> bind in binds!)
{
if (bind.Value.Count == 0)
{
missing.Add(bind.Key);
}
}
if (missing.Count > 0)
{
VA!.SetText("~bindED.missingBinds", string.Join("\r\n", missing));
VA!.SetBoolean("~bindED.missingBinds", true);
LogInfo("List of missing Elite binds saved to TXT variable '~bindED.missingBinds'.");
}
else
{
LogInfo($"No missing keyboard binds found.");
}
}
private static Dictionary<string, int> LoadKeyMap(string layout)
{
string mapFile = Path.Combine(pluginPath, $"EDMap-{layout.ToLower()}.txt");
if (!File.Exists(mapFile))
{
throw new FileNotFoundException($"No map file for layout '{layout}' found.");
}
Dictionary<string, int> map = new (256);
foreach (string line in File.ReadAllLines(mapFile, System.Text.Encoding.UTF8))
{
string[] arItem = line.Split(";".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries);
if ((arItem.Count() == 2) && (!string.IsNullOrWhiteSpace(arItem[0])) && (!map.ContainsKey(arItem[0])))
{
ushort iKey;
if (ushort.TryParse(arItem[1], out iKey))
{
if (iKey > 0 && iKey < 256)
{
map.Add(arItem[0].Trim(), iKey);
}
}
}
}
if (map.Count == 0)
{
throw new Exception($"Map file for {layout} does not contain any elements.");
}
return map;
}
private static string DetectPreset()
{
string startFile = Path.Combine(BindingsDir, "StartPreset.4.start");
if (!File.Exists(startFile))
{
startFile = Path.Combine(BindingsDir, "StartPreset.start");
if (!File.Exists(startFile))
{
throw new FileNotFoundException("No 'StartPreset.start' file found. Please run Elite: Dangerous at least once, then restart VoiceAttack.");
}
}
IEnumerable<string> presets = File.ReadAllLines(startFile).Distinct();
if (presets.Count() > 1)
{
LogError($"You have selected multiple control presets ('{string.Join("', '", presets)}'). "
+ $"Only binds from '{presets.First()}' will be used. Please refer to the documentation for more information.");
}
return presets.First();
}
private static string DetectBindsFile(string preset)
{
DirectoryInfo dirInfo = new (BindingsDir);
FileInfo[] bindFiles = dirInfo.GetFiles()
.Where(i => Regex.Match(i.Name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
.OrderByDescending(p => p.Name).ToArray();
if (bindFiles.Count() == 0)
{
bindFiles = dirInfo.GetFiles($"{preset}.binds");
if (bindFiles.Count() == 0)
{
throw new FileNotFoundException($"No bindings file found for preset '{preset}'. If this is a default preset, please change anything in Elites controls options.");
}
}
return bindFiles[0].FullName;
}
private static Dictionary<string, List<string>> ReadBinds(string file)
{
XElement rootElement;
rootElement = XElement.Load(file);
Dictionary<string, List<string>> binds = new (512);
if (rootElement != null)
{
foreach (XElement c in rootElement.Elements().Where(i => i.Elements().Count() > 0))
{
List<string> keys = new ();
foreach (var element in c.Elements().Where(i => i.HasAttributes))
{
if (element.Name == "Primary")
{
if (element.Attribute("Device").Value == "Keyboard"
&& !string.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
{
foreach (var modifier in element.Elements().Where(i => i.Name.LocalName == "Modifier"))
{
keys.Add(modifier.Attribute("Key").Value);
}
keys.Add(element.Attribute("Key").Value);
}
}
if (keys.Count == 0 && element.Name == "Secondary")
{ // nothing found in primary... look in secondary
if (element.Attribute("Device").Value == "Keyboard"
&& !string.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
{
foreach (var modifier in element.Elements().Where(i => i.Name.LocalName == "Modifier"))
{
keys.Add(modifier.Attribute("Key").Value);
}
keys.Add(element.Attribute("Key").Value);
}
}
}
binds.Add($"ed{c.Name.LocalName}", keys);
}
}
return binds;
}
private static void FileChangedHandler(string name)
{
// so apparently these events all fire twice … lets make sure we only handle it once.
if (FileEventCount.ContainsKey(name))
{
FileEventCount[name] += 1;
}
else
{
FileEventCount.Add(name, 1);
}
if (FileEventCount[name] % 2 == 0)
{
try
{
// lets make semi-sure that the file isnt locked …
// FIXXME: solve this properly
Thread.Sleep(500);
// Going by name only is a bit naïve given were watching 2
// separate directories, but hey … worst case if something
// is doing unintended things is unnecessarily reloading the
// binds.
if (name == $"EDMap-{Layout!.ToLower()}.txt")
{
LogInfo($"Key map for layout '{Layout}' has changed, reloading …");
KeyMap = null;
LoadBinds(Binds);
}
else if (name == "StartPreset.start")
{
LogInfo("Controls preset has changed, reloading …");
Preset = null;
LoadBinds(Binds);
}
else if (Regex.Match(name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
{
LogInfo($"Bindings file '{name}' has changed, reloading …");
Binds = null;
LoadBinds(Binds);
}
}
catch (Exception e)
{
LogError(e.Message);
}
}
}
}
}

View file

@ -21,7 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<CodeAnalysisRuleSet />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -30,7 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<CodeAnalysisRuleSet />
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
@ -40,6 +40,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="bindED.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@ -54,6 +55,8 @@
</COMReference>
</ItemGroup>
<ItemGroup>
<None Include="docs\index.md" />
<Content Include=".gitignore" />
<Content Include="EDMap-de-neo2.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -63,35 +66,32 @@
<Content Include="EDMap-en-us.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="ReadMe.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="docs\ReadMe.txt" />
</ItemGroup>
<ItemGroup>
<None Include=".editorconfig" />
<None Include="bindED-diagnostics-Profile.vap">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="bindED-reports-Profile.vap">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="CHANGELOG.md" />
<None Include="docs\troubleshooting.md" />
<None Include="LICENSE">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="mkdocs.yml" />
<None Include="README.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>powershell if (Test-Path '$(SolutionDir)bindEDplugin.zip') { Remove-Item -Path '$(SolutionDir)bindEDplugin.zip' }</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>if $(ConfigurationName) == Release (powershell Compress-Archive -Path '$(TargetDir)' -DestinationPath '$(SolutionDir)bindEDplugin.zip' -Force)</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release' ">
<ZipDirectory SourceDirectory="bin" DestinationFile="bindEDplugin.zip" Overwrite="true" />
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View file

@ -1,11 +1,18 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30517.126
# Visual Studio Version 17
VisualStudioVersion = 17.3.32519.111
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bindEDplugin", "bindEDplugin.csproj", "{C8AC9134-639D-45D2-B5EF-138E0550E0C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC89314B-E504-4D5D-BB48-F1E3E4980A13}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\create-release.yaml = .github\workflows\create-release.yaml
Directory.build.props = Directory.build.props
GlobalSuppressions.cs = GlobalSuppressions.cs
stylecop.json = stylecop.json
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -1,335 +1,335 @@
----------------------------------------------------------------
About the bindED plugin - v1.0.0.1 (See end for version history)
----------------------------------------------------------------
The bindED plugin was created to copy the keyboard key bind information for Elite: Dangerous directly into VoiceAttack keypress variables. The variables
can then be used in conjunction with key press actions within VoiceAttack (the latest betas of VoiceAttack allow for keypresses based on variables).
The idea is to be able to change your key bindings from within Elite: Dangerous and have each change be reflected (semi-automatically) in VoiceAttack,
possibly saving some time and/or hair loss.
Thanks to Lavaeolus in the VoiceAttack user forum for starting up the EDMap.txt layout, as well as all those that participate in making plugins all they
can be ;)
--------------------------------------------------------------------------------------------------------------------------------
Setting up the bindED plugin
--------------------------------------------------------------------------------------------------------------------------------
Copy the entire bindED folder from the zip file to the VoiceAttack Apps folder (the whole folder, not just the contents). The folder is usually located
in C:\Program Files (x86)\VoiceAttack (An installer may be created later to make this easier... for now it's just a zip ;))
For most, this is all you'll need to do. For others having trouble or want to explore, read on:
Inside the bindED folder, you will find the plugin file itself (bindED.dll), which uses VoiceAttack's Plugin v4 interface... that just means that you
will need VoiceAttack beta version v1.5.12.32 or later to use the plugin.
You will also find a file called, 'EDMap.txt'. If you open this text file, you will see that it contains each of the Elite: Dangerous key bind types associated
with a numeric code. The code is simply the keyboard key code expressed as a number. For instance, the, 'A' key is 65, 'Z' is 90, 'Escape' is 27 and so on.
This is for English-US keyboards. Your keyboard layout may not line up exactly with this mapping, so adjustments may need to be made (you can edit this file
as you see fit, just as long as it follows the KeyName;Code structure that you see inside the file).
If you happen to create a layout that is different or find a mistake/omission in the English-US layout (this is new stuff, after all), please feel free to
upload your update to the VoiceAttack user forum for others to use: http://www.voiceattack.com/forum
To see the list of key codes, go here: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
Note that the key codes are expressed in hexadecimal and will need to be converted to integer values.
--------------------------------------------------------------------------------------------------------------------------------
Using the bindED plugin
--------------------------------------------------------------------------------------------------------------------------------
To use the plugin, you will need to do a few things.
1) First, you will need to turn on plugin support in VoiceAttack. This is done from the Options screen.
2) Next, you will need to create a command in VoiceAttack to call the bindED plugin which (optionally) includes the Elite: Dangerous binds file that you want to use.
To do this, add a new command and then click on Other -> Advanced -> Execute an external plugin function.
3) In the new plugin action screen, select the, 'bindED' plugin from the Plugin list (bindED will be available if you had turned on plugin support and had installed
the bindED plugin correctly).
4) There are two routes you can take here. If you only have ONE binds file, and you want the bindED plugin to try to access your .binds file that is located
in C:\Users\YOUR_USER_NAME\AppData\Local\Frontier Developments\Elite Dangerous\Options\Bindings, simply leave the, 'Plugin Context' box blank.
The plugin will simply grab the NEWEST .binds file it finds in that directory and use it.
If you have MULTIPLE binds files for different situations, OR, the default behavior indicated above does not meet your need, you can specify which binds
file the bindED plugin is to access by typing or pasting the full path to the binds file that you want to use in the 'Plugin Context' box:
Selecting the, 'Wait for the plugin to fininsh...' option is up to you. Generally this should run very quicky, but if you have a situation where things need
to occur in a very specific order, select that option.
The full path is cut off in the image, but you get the idea ;)
5) Click OK, Done, etc.
The next time that this new command is executed (whether by itself to, 'reload' bindings or from a profile change) the VoiceAttack variables will be updated
with what is indicated in the bind file.
--------------------------------------------------------------------------------------------------------------------------------
How to use the keypress variables once they are updated
--------------------------------------------------------------------------------------------------------------------------------
This is where it gets a little tricky. There are a lot of words here, but as you'll see, there's not a whole lot going on.
First, you will need to be familiar with the Elite: Dangerous key binds in-game, as well as where those binds are stored in the configuration (.binds) files.
For this example, the landing gear toggle will be used. To toggle the landing gear, let's say the 'L' key is used in-game.
The configuration file (.binds) will store that information in an xml element called, 'LandingGearToggle'. Inside the binds file, you will see the entry
looks something like this:
<LandingGearToggle>
<Primary Device="Keyboard" Key="Key_L" />
<Secondary Device="{NoDevice}" Key="" />
</LandingGearToggle>
When VoiceAttack encounters this element, it creates and a TEXT variable called, 'edLandingGearToggle'. It's just simply the element name with, 'ed'
prefixed to it. Then, VoiceAttack notices that the key used is 'Key_L'. VoiceAttack gets the value indicated in the EDMap.txt file and puts it in the,
'edLandingGearToggle' variable (you don't necessarily have to know this... I threw it in there in case you were wondering ;))
So, in VoiceAttack (later betas), you can have a key (or keys) pressed based on a variable. At the bottom of the keypress screen, you will see an input box
that will allow you to specify what variable to use. Before, you would have, 'L' selected at the top of the screen. Now, you would just type in,
'edLandingGearToggle' (no quotes) in the variable input box.
NOTE: A current list (well... my current list) of variables that can be affected are listed at the end of this file.
Your command probably looks like this now:
When I say... 'Landing Gear'
Press L key and hold for 0.1 seconds and release
It would now look like this:
When I say... 'Landing Gear'
Press variable key(s) [edLandingGear] and hold for 0.1 seconds and release
If anybody out there wants to create an Elite: Dangerous variable mapping to what is seen in-game, that would be really cool ;)
--------------------------------------------------------------------------------------------------------------------------------
Advanced stuff and things to consider for those who want even more confusion.
--------------------------------------------------------------------------------------------------------------------------------
Note that you can have multiple plugin actions to load several (possibly custom) bind files at the same time if you need to. Each subsequently loaded bind file
will override the previous ones. You can also do this with one action by separating the bind paths with a semicolon. You may never need to do this, but it's
there if you want to do something with it.
Way off the chart... Something else to try that is left over from testing (and left in for anybody that wants to use it) is if you want to load a
binds file on plugin initialization (that is, when the plugin first loads (when VA starts up)... not when the plugin is invoked), create shortcuts to
the binds files and place them in the same folder as the plugin .dll. Each shortcut is processed in alphabetical order. This is one way to have an
automatic, global initialization of variables that do not require the plugin to be invoked. If this doesn't make any sense to you, do not worry ;)
If you are going to be creating profiles for others, it might be a good idea to put some extra steps in your commands in case your users do not have
the bindED plugin (either not installed, not configured properly or not initialized). Going to expand on the whole landing gear thing again in this example...
Begin Text Compare : [edLandingGearToggle] Has Been Set
Press variable key(s) [edLandingGearToggle] and hold for 0.1 seconds and release
Else
Press L key and hold for 0.1 seconds and release
Write '[Orange] Variable keypress not set. Using default keypress.' to log
End Condition
Note that if the variable HAS BEEN SET (not null), the keypress uses the variable. If the variable has not been set, you can have a fall-back keypress, or,
just show a warning.
One more thing... if you happen to have a '.binds' file for Star Citizen that has a good variety of key/key combinations (with modifiers... ctrl/alt/shift/win),
please let me know and I will create a bindSC ;)
--------------------------------------------------------------------------------------------------------------------------------
Variable List Reference
--------------------------------------------------------------------------------------------------------------------------------
Below is a current list of the variables that bindED can update (and you can use in your keypress actions). Remember, the element within the .binds file is
whatever the variable name is below, minus the, 'ed' prefix. So, 'edAutoBreakBuggyButton' below correlates to the 'AutoBreakBuggyButton' element in the .binds
file. Note that if no keys are set for the particular element, the variable value will be null (Not Set). This list can change at any time depending
on Frontier, and is only here to give you an idea of what's currently available without having to dig into your .binds file. Good luck, Captain. ;)
edAutoBreakBuggyButton
edBackwardKey
edBackwardThrustButton
edBackwardThrustButton_Landing
edBuggyPitchDownButton
edBuggyPitchUpButton
edBuggyPrimaryFireButton
edBuggyRollLeftButton
edBuggyRollRightButton
edBuggySecondaryFireButton
edBuggyToggleReverseThrottleInput
edBuggyTurretPitchDownButton
edBuggyTurretPitchUpButton
edBuggyTurretYawLeftButton
edBuggyTurretYawRightButton
edCamPitchDown
edCamPitchUp
edCamTranslateBackward
edCamTranslateDown
edCamTranslateForward
edCamTranslateLeft
edCamTranslateRight
edCamTranslateUp
edCamTranslateZHold
edCamYawLeft
edCamYawRight
edCamZoomIn
edCamZoomOut
edChargeECM
edCycleFireGroupNext
edCycleFireGroupPrevious
edCycleNextHostileTarget
edCycleNextPanel
edCycleNextSubsystem
edCycleNextTarget
edCyclePreviousHostileTarget
edCyclePreviousPanel
edCyclePreviousSubsystem
edCyclePreviousTarget
edDecreaseSpeedButtonMax
edDeployHardpointToggle
edDeployHeatSink
edDisableRotationCorrectToggle
edDownThrustButton
edDownThrustButton_Landing
edEjectAllCargo
edEjectAllCargo_Buggy
edEngineColourToggle
edFireChaffLauncher
edFocusCommsPanel
edFocusCommsPanel_Buggy
edFocusLeftPanel
edFocusLeftPanel_Buggy
edFocusRadarPanel
edFocusRadarPanel_Buggy
edFocusRightPanel
edFocusRightPanel_Buggy
edForwardKey
edForwardThrustButton
edForwardThrustButton_Landing
edGalaxyMapOpen
edGalaxyMapOpen_Buggy
edHeadlightsBuggyButton
edHeadLookPitchDown
edHeadLookPitchUp
edHeadLookReset
edHeadLookToggle
edHeadLookToggle_Buggy
edHeadLookYawLeft
edHeadLookYawRight
edHMDReset
edHyperspace
edHyperSuperCombination
edIncreaseEnginesPower
edIncreaseEnginesPower_Buggy
edIncreaseSpeedButtonMax
edIncreaseSystemsPower
edIncreaseSystemsPower_Buggy
edIncreaseWeaponsPower
edIncreaseWeaponsPower_Buggy
edLandingGearToggle
edLeftThrustButton
edLeftThrustButton_Landing
edMicrophoneMute
edMouseReset
edOpenOrders
edOrbitLinesToggle
edOrderAggressiveBehaviour
edOrderDefensiveBehaviour
edOrderFocusTarget
edOrderFollow
edOrderHoldFire
edOrderHoldPosition
edOrderRequestDock
edPause
edPhotoCameraToggle
edPhotoCameraToggle_Buggy
edPitchDownButton
edPitchDownButton_Landing
edPitchUpButton
edPitchUpButton_Landing
edPrimaryFire
edQuickCommsPanel
edQuickCommsPanel_Buggy
edRadarDecreaseRange
edRadarIncreaseRange
edRecallDismissShip
edResetPowerDistribution
edResetPowerDistribution_Buggy
edRightThrustButton
edRightThrustButton_Landing
edRollLeftButton
edRollLeftButton_Landing
edRollRightButton
edRollRightButton_Landing
edSecondaryFire
edSelectHighestThreat
edSelectTarget
edSelectTarget_Buggy
edSelectTargetsTarget
edSetSpeed100
edSetSpeed25
edSetSpeed50
edSetSpeed75
edSetSpeedMinus100
edSetSpeedMinus25
edSetSpeedMinus50
edSetSpeedMinus75
edSetSpeedZero
edShipSpotLightToggle
edShowPGScoreSummaryInput
edSteerLeftButton
edSteerRightButton
edSupercruise
edSystemMapOpen
edSystemMapOpen_Buggy
edTargetNextRouteSystem
edTargetWingman0
edTargetWingman1
edTargetWingman2
edToggleBuggyTurretButton
edToggleButtonUpInput
edToggleCargoScoop
edToggleCargoScoop_Buggy
edToggleDriveAssist
edToggleFlightAssist
edToggleReverseThrottleInput
edUI_Back
edUI_Down
edUI_Left
edUI_Right
edUI_Select
edUI_Toggle
edUI_Up
edUIFocus
edUIFocus_Buggy
edUpThrustButton
edUpThrustButton_Landing
edUseAlternateFlightValuesToggle
edUseBoostJuice
edUseShieldCell
edVerticalThrustersButton
edWeaponColourToggle
edWingNavLock
edYawLeftButton
edYawLeftButton_Landing
edYawRightButton
edYawRightButton_Landing
edYawToRollButton
--------------------------------------------------------------------------------------------------------------------------------
Version History
--------------------------------------------------------------------------------------------------------------------------------
v1.0 - Initial release
v1.0.0.1 - Fixed file sorting issue when accessing .binds files.
----------------------------------------------------------------
About the bindED plugin - v1.0.0.1 (See end for version history)
----------------------------------------------------------------
The bindED plugin was created to copy the keyboard key bind information for Elite: Dangerous directly into VoiceAttack keypress variables. The variables
can then be used in conjunction with key press actions within VoiceAttack (the latest betas of VoiceAttack allow for keypresses based on variables).
The idea is to be able to change your key bindings from within Elite: Dangerous and have each change be reflected (semi-automatically) in VoiceAttack,
possibly saving some time and/or hair loss.
Thanks to Lavaeolus in the VoiceAttack user forum for starting up the EDMap.txt layout, as well as all those that participate in making plugins all they
can be ;)
--------------------------------------------------------------------------------------------------------------------------------
Setting up the bindED plugin
--------------------------------------------------------------------------------------------------------------------------------
Copy the entire bindED folder from the zip file to the VoiceAttack Apps folder (the whole folder, not just the contents). The folder is usually located
in C:\Program Files (x86)\VoiceAttack (An installer may be created later to make this easier... for now it's just a zip ;))
For most, this is all you'll need to do. For others having trouble or want to explore, read on:
Inside the bindED folder, you will find the plugin file itself (bindED.dll), which uses VoiceAttack's Plugin v4 interface... that just means that you
will need VoiceAttack beta version v1.5.12.32 or later to use the plugin.
You will also find a file called, 'EDMap.txt'. If you open this text file, you will see that it contains each of the Elite: Dangerous key bind types associated
with a numeric code. The code is simply the keyboard key code expressed as a number. For instance, the, 'A' key is 65, 'Z' is 90, 'Escape' is 27 and so on.
This is for English-US keyboards. Your keyboard layout may not line up exactly with this mapping, so adjustments may need to be made (you can edit this file
as you see fit, just as long as it follows the KeyName;Code structure that you see inside the file).
If you happen to create a layout that is different or find a mistake/omission in the English-US layout (this is new stuff, after all), please feel free to
upload your update to the VoiceAttack user forum for others to use: http://www.voiceattack.com/forum
To see the list of key codes, go here: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
Note that the key codes are expressed in hexadecimal and will need to be converted to integer values.
--------------------------------------------------------------------------------------------------------------------------------
Using the bindED plugin
--------------------------------------------------------------------------------------------------------------------------------
To use the plugin, you will need to do a few things.
1) First, you will need to turn on plugin support in VoiceAttack. This is done from the Options screen.
2) Next, you will need to create a command in VoiceAttack to call the bindED plugin which (optionally) includes the Elite: Dangerous binds file that you want to use.
To do this, add a new command and then click on Other -> Advanced -> Execute an external plugin function.
3) In the new plugin action screen, select the, 'bindED' plugin from the Plugin list (bindED will be available if you had turned on plugin support and had installed
the bindED plugin correctly).
4) There are two routes you can take here. If you only have ONE binds file, and you want the bindED plugin to try to access your .binds file that is located
in C:\Users\YOUR_USER_NAME\AppData\Local\Frontier Developments\Elite Dangerous\Options\Bindings, simply leave the, 'Plugin Context' box blank.
The plugin will simply grab the NEWEST .binds file it finds in that directory and use it.
If you have MULTIPLE binds files for different situations, OR, the default behavior indicated above does not meet your need, you can specify which binds
file the bindED plugin is to access by typing or pasting the full path to the binds file that you want to use in the 'Plugin Context' box:
Selecting the, 'Wait for the plugin to fininsh...' option is up to you. Generally this should run very quicky, but if you have a situation where things need
to occur in a very specific order, select that option.
The full path is cut off in the image, but you get the idea ;)
5) Click OK, Done, etc.
The next time that this new command is executed (whether by itself to, 'reload' bindings or from a profile change) the VoiceAttack variables will be updated
with what is indicated in the bind file.
--------------------------------------------------------------------------------------------------------------------------------
How to use the keypress variables once they are updated
--------------------------------------------------------------------------------------------------------------------------------
This is where it gets a little tricky. There are a lot of words here, but as you'll see, there's not a whole lot going on.
First, you will need to be familiar with the Elite: Dangerous key binds in-game, as well as where those binds are stored in the configuration (.binds) files.
For this example, the landing gear toggle will be used. To toggle the landing gear, let's say the 'L' key is used in-game.
The configuration file (.binds) will store that information in an xml element called, 'LandingGearToggle'. Inside the binds file, you will see the entry
looks something like this:
<LandingGearToggle>
<Primary Device="Keyboard" Key="Key_L" />
<Secondary Device="{NoDevice}" Key="" />
</LandingGearToggle>
When VoiceAttack encounters this element, it creates and a TEXT variable called, 'edLandingGearToggle'. It's just simply the element name with, 'ed'
prefixed to it. Then, VoiceAttack notices that the key used is 'Key_L'. VoiceAttack gets the value indicated in the EDMap.txt file and puts it in the,
'edLandingGearToggle' variable (you don't necessarily have to know this... I threw it in there in case you were wondering ;))
So, in VoiceAttack (later betas), you can have a key (or keys) pressed based on a variable. At the bottom of the keypress screen, you will see an input box
that will allow you to specify what variable to use. Before, you would have, 'L' selected at the top of the screen. Now, you would just type in,
'edLandingGearToggle' (no quotes) in the variable input box.
NOTE: A current list (well... my current list) of variables that can be affected are listed at the end of this file.
Your command probably looks like this now:
When I say... 'Landing Gear'
Press L key and hold for 0.1 seconds and release
It would now look like this:
When I say... 'Landing Gear'
Press variable key(s) [edLandingGear] and hold for 0.1 seconds and release
If anybody out there wants to create an Elite: Dangerous variable mapping to what is seen in-game, that would be really cool ;)
--------------------------------------------------------------------------------------------------------------------------------
Advanced stuff and things to consider for those who want even more confusion.
--------------------------------------------------------------------------------------------------------------------------------
Note that you can have multiple plugin actions to load several (possibly custom) bind files at the same time if you need to. Each subsequently loaded bind file
will override the previous ones. You can also do this with one action by separating the bind paths with a semicolon. You may never need to do this, but it's
there if you want to do something with it.
Way off the chart... Something else to try that is left over from testing (and left in for anybody that wants to use it) is if you want to load a
binds file on plugin initialization (that is, when the plugin first loads (when VA starts up)... not when the plugin is invoked), create shortcuts to
the binds files and place them in the same folder as the plugin .dll. Each shortcut is processed in alphabetical order. This is one way to have an
automatic, global initialization of variables that do not require the plugin to be invoked. If this doesn't make any sense to you, do not worry ;)
If you are going to be creating profiles for others, it might be a good idea to put some extra steps in your commands in case your users do not have
the bindED plugin (either not installed, not configured properly or not initialized). Going to expand on the whole landing gear thing again in this example...
Begin Text Compare : [edLandingGearToggle] Has Been Set
Press variable key(s) [edLandingGearToggle] and hold for 0.1 seconds and release
Else
Press L key and hold for 0.1 seconds and release
Write '[Orange] Variable keypress not set. Using default keypress.' to log
End Condition
Note that if the variable HAS BEEN SET (not null), the keypress uses the variable. If the variable has not been set, you can have a fall-back keypress, or,
just show a warning.
One more thing... if you happen to have a '.binds' file for Star Citizen that has a good variety of key/key combinations (with modifiers... ctrl/alt/shift/win),
please let me know and I will create a bindSC ;)
--------------------------------------------------------------------------------------------------------------------------------
Variable List Reference
--------------------------------------------------------------------------------------------------------------------------------
Below is a current list of the variables that bindED can update (and you can use in your keypress actions). Remember, the element within the .binds file is
whatever the variable name is below, minus the, 'ed' prefix. So, 'edAutoBreakBuggyButton' below correlates to the 'AutoBreakBuggyButton' element in the .binds
file. Note that if no keys are set for the particular element, the variable value will be null (Not Set). This list can change at any time depending
on Frontier, and is only here to give you an idea of what's currently available without having to dig into your .binds file. Good luck, Captain. ;)
edAutoBreakBuggyButton
edBackwardKey
edBackwardThrustButton
edBackwardThrustButton_Landing
edBuggyPitchDownButton
edBuggyPitchUpButton
edBuggyPrimaryFireButton
edBuggyRollLeftButton
edBuggyRollRightButton
edBuggySecondaryFireButton
edBuggyToggleReverseThrottleInput
edBuggyTurretPitchDownButton
edBuggyTurretPitchUpButton
edBuggyTurretYawLeftButton
edBuggyTurretYawRightButton
edCamPitchDown
edCamPitchUp
edCamTranslateBackward
edCamTranslateDown
edCamTranslateForward
edCamTranslateLeft
edCamTranslateRight
edCamTranslateUp
edCamTranslateZHold
edCamYawLeft
edCamYawRight
edCamZoomIn
edCamZoomOut
edChargeECM
edCycleFireGroupNext
edCycleFireGroupPrevious
edCycleNextHostileTarget
edCycleNextPanel
edCycleNextSubsystem
edCycleNextTarget
edCyclePreviousHostileTarget
edCyclePreviousPanel
edCyclePreviousSubsystem
edCyclePreviousTarget
edDecreaseSpeedButtonMax
edDeployHardpointToggle
edDeployHeatSink
edDisableRotationCorrectToggle
edDownThrustButton
edDownThrustButton_Landing
edEjectAllCargo
edEjectAllCargo_Buggy
edEngineColourToggle
edFireChaffLauncher
edFocusCommsPanel
edFocusCommsPanel_Buggy
edFocusLeftPanel
edFocusLeftPanel_Buggy
edFocusRadarPanel
edFocusRadarPanel_Buggy
edFocusRightPanel
edFocusRightPanel_Buggy
edForwardKey
edForwardThrustButton
edForwardThrustButton_Landing
edGalaxyMapOpen
edGalaxyMapOpen_Buggy
edHeadlightsBuggyButton
edHeadLookPitchDown
edHeadLookPitchUp
edHeadLookReset
edHeadLookToggle
edHeadLookToggle_Buggy
edHeadLookYawLeft
edHeadLookYawRight
edHMDReset
edHyperspace
edHyperSuperCombination
edIncreaseEnginesPower
edIncreaseEnginesPower_Buggy
edIncreaseSpeedButtonMax
edIncreaseSystemsPower
edIncreaseSystemsPower_Buggy
edIncreaseWeaponsPower
edIncreaseWeaponsPower_Buggy
edLandingGearToggle
edLeftThrustButton
edLeftThrustButton_Landing
edMicrophoneMute
edMouseReset
edOpenOrders
edOrbitLinesToggle
edOrderAggressiveBehaviour
edOrderDefensiveBehaviour
edOrderFocusTarget
edOrderFollow
edOrderHoldFire
edOrderHoldPosition
edOrderRequestDock
edPause
edPhotoCameraToggle
edPhotoCameraToggle_Buggy
edPitchDownButton
edPitchDownButton_Landing
edPitchUpButton
edPitchUpButton_Landing
edPrimaryFire
edQuickCommsPanel
edQuickCommsPanel_Buggy
edRadarDecreaseRange
edRadarIncreaseRange
edRecallDismissShip
edResetPowerDistribution
edResetPowerDistribution_Buggy
edRightThrustButton
edRightThrustButton_Landing
edRollLeftButton
edRollLeftButton_Landing
edRollRightButton
edRollRightButton_Landing
edSecondaryFire
edSelectHighestThreat
edSelectTarget
edSelectTarget_Buggy
edSelectTargetsTarget
edSetSpeed100
edSetSpeed25
edSetSpeed50
edSetSpeed75
edSetSpeedMinus100
edSetSpeedMinus25
edSetSpeedMinus50
edSetSpeedMinus75
edSetSpeedZero
edShipSpotLightToggle
edShowPGScoreSummaryInput
edSteerLeftButton
edSteerRightButton
edSupercruise
edSystemMapOpen
edSystemMapOpen_Buggy
edTargetNextRouteSystem
edTargetWingman0
edTargetWingman1
edTargetWingman2
edToggleBuggyTurretButton
edToggleButtonUpInput
edToggleCargoScoop
edToggleCargoScoop_Buggy
edToggleDriveAssist
edToggleFlightAssist
edToggleReverseThrottleInput
edUI_Back
edUI_Down
edUI_Left
edUI_Right
edUI_Select
edUI_Toggle
edUI_Up
edUIFocus
edUIFocus_Buggy
edUpThrustButton
edUpThrustButton_Landing
edUseAlternateFlightValuesToggle
edUseBoostJuice
edUseShieldCell
edVerticalThrustersButton
edWeaponColourToggle
edWingNavLock
edYawLeftButton
edYawLeftButton_Landing
edYawRightButton
edYawRightButton_Landing
edYawToRollButton
--------------------------------------------------------------------------------------------------------------------------------
Version History
--------------------------------------------------------------------------------------------------------------------------------
v1.0 - Initial release
v1.0.0.1 - Fixed file sorting issue when accessing .binds files.

BIN
docs/images/keypress.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

77
docs/index.md Normal file
View file

@ -0,0 +1,77 @@
# bindED
This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as
VoiceAttack variables. It was originally written by Gary (the developer of
VoiceAttack) [and published on the VoiceAttack
forums](https://forum.voiceattack.com/smf/index.php?topic=564.0). You can find
the [original README here](ReadMe.txt) for reference.
I have basically done a complete rewrite the original source code at this point
and added a lot of features including automatic detection of the correct
bindings file and support for non-US keyboard layouts (see below for details).
## Installing
Grab `bindEDplugin.zip` from the [release
page](https://github.com/alterNERDtive/bindED/releases/latest) and extract it
into your VoiceAttacks `Apps` directory. If you have an older version already
installed, please delete the `bindED` subfolder first.
For Horizons players, thats it! When VoiceAttack loads, bindED will
automatically detect your bindings. It will also keep a watchful eye on Elites
bindings folder and reload them when there is a change!
For Odyssey players, there is an additional caveat: you have to use the same
preset for all 4 sections (general, ship, SRV, foot). Sadly its not apparent
from the files which of these sections a bind belongs to, so there is no simple
way to read multiple files properly.
## Using Binds In Commands
Each bind in Elite Dangerous has a name assigned in the binds file. The plugin
takes this name, prepends `ed` and turns it into a VoiceAttack variable.
To press the corresponding key in the game, you will have to create a new
`Key Press` action in a VoiceAttack command and use the desired variable named
after the ingame bind. In order to toggle your landing gear for example you will
have to create a `Key Press` action and use the `edLandingGearToggle` variable.
![[keypress.png]]
Instead of looking through a binds file to find variable names, you can also
import and run the `bindED-reports` profile from the plugin directory. It will
put a) a list of all binds and b) a list of all missing binds on your Desktop.
Currently these lists will include _all_ binds, including axes that cannot be
bound to the keyboard.
## Supported Keyboard Layouts
If you are using any non-US layout you might have noticed that some binds dont
work. You can set a text variable in VoiceAttack called `bindED.layout#` to the
layout you want to use. If the variable is not set it will defaut to `en-us`.
This default should work for most™ keys on any keyboard layout natively
supported by Elite Dangerous; some special keys might not be usable.
The following layouts are _fully_ supported out of the box:
* en-US
* en-GB
* de-neo2
If you are on a different layout, you can either use only the keys that work
with the default layout or add support for your preferred layout yourself. See
[[Troubleshooting#Adding a Keyboard Layout]].
## Need Help / Want to Contribute?
If you run into any errors have a look at [[Troubleshooting]]. If your problem
persists, please [file an
issue](https://github.com/alterNERDtive/bindED/issues/new). Thanks! :)
You can also [say “Hi” on Discord](https://discord.gg/YeXh2s5UC6) if that is
your thing.
[![GitHub Sponsors](https://img.shields.io/github/sponsors/alterNERDtive?style=for-the-badge)](https://github.com/sponsors/alterNERDtive)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S1DLYBS)

121
docs/troubleshooting.md Normal file
View file

@ -0,0 +1,121 @@
# Troubleshooting
## Diagnosing Plugin Issues
If something goes awry you can manually call the `loadbinds` plugin context or
restart VoiceAttack to force a refresh.
If that doesnt help and your problems persist the first step should be to
import and load the included `bindED-diagnostics` profile from the plugin
directory. It will output the current plugin state (keyboard layout, preset and
binds file used) to the VoiceAttack log. You should also include the output of
that if you file an issue or ask for help on Discord.
If that seems correct, theres the `bindED-reports` profile. It will generate
both a list of bind names used by Elite and a report of binds that do not have a
keyboard shortcut assigned, and put them on your Desktop. Note that those lists
are currently not filtered in any way and will contain _all_ binds regardless if
they are used in any profiles or if they can even have a keyboard key assigned
(e.g. axes).
## Migrating from the Old Plugin
If you use this as a drop-in replacement for the initial version of bindED made
by Gary all commands invoking the plugin will throw an error message. Gary has
asked me to change the plugins GUID, and the plugin with the old one will no
longer be found. _That is irrelevant in basically all cases and can safely be
ignored_.
Binds will be read automatically when VoiceAttack starts and whenever they
change. To get rid of the error message(s) remove the outdated plugin
invocation(s) from your commands.
## Make Sure You Actually Have Binds Files
Before starting VoiceAttack with the plugin installed, you need to load the game
at least once! That will create the directory structure the plugin is going to
read from. You also need to have changed _any_ key in controls options.
If you start Elite for the first time with VoiceAttack already running you will
have to either invoke the plugins `loadbinds` config manually or restart
VoiceAttack after you have loaded into the games main menu and changed any key
bind.
## Horizons (legacy) vs. Odyssey (live)
Horizons support has been dropped. If you still play legacy, you will have to
use bindED <5.0.
## Specifying a Binds File to Load
You can set the text variable `~bindsFile` to a specific file name (e.g.
`custom.3.0.binds`) before executing the `loadbinds` context to have that
specific binds file loaded.
Make sure to only use the _file name_ of an existing binds file, do _not_
specify the full path.
This should be a last resort effort for when the game introduces changes that
break the plugins auto detection.
## Adding a Keyboard Layout
If you are using any non-US layout you might have noticed that some binds dont
work. The game itself supports a certain range of “standard” keyboard layouts
and falls back to UK QWERTY if you are using something else. The original plugin
was made for the US keyboard layout. So if you are using a layout that is not
natively supported by Elite and/or that has keys that dont exist on the US
layout, some keys will not work out of the box, e.g.:
* “VoiceAttack presses `p` but the game thinks its `v`!” (layout not natively
supported by Elite)
* “VoiceAttack presses `ß` but nothing happens!” (key not in the US key map)
I have included a map file for [Neo2](https://neo-layout.org)
(`EDMap-de-neo2.txt`) which is the layout that I am using personally and
A.Cyprus was kind enough to provide full support for en-GB. If you are on a
different layout, you will have to create a corresponding map file yourself. But
if you do Ill be happy to include it in future releases!
To add support for a new keyboard layout, you will have to create the
corresponding `EDMap-<id>.txt` file. Start by copying `EDMap-en-us.txt` in the
plugin directory and renaming it appropriately. The file contains a list of
`sym:code` pairs, one per line. The `sym` part is exactly like it is listed in
Elites `.binds` files, the `code` is the corresponding keycode VoiceAttack is
going to send.
You will have to go through the entire list and replace the existing `code`s
with the right ones for your layout. In order to do so I recommend following
these steps:
1. Open a reference for the UK QWERTY layout, [e.g. Wikipedias
image](https://en.wikipedia.org/wiki/QWERTY#/media/File:KB_United_Kingdom.svg).
1. Get a tool that can display keycodes for keys you press, [e.g. NirSofts
KeyboardStateView](https://www.nirsoft.net/utils/keyboard_state_view.html).
Or anything else that does it. Make sure it shows you _decimal_ keycodes
instead of (only) hex!
1. Make sure your PC is actually set to the layout you want to add.
1. Go through the file one by one.
1. Take note of its position on the keyboard. In this example we are using
the one in the top left next to `1` and my layout Neo2.
1. Find the correct key on the UK reference, for this example “Key_Grave”.
1. Press the corresponding key on your keyboard and observe the keycode it
produces. For Neo2 that was 186.
1. Change the `code` in the line to the correct keycode. For Neo2 the line
reads “Key_Grave:186”.
1. Repeat for the next line.
There are a bunch of symbols in the file that arent on a standard keyboard,
just ignore those. If you have extra keys that are not on the UK QWERTY layout,
youll have to go into the games controls options and try binding those keys.
If it works take the symbols they produce and go back to step 4 with them.
For the actual _letters_ youll have to assign the keycode of the key you are
pressing to the “Key_X” that is displayed ingame. So if the ingame controls
options say youve just pressed “H”, replace the “Key_H” line with the keycode
you get when you press the key you bound ingame. If the game assigns the letters
that are actually in that place on your layout, you wont have to do anything
for these.
Once you have tested and confirmed everything working, feel free to open a pull
request or issue and Ill include the new map!

26
mkdocs.yml Normal file
View file

@ -0,0 +1,26 @@
site_name: "bindED VoiceAttack plugin"
site_url: https://alterNERDtive.github.io/bindED
repo_url: https://github.com/alterNERDtive/bindED
edit_uri: "edit/devel/docs/"
site_description: "This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as VoiceAttack variables."
site_author: "alterNERDtive"
remote_name: "origin"
theme:
name: readthedocs
prev_next_buttons_location: both
plugins:
- search
- roamlinks
markdown_extensions:
- toc:
permalink: True
- sane_lists
nav:
- 'index.md'
- 'troubleshooting.md'
- '⎋ Changelog': 'https://github.com/alterNERDtive/bindED/blob/release/CHANGELOG.md'
- '⎋ Report a Bug': 'https://github.com/alterNERDtive/bindED/issues/'

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
mkdocs-roamlinks-plugin

16
stylecop.json Normal file
View file

@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace"
},
"documentationRules": {
"companyName": "alterNERDtive",
"copyrightText": "Copyright {year} {companyName}.\n\nThis file is part of {application}.\n\n{application} is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\n{application} is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with {application}. If not, see <https://www.gnu.org/licenses/>.",
"variables": {
"application": "bindED VoiceAttack plugin",
"year": "20202022"
}
}
}
}