Skip to content

Commit

Permalink
feat: add windows NVDA support (#6)
Browse files Browse the repository at this point in the history
* feat: check registry for guidepup sub key
* feat: handle listing, creation, and update of reg key
* fix: downgrade `regedit` REF: kessler/node-regedit#110
* ci: add resolution fix for ci
* fix: only perform resolution fix for macos
* feat: add portable nvda zip
* feat: add nvda download
* feat: unzip portable nvda
* feat: use `decompress` package
* chore: disable macos-12
  • Loading branch information
cmorten authored Dec 18, 2022
1 parent c71bb2c commit 775f2a7
Show file tree
Hide file tree
Showing 16 changed files with 528 additions and 13 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/resolutionFix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { execSync } from "child_process";

if (process.platform === "darwin") {
try {
execSync(
`"/Library/Application Support/VMware Tools/vmware-resolutionSet" 1920 1080`
);
} catch (_) {
// swallow
}
}
10 changes: 9 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-11, macos-12, macos-latest]
os:
[
macos-11,
# macos-12,
macos-latest,
windows-2019,
windows-2022,
windows-latest,
]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ If you are using GitHub Actions, check out the dedicated [`guidepup/setup-action
uses: guidepup/[email protected]
```
## Debugging
## Recording
If you are encountering errors in CI you can pass a `--record` flag to the command which will output a screen-recording of the setup to a `./recordings/` directory.
If you are encountering errors in CI for MacOS you can pass a `--record` flag to the command which will output a screen-recording of the setup to a `./recordings/` directory.

## NVDA Installation

When running on windows a portable NVDA instance compatible with Guidepup will be installed to a temporary directory. The location of this installation directory is stored in the Windows registry under the key `HKCU\Software\Guidepup\Nvda`.

## See Also 🐶

Expand All @@ -53,7 +57,7 @@ Check out some of the other Guidepup modules:
Support:

- [x] VoiceOver on MacOS
- [ ] NVDA on Windows
- [x] NVDA on Windows
- [ ] VoiceOver on iOS
- [ ] Talkback on Android

Expand Down
2 changes: 1 addition & 1 deletion bin/setup
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/usr/bin/env node
require("../lib/");
require("../lib");
Binary file added downloads/guidepup_nvda.zip
Binary file not shown.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
],
"scripts": {
"build": "yarn clean && yarn compile",
"ci": "yarn clean && yarn lint && yarn build && yarn start --ci --record",
"ci": "yarn clean && yarn lint && yarn build && yarn resolutionFix && yarn start --ci --record",
"clean": "rimraf lib",
"compile": "tsc",
"dev": "ts-node ./src/index.ts",
"resolutionFix": "ts-node ./.github/workflows/resolutionFix.ts",
"lint": "eslint . --ext .ts",
"lint:fix": "yarn lint --fix",
"start": "./bin/setup",
"start": "node ./bin/setup",
"prepublish": "yarn build"
},
"devDependencies": {
Expand All @@ -46,7 +47,9 @@
},
"dependencies": {
"chalk": "^4.0.0",
"decompress": "^4.2.1",
"inquirer": "^8.2.0",
"regedit": "5.0.1",
"semver": "^7.3.5"
}
}
4 changes: 4 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ export const ERR_MACOS_REQUIRES_MANUAL_USER_INTERACTION =
"Unable to setup environment without manual user interaction";
export const ERR_MACOS_FAILED_TO_ENABLE_DO_NOT_DISTURB =
'Failed to enable "Do not disturb" mode';

export const ERR_WINDOWS_UNABLE_TO_ACCESS_REGISTRY = "Unable to access Windows registry";
export const ERR_WINDOWS_UNABLE_TO_UPDATE_REGISTRY = "Unable to update Windows registry";
export const ERR_WINDOWS_FAILED_TO_INSTALL_NVDA = "Unable to install NVDA";
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { setup as setupMacOS } from "./macOS/setup";
import { setup as setupWindows } from "./windows/setup";
import { handleError, handleComplete } from "./logging";
import { ERR_UNSUPPORTED_OS } from "./errors";

Expand All @@ -9,6 +10,10 @@ async function run(): Promise<void> {
await setupMacOS();
break;
}
case "win32": {
await setupWindows();
break;
}
default: {
throw new Error(ERR_UNSUPPORTED_OS);
}
Expand Down
5 changes: 5 additions & 0 deletions src/windows/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { version } = require("../../package.json");

export const VERSIONED_KEY = `guidepup_nvda_${version}`;
export const SUB_KEY_GUIDEPUP_NVDA = "HKCU\\Software\\Guidepup\\Nvda";
11 changes: 11 additions & 0 deletions src/windows/createNvdaRegistryKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { promisified as regedit } from "regedit";
import { SUB_KEY_GUIDEPUP_NVDA } from "./constants";
import { ERR_WINDOWS_UNABLE_TO_UPDATE_REGISTRY } from "../errors";

export async function createNvdaRegistryKey() {
try {
await regedit.createKey([SUB_KEY_GUIDEPUP_NVDA]);
} catch {
throw new Error(ERR_WINDOWS_UNABLE_TO_UPDATE_REGISTRY);
}
}
15 changes: 15 additions & 0 deletions src/windows/getNvdaRegistryData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { promisified as regedit } from "regedit";
import { SUB_KEY_GUIDEPUP_NVDA } from "./constants";
import { ERR_WINDOWS_UNABLE_TO_ACCESS_REGISTRY } from "../errors";

export async function getNvdaRegistryData() {
try {
const {
[SUB_KEY_GUIDEPUP_NVDA]: { exists, values },
} = await regedit.list([SUB_KEY_GUIDEPUP_NVDA]);

return { exists, values };
} catch {
throw new Error(ERR_WINDOWS_UNABLE_TO_ACCESS_REGISTRY);
}
}
61 changes: 61 additions & 0 deletions src/windows/installNvda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import decompress from "decompress";
import { get } from "https";
import { createWriteStream, mkdtempSync, rmSync } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import { ERR_WINDOWS_FAILED_TO_INSTALL_NVDA } from "../errors";

const appName = "guidepup_nvda";
const sourceUrl = `https://raw.githubusercontent.com/guidepup/setup/feat/add-nvda-setup/downloads/${appName}.zip`;

export async function installNvda(): Promise<string> {
const destinationBaseDirectory = mkdtempSync(join(tmpdir(), `${appName}_`));
const destinationDirectory = join(destinationBaseDirectory, appName);
const destinationZip = join(destinationBaseDirectory, `${appName}.zip`);
const fileZip = createWriteStream(destinationZip);

function removeAll() {
try {
rmSync(destinationBaseDirectory, { recursive: true });
} catch {
// swallow
}
}

function removeZip() {
try {
rmSync(destinationZip, { recursive: true });
} catch {
// swallow
}
}

try {
await new Promise<void>((resolve, reject) => {
function onSuccess() {
fileZip.close((error) => {
if (error) {
return reject(error);
}

resolve();
});
}

const request = get(sourceUrl, (response) => response.pipe(fileZip));
request.on("error", reject);
fileZip.on("finish", onSuccess);
fileZip.on("error", reject);
});

await decompress(destinationZip, destinationBaseDirectory);

removeZip();
} catch {
removeAll();

throw new Error(ERR_WINDOWS_FAILED_TO_INSTALL_NVDA);
}

return destinationDirectory;
}
16 changes: 16 additions & 0 deletions src/windows/isNvdaInstalled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { existsSync } from "fs";
import { VERSIONED_KEY } from "./constants";

export function isNvdaInstalled({ exists, values }) {
if (!exists) {
return false;
}

const path = values[VERSIONED_KEY]?.value;

if (!path) {
return false;
}

return existsSync(path);
}
21 changes: 21 additions & 0 deletions src/windows/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getNvdaRegistryData } from "./getNvdaRegistryData";
import { isNvdaInstalled } from "./isNvdaInstalled";
import { createNvdaRegistryKey } from "./createNvdaRegistryKey";
import { installNvda } from "./installNvda";
import { updateNvdaRegistryData } from "./updateNvdaRegistryData";

export async function setup(): Promise<void> {
const { exists, values } = await getNvdaRegistryData();

if (isNvdaInstalled({ exists, values })) {
return;
}

if (!exists) {
await createNvdaRegistryKey();
}

const nvdaDirectory = await installNvda();

await updateNvdaRegistryData({ nvdaDirectory });
}
18 changes: 18 additions & 0 deletions src/windows/updateNvdaRegistryData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { promisified as regedit } from "regedit";
import { SUB_KEY_GUIDEPUP_NVDA, VERSIONED_KEY } from "./constants";
import { ERR_WINDOWS_UNABLE_TO_UPDATE_REGISTRY } from "../errors";

export async function updateNvdaRegistryData({ nvdaDirectory }) {
try {
await regedit.putValue({
[SUB_KEY_GUIDEPUP_NVDA]: {
[VERSIONED_KEY]: {
value: nvdaDirectory,
type: "REG_SZ",
},
},
});
} catch {
throw new Error(ERR_WINDOWS_UNABLE_TO_UPDATE_REGISTRY);
}
}
Loading

0 comments on commit 775f2a7

Please sign in to comment.