Compare commits

...

64 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
4791bc3e26 3.1! 2021-01-29 15:16:52 +01:00
f25f6cd1e2 README: new Discord invite link 2021-01-28 17:26:34 +01:00
fa1709c782 now sets some version information
fixes #8
2021-01-06 22:30:57 +01:00
1fb2a491f4 updated CHANGELOG 2020-11-17 10:29:14 +01:00
4310a48aad now watching current layout’s map file for changes
fixes #4
2020-11-17 10:27:33 +01:00
225456a993 teensy tiny CHANGELOG typo 2020-11-15 00:36:02 +01:00
42df5c990c make loadbinds context a force reset of everything
fixes #5
2020-11-14 17:41:43 +01:00
25 changed files with 1494 additions and 845 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}]
[*.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

1
.gitignore vendored
View file

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

View file

@ -1,8 +1,138 @@
# 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
side, the things that no longer work like they did in Garys initial release
should basically never be used anyway.
## Removed
@ -29,9 +159,9 @@ should basically never be used anyway.
* 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)
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 save them to your Desktop.
list report when you load it and saves them to your Desktop.
-----
@ -39,6 +169,6 @@ should basically never be used anyway.
## Added
* Support for non-US keyboard layouts. `de-neo2` is included (because thats
what Im using), others can be added ([see the wiki for
* 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

View file

@ -1,78 +1,30 @@
# bindED
# 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 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).
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.
You can find the [original README here](ReadMe.txt).
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).
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).
## Documentation & Installation Guide
## Migrating from the Old Plugin
You can find [comprehensive documentation on Github
Pages](https://alterNERDtive.github.io/bindED).
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.
## Need Help / Want to Contribute?
_That is irrelevant in basically all cases and can safely be ignored_. Binds
will be read automatically when VoiceAttack starts, and when they change.
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! :)
## Usage
You can also [say “Hi” on Discord](https://discord.gg/YeXh2s5UC6) if that is
your thing.
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/kXtXm54).
[![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.

327
bindED.cs
View file

@ -1,4 +1,23 @@
#nullable enable
// <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;
@ -10,82 +29,131 @@ 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 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 readonly Version VERSION = new ("5.0.1");
private static FileSystemWatcher Watcher
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 (_watcher == null)
if (bindsWatcher == null)
{
_watcher = new FileSystemWatcher(_bindingsDir);
_watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
_watcher.Changed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_watcher.Created += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_watcher.Renamed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
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 _watcher!;
return bindsWatcher!;
}
}
private static FileSystemWatcher? _watcher;
private static string Layout
private static FileSystemWatcher MapWatcher
{
get => _layout ??= _VA?.GetText("bindED.layout#") ?? "en-us";
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;
layout = value;
KeyMap = null;
}
}
private static string? _layout;
private static Dictionary<string, int>? KeyMap
{
get => _keyMap ??= LoadKeyMap(Layout);
set
{
_keyMap = value;
}
get => keyMap ??= LoadKeyMap(Layout!);
set => keyMap = value;
}
private static Dictionary<string, int>? _keyMap;
private static string? Preset
{
get => _preset ??= DetectPreset();
get => preset ??= DetectPreset();
set
{
_preset = value;
preset = value;
Binds = null;
}
}
private static string? _preset;
private static Dictionary<string, List<string>>? Binds
{
get => _binds ??= ReadBinds(DetectBindsFile(Preset));
set => _binds = value;
get => binds ??= ReadBinds(DetectBindsFile(Preset!));
set => binds = value;
}
private static Dictionary<string, List<string>>? _binds;
public static string VERSION = "3.0";
/// <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";
public static string VA_DisplayInfo() => "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n2020 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";
public static Guid VA_Id() => new Guid("{524B4B9A-3965-4045-A39A-A239BF6E2838}");
/// <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 = 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
{
@ -97,22 +165,43 @@ namespace bindEDplugin
}
finally
{
Watcher.EnableRaisingEvents = true;
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;
VA = vaProxy;
try
{
string context = _VA.Context.ToLower();
if (context == "listbinds")
string context = VA.Context.ToLower();
if (context == "diagnostics")
{
ListBinds(Binds, _VA.GetText("bindED.separator") ?? "\r\n");
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")
@ -121,9 +210,16 @@ namespace bindEDplugin
}
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);
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)
@ -132,11 +228,26 @@ namespace bindEDplugin
}
}
public static void VA_StopCommand() { }
/// <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)
{
}
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()
{
}
public static void TextVariableChanged(string name, string from, string to, Guid? internalID)
private static void TextVariableChanged(string name, string from, string to, Guid? internalID = null)
{
if (name == "bindED.layout#")
{
@ -155,22 +266,22 @@ namespace bindEDplugin
private static void LogError(string message)
{
_VA!.WriteToLog($"ERROR | bindED: {message}", "red");
VA!.WriteToLog($"ERROR | bindED: {message}", "red");
}
private static void LogInfo(string message)
{
_VA!.WriteToLog($"INFO | bindED: {message}", "blue");
VA!.WriteToLog($"INFO | bindED: {message}", "blue");
}
private static void LogWarn(string message)
{
_VA!.WriteToLog($"WARN | bindED: {message}", "yellow");
VA!.WriteToLog($"WARN | bindED: {message}", "yellow");
}
public static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
private static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
{
_VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys));
VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys));
LogInfo("List of Elite binds saved to TXT variable '~bindED.bindsList'.");
}
@ -182,7 +293,7 @@ namespace bindEDplugin
bool valid = true;
if (bind.Value.Count == 0)
{
//LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …");
// LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …");
}
else
{
@ -198,30 +309,32 @@ namespace bindEDplugin
LogError($"No valid key code for '{key}' found, skipping bind for '{bind.Key}' …");
}
}
if (valid)
{
_VA!.SetText(bind.Key, value);
VA!.SetText(bind.Key, value);
}
}
}
LogInfo($"Elite binds '{Preset}' for layout '{Layout}' loaded successfully.");
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 List<string>(256);
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);
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
@ -230,52 +343,75 @@ namespace bindEDplugin
}
}
private static Dictionary<String, int> LoadKeyMap(string layout)
private static Dictionary<string, int> LoadKeyMap(string layout)
{
string mapFile = Path.Combine(_pluginPath, $"EDMap-{layout.ToLower()}.txt");
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))
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])))
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");
string startFile = Path.Combine(BindingsDir, "StartPreset.4.start");
if (!File.Exists(startFile))
{
throw new FileNotFoundException("No 'StartPreset.start' file found. Please run Elite: Dangerous at least once, then restart VoiceAttack.");
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);
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)
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();
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)
{
throw new FileNotFoundException($"No bindings file found for preset '{preset}'. If this is a default preset, please change anything in Elites controls options.");
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;
@ -287,68 +423,87 @@ namespace bindEDplugin
rootElement = XElement.Load(file);
Dictionary<string, List<string>> binds = new Dictionary<string, List<string>>(512);
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 List<string>();
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_"))
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_"))
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))
if (FileEventCount.ContainsKey(name))
{
_fileEventCount[name] += 1;
FileEventCount[name] += 1;
}
else
{
_fileEventCount.Add(name, 1);
FileEventCount.Add(name, 1);
}
if (_fileEventCount[name] % 2 == 0)
if (FileEventCount[name] % 2 == 0)
{
try
{
// lets make semi-sure that the file isnt locked …
// FIXXME: solve this properly
Thread.Sleep(500);
if (name == "StartPreset.start")
// 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("Controls preset changed, reloading …");
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)
else if (Regex.Match(name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
{
LogInfo($"Bindings file '{name}' has changed, reloading …");
Binds = null;

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>

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,4 +1,4 @@
----------------------------------------------------------------
----------------------------------------------------------------
About the bindED plugin - v1.0.0.1 (See end for version history)
----------------------------------------------------------------

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"
}
}
}
}