Skip to content

Commit

Permalink
Merge pull request #12 from sebinside/plugin-dev
Browse files Browse the repository at this point in the history
Rework HotkeylessAHK and add Stream Deck plugin
  • Loading branch information
sebinside authored Sep 11, 2022
2 parents 8ee14a6 + 3c4d352 commit bff41be
Show file tree
Hide file tree
Showing 25 changed files with 1,952 additions and 42 deletions.
22 changes: 13 additions & 9 deletions HotkeylessAHK.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ SetWorkingDir %A_ScriptDir%
Menu, Tray, Icon, shell32.dll, 147
#singleinstance force

#Include files/lib.ahk
#Include files\lib.ahk

; HotkeylessAHK by sebinside
; ALL INFORMATION: https://github.com/sebinside/HotkeylessAHK
; Make sure that you have downloaded everything, especially the "/files" folder.
; Make sure that you have nodeJS installed and available in the PATH variable.
; Make sure that you have NodeJS installed and available in the PATH variable.

SetupServer()
RunClient()

; Your custom functions go here!
; Your custom functions go into the 'CustomFunctions' class.
; You can then call them by using the URL "localhost:42800/send/yourFunctionName"
; The funciton name "kill" is reserved to end the script execution.
; The function name "kill" is reserved.

HelloWorld() {
MsgBox, Hello World
}
Class CustomFunctions {

HelloWorld() {
MsgBox, Hello World
}

OpenExplorer() {
Run, explorer.exe
}

OpenExplorer() {
Run, explorer.exe
}
34 changes: 24 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

## Hotkeyless AutoHotkey

The **AutoHotkey** (AHK) environment is a *powerful tool* to enhance your productivity and *speed up your workflow*. There are so many possibilities and use cases that sometimes... you might *run out of hotkeys*. Or you might want to *combine* AHK-scripts or trigger them from different sources.
The **AutoHotkey** (AHK) environment is a *powerful tool* to enhance your productivity and *speed up your workflow*. There are so many possibilities and use cases that sometimes... you might *run out of hotkeys*. Or you might want to *combine* AHK-scripts or trigger them from different sources or even from other computers in your local network.

Until now, you had only *two possible solutions*:

- **Exotic key combinations**, like... `CTRL + ALT + F13`. This solution is **fast**, but not very scalable. You might not remember, which hotkeys are still available and sometimes encounter bad side effects with your favorite software
- **Single AHK-scripts**, which you run directly from AHK or the explorer window. This solution is **scalable**, but how can you live with about *one second* of delay, while the AHK-process is starting up?
- **Single AHK-scripts**, which you run directly from AHK or the explorer window. This solution is **scalable**, but how can you live with the delay of the AHK-process starting up?

**Hotkeyless AutoHotkey** combines the best of both worlds. It's an easy way to expand your AHK-possibilities, both **fast** and **scalable**. But don't take my word for it, here are some numbers:
**Hotkeyless AutoHotkey** combines the best of both worlds. It's an easy way to expand your AHK-capabilities, both **fast** and **scalable**. But don't take my word for it, here are some numbers:

| Approach | Delay | Fast | Scalable |
| ----------------------- | :------- | :--: | :------: |
Expand All @@ -19,39 +19,51 @@ Until now, you had only *two possible solutions*:

You can make your own *performance tests*. Just have a look at the `performance-tests`-folder!

Also, **Hotkeyless AutoHotkey** enables the execution of AutoHotkey code from other computers in the same local network. And we also provide a **Stream Deck** plugin, that enables calling AHK code by simply pressing a button (and without cluttering up your shortcut list).

## Functionality

The **Hotkeyless AHK** script launches a lightweight web server. It listens to the endpoint `localhost:42800/send/*` for http requests and redirects them to the `HotkeylessAHK.ahk` script. To be more precise, if you call `localhost:42800/send/HelloWorld`, the `HelloWorld()`-function inside the AHK-file is executed:
**Hotkeyless AHK** launches a lightweight web server. Per default, it listens to the endpoint `localhost:42800/send/*` for http requests and redirects them to the `HotkeylessAHK.ahk` script. Simply put, if you call `localhost:42800/send/HelloWorld`, the `HelloWorld()`-function inside the AHK-file is executed:

```ahk
HelloWorld() {
MsgBox, Hello World
}
```

Also, you can include your AHK-scripts and define custom functionality in a nice and clean way - without loosing too much performance. You can use your web browser, shortcuts or utility hardware like the [Stream Deck](https://www.elgato.com/gaming/stream-deck), like this:
Also, you can include your AHK-scripts and define custom functionality in a nice and clean way - without loosing too much performance. You can use your web browser, shortcuts or the [Elgato Stream Deck](https://www.elgato.com/gaming/stream-deck) with the **HotkeylessAHK** plugin. This plugin automatically crawls existing functions and lets you call them easily.

![Stream Deck example](streamdeck.PNG)

## Installation

*Note: Requires Windows 10*
### HotkeylessAHK

*Note: Requires Windows 10 or newer*

1. Install [AutoHotkey](https://www.autohotkey.com/). Well... if you're reading this, you will probably already have.
1. Install [AutoHotkey](https://www.autohotkey.com/). Well... if you're reading this, you probably already have.
2. Install node. You can download it from https://nodejs.org/. Make sure that node is in the PATH-variable and available from the console. You can test this by executing `node -v`
3. Clone or download this repository. You can also just head over to [releases](https://github.com/sebinside/HotkeylessAHK/releases)
4. Open a console window and enter the `files` folder. Then, execute `npm install` to install all web server dependencies.
4. Open a console window and enter the `files` folder. Then, execute `npm i` to install all web server dependencies.
5. Start *Hotkeyless AutoHotkey* by executing the `HotkeylessAHK.ahk`-file.
6. Open your web browser and navigate to `http://localhost:42800/send/HelloWorld`. This should open a message dialog, triggered by the `HotkeylessAHK.ahk`-file.
7. Now, you're ready to go.

*Troubleshooting*: If anything does not work, disable console window hiding by deleting lines 3 and 4 in the `SetupServer()`-method in the `lib.ahk`-file and restart the process. This might give you more information.
*Troubleshooting*: If anything does not work, disable console window hiding by deleting lines 3 and 4 in the `SetupServer()`-method in the `files/lib.ahk`-file and restart the process. This might give you more information.

```
DllCall("AllocConsole")
WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr")
```

### Elgato Stream Deck Plugin

1. Clone or download this repository or a release of it. You probably already have done this during the installation of **HotkeylessAHK** explained above.
2. Make sure you are using a [Elgato Stream Deck](https://www.elgato.com/gaming/stream-deck) and have the Stream Deck Software installed.
3. Identify the Stream Deck Software plugin folder. On windows, it should be located here: `%appdata%\Elgato\StreamDeck\Plugins\`. Find more information in the [developer documentation](https://developer.elgato.com/documentation/stream-deck/sdk/create-your-own-plugin/).
4. Copy everything inside of the `stream-deck-plugin` folder into the plugin folder of the Stream Deck Software. It should look like this: `%appdata%\Elgato\StreamDeck\Plugins\de.sebinside.hotkeylessahk.sdPlugin`.
5. Restart the Stream Deck Software. The new plugin should appear in your plugin list.

## Usage

Once installed, the usage of **Hotkeyless AutoHotkey** is easy: Write your methods inside the `HotkeylessAHK.ahk`-file (or include other scripts) and call them with your web browser, the `curl`-console command, a stream deck, ...
Expand All @@ -60,12 +72,14 @@ The endpoint is always the same: `http://localhost:42800/send/YourFunctionNameGo

To terminate the running tool, call `http://localhost:42800/send/kill`.

If you're using the [Elgato Stream Deck](https://www.elgato.com/gaming/stream-deck) plugin, you will not have to deal with the internals.

## More

This is yet another small tool to enhance the power of AHK. Some more links, you might find interesting:

- **[AHK2PremiereCEP](https://github.com/sebinside/AHK2PremiereCEP)**, another utility tool from me which helps you connect AutoHotkey with the Adobe Premiere CEP scripting environment. A very helpful tool for video production.
- Taran Van Hemert, a macro specialist: https://www.youtube.com/user/TaranVH
- And my own twitch channel, where I develop with these techniques, sometimes: https://www.twitch.tv/skate702
- And my own twitch channel, where I develop with these techniques: https://www.twitch.tv/skate702

If there are more questions, you can contact me on [Twitter](https://twitter.com/skate702) or via [mail](mailto:hi@sebinside.de).
2 changes: 1 addition & 1 deletion files/index.js → files/dist/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__esModule = true;
var server_1 = require("./server");
var SERVER_PORT = 42800;
console.log("HotkeylessAHK by sebinside.");
Expand Down
29 changes: 21 additions & 8 deletions files/server.js → files/dist/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use strict";
exports.__esModule = true;
exports.HotkeylessAHKServer = void 0;
var express = require("express");
var HotkeylessAHKServer = /** @class */ (function () {
function HotkeylessAHKServer(serverPort) {
Expand All @@ -9,46 +10,58 @@ var HotkeylessAHKServer = /** @class */ (function () {
this.app = express();
this.router = express.Router();
this.pendingResult = null;
this.list = "";
/**
* Handles the subscriber aka. redirecting ahk script
*/
this.subscriberFunction = function (req, res) {
this.subscribe = function (req, res) {
_this.pendingResult = res;
console.log("Received subscriber.");
};
/**
* Handles the sender aka the caller e.g. a stream deck
*/
this.sendFunction = function (req, res) {
this.send = function (req, res) {
if (_this.pendingResult !== null) {
var command = req.params.command;
_this.pendingResult.send(command);
_this.pendingResult = null;
res.send("success");
console.log("Send command: " + command);
console.log("Send command: ".concat(command));
}
else {
console.error("No subscribing process registered. Please call '/subscribe' first!");
res.send("failure");
}
};
this.register = function (req, res) {
var list = req.params.list;
// This is required due to the last comma added in the ahk code
_this.list = list.substring(0, list.length - 1);
res.send("success");
};
this.getList = function (req, res) {
res.send(_this.list);
};
/**
* Stops the node process
*/
this.killFunction = function (req, res) {
this.kill = function (req, res) {
console.log("Shutting down server...");
process.exit(0);
};
}
HotkeylessAHKServer.prototype.setup = function () {
console.log("Starting server...");
this.router.get("/subscribe", this.subscriberFunction);
this.router.get("/send/:command", this.sendFunction);
this.router.get("/kill", this.killFunction);
this.router.get("/subscribe", this.subscribe);
this.router.get("/send/:command", this.send);
this.router.get("/kill", this.kill);
this.router.get("/register/:list", this.register);
this.router.get("/list", this.getList);
// Start server
this.app.use('/', this.router);
this.app.listen(this.serverPort);
console.log("Server running on port " + this.serverPort + ".");
console.log("Server running on port ".concat(this.serverPort, "."));
console.log("Please use the '/subscribe' endpoint first!");
};
return HotkeylessAHKServer;
Expand Down
41 changes: 34 additions & 7 deletions files/lib.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ SetupServer() {
WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr")

; Starts the server using node js
Run node ""files/index.js""
Run node ""files/dist/index.js""
}

RunClient() {
shell := ComObjCreate("WScript.Shell")
server := "curl http://localhost:42800/subscribe -m 25"

allFunctions := GetAvailableFunctions()
sendListToServer := "curl http://localhost:42800/register/" . allFunctions
shell.Exec(ComSpec " /C " sendListToServer)

; Go in subscriber mode and wait for commands.
; You can trigger these commands by calling "localhost:42800/send/commandNameGoesHere"
Loop {
Expand All @@ -22,12 +26,35 @@ RunClient() {
Run curl ""http://localhost:42800/kill""
Exit
} else {
; Calls a custom defined function in any included script.
; Does ignore wrong calls (not defined functions).
fn := Func(command)
if(fn != 0) {
%fn%()
}
CallCustomFunctionByName(command)
}
}
}

CallCustomFunctionByName(functionName) {
CustomFunctionsInstance := New CustomFunctions
if(IsFunctionAvailable(functionName)) {
CustomFunctionsInstance[functionName]()
}
}

IsFunctionAvailable(functionName) {
CustomFunctionsFunctionName := "CustomFunctions." . functionName
fn := Func(CustomFunctionsFunctionName)
return (fn != 0)
}

GetAvailableFunctions() {
CustomFunctionsInstance := New CustomFunctions
For key,value in CustomFunctionsInstance.Base
if((key != "__Class") && (GetFunctionParameterCount(key) <= 1)) {
BaseMembers .= key ","
}
return %BaseMembers%
}

GetFunctionParameterCount(functionName) {
CustomFunctionsFunctionName := "CustomFunctions." . functionName
fn := Func(CustomFunctionsFunctionName)
return fn.MinParams
}
30 changes: 23 additions & 7 deletions files/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as express from "express";
import {RequestHandler, Response} from "express";
import { RequestHandler, Response } from "express";

export class HotkeylessAHKServer {

Expand All @@ -8,19 +8,20 @@ export class HotkeylessAHKServer {
private router = express.Router();

private pendingResult: Response = null;
private list = "";

/**
* Handles the subscriber aka. redirecting ahk script
*/
private subscriberFunction: RequestHandler = (req, res) => {
private subscribe: RequestHandler = (req, res) => {
this.pendingResult = res;
console.log("Received subscriber.");
};

/**
* Handles the sender aka the caller e.g. a stream deck
*/
private sendFunction: RequestHandler = (req, res) => {
private send: RequestHandler = (req, res) => {
if (this.pendingResult !== null) {
const command = req.params.command;
this.pendingResult.send(command);
Expand All @@ -33,10 +34,23 @@ export class HotkeylessAHKServer {
}
};

private register: RequestHandler = (req, res) => {
const list = req.params.list as String;

// This is required due to the last comma added in the ahk code
this.list = list.substring(0, list.length - 1);
res.send("success");
};

private getList: RequestHandler = (req, res) => {
res.send(this.list);
}


/**
* Stops the node process
*/
private killFunction: RequestHandler = (req, res) => {
private kill: RequestHandler = (req, res) => {
console.log("Shutting down server...");
process.exit(0);
};
Expand All @@ -47,11 +61,13 @@ export class HotkeylessAHKServer {
setup() {
console.log("Starting server...");

this.router.get("/subscribe", this.subscriberFunction);
this.router.get("/subscribe", this.subscribe);
this.router.get("/send/:command", this.send);

this.router.get("/send/:command", this.sendFunction);
this.router.get("/kill", this.kill);

this.router.get("/kill", this.killFunction);
this.router.get("/register/:list", this.register)
this.router.get("/list", this.getList)

// Start server
this.app.use('/', this.router);
Expand Down
5 changes: 5 additions & 0 deletions files/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"outDir": "dist"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit bff41be

Please sign in to comment.