Introduction¶
Overview¶
Talon aims to bring programming, realtime video gaming, command line, and full desktop computer proficiency to people who have limited or no use of their hands, and vastly improve productivity and wow-factor of anyone who can use a computer.
Join the Slack to talk, get hyped, or for help with Talon.
NOTE: This Talon release is very new and is not fully documented yet! Please ask any questions in the #help channel on the Slack linked above.
- System requirements:
macOS High Sierra (10.13) or newer. Talon is a universal2 build with native Apple Silicon support.
Linux / X11 (Ubuntu 18.04+, and most modern distros), Wayland support is currently limited to XWayland
Windows 8 or newer
Powerful voice control - Talon comes with a free speech recognition engine, and it is also compatible with Dragon with no additional setup.
Multiple algorithms for eye tracking mouse control (depends on a single Tobii 4C, Tobii 5 or equivalent eye tracker)
Noise recognition system (pop and hiss). Many more noises coming soon.
Scriptable with Python 3 (via embedded CPython, no need to install or configure Python on your host system).
Talon is very modular and adaptable - you can use eye tracking without speech recognition, or vice versa.
Getting Started¶
Download and install Talon for your operating system.
Run the Talon app.
Open the Talon Home directory. This is
%APPDATA%\Talon
on Windows, and~/.talon
on macOS/Linux. (Talon has a menu in your system tray near the clock, you can useScripting -> Open ~/.talon
as a shortcut open Talon Home).Add some scripts to
~/.talon/user
to add voice commands and other behaviour to Talon (see the Getting Scripts section below). Your user scripts control all of the voice commands in Talon, so Talon won’t recognize any commands until you add some scripts.Install Conformer using Talon’s Speech Recognition menu.
Go to
Scripting -> View Log
in the menu for debug output, orScripting -> Open REPL
for a Python command line.
Getting Scripts¶
The best way to get started right now is to clone talonhub/community into your ~/.talon/user
directory. Ask in #help on Slack if you run into any problems.
Learning Talon¶
These are some resources to help you learn to use and customize Talon:
.talon Files¶
Overview¶
Voice commands are defined in files with the .talon
file extension, located in the user
directory inside Talon Home.
.talon
files can:
match specific applications, window titles, or other criteria
define voice commands
define global hotkeys
reimplement global actions (for example, to change the behavior of Talon in a specific application)
set settings
enable tags
Creating a file in user
named hello.talon
with these contents will declare a voice command:
hello talon: "hello world"
This means when you say hello talon
, Talon will type hello world
.
This is a more advanced example:
# activate this .talon file if the current app name is "Chrome"
# you can find app names by running ui.apps() in the REPL
app.name: Chrome
-
# key_wait increases the delay when pressing keys (milliseconds)
# this is useful if an app seems to jumble or drop keys
settings():
key_wait = 4.0
# activate the global tag "browser"
tag(): browser
# define some voice commands
hello chrome: "hello world"
switch tab: key(ctrl-tab)
go to google:
# note: use key(cmd-t) on Mac
key(ctrl-t)
insert("google.com")
key(enter)
The -
on the line after after app.name
is important.
Lines above the
-
are used to set criteria for activating the file.Lines below the
-
declare things or activate tags.
Any line beginning with #
is considered to be a comment and ignored.
API Reference¶
talon¶
- class talon.Context(name: str = None, *, desc: str = None)¶
Creating a Context:
from talon import Context ctx = Context()
- action_class(path: str) Callable[[Class], ActionClassProxy] ¶
@ctx.action_class('prefix') class Actions: def action_name(): print("Running actions.prefix.action_name()")
- action(path: str)¶
@ctx.action('prefix.action_name') def action_name(): print("Running actions.prefix.action_name()")
- capture(path: str = None, *, rule: str = None) Callable[[DecoratedT], DecoratedT] ¶
@ctx.capture('number', rule='(one | two)') def number(m) -> int: return 1 if m[0] == 'one' else 2
- dynamic_list(path: str) Callable[[DecoratedT], DecoratedT] ¶
@ctx.dynamic_list('prefix.list_name') def list_name() -> Union[str, list[str], dict[str, str]]: return ['word', 'word']
- property matches: str¶
Describe when to activate this Context. If not specified, Context is always active.
ctx.matches = r""" os: windows app.name: Slack """
- Type:
str
- property apps¶
apps:
ctx.apps["chrome"] = r""" os: windows app.name: Google Chrome """
- Type:
str
- property selections: dict[str, str]¶
selections:
ctx.selections["user.listname"] = """ some text to use for a subset selection """
- Type:
dict[str, str]
- property lists: dict[str, Mapping[str, str]]¶
lists:
ctx.lists["user.listname"] = ["word", "word2"] ctx.lists["user.listname"] = { "pronunciation": "word", }
- Type:
dict[str, Union[list[str], dict[str, str]]]
- property settings¶
settings:
ctx.settings = { "input_wait": 1.0, }
- Type:
dict[str, Any]
- property tags¶
tags:
ctx.tags = ["user.terminal"]
- Type:
frozenset[str]
- property commands: Mapping[str, CommandImpl]¶
Return the commands defined by this Context
- Type:
dict[str, CommandImpl]
- property hotkeys: Mapping[str, ScriptImpl]¶
Return the hotkeys defined by this Context
- Type:
dict[str, ScriptImpl]
- class talon.Module(name: str = None, *, desc: str = None)¶
Creating a Module:
from talon import Module mod = Module()
- action_class(cls: Class) ActionClassProxy ¶
@mod.action_class class Actions: def action_name(): "Description of the action" def second_action(arg1: int, arg2: str='') -> str: "Action with arguments, return type, and body" return 'test'
- action(func: DecoratedT) ActionDecl[DecoratedT] ¶
@mod.action def action_name(): "Description of the action"
- capture(*, rule: str) Callable[[DecoratedT], DecoratedT] ¶
- capture(func: DecoratedT) DecoratedT
@mod.capture def capture_name() -> str: "Description of the capture"
- scope(func: ScopeFunc) ScopeDecl ¶
@mod.scope def scope(): return { "key": "value", } # call scope.update() at any time to force a scope update
- setting(name: str, type: ~typing.Type[~talon.scripting.types.T], default: ~talon.scripting.types.T | ~talon.scripting.types.SettingDecl.NoValueType = <talon.scripting.types.SettingDecl.NoValueType object>, desc: str = None) SettingDecl[T] ¶
mod.setting("setting_name", int, default=0, desc="an example integer setting") mod.setting("setting_name_2", str, default='', desc="an example string setting")
- list(name: str, desc: str = None) NameDecl ¶
mod.list("list_name", desc="list description")
- mode(name: str, desc: str = None) NameDecl ¶
mod.mode("mode_name", desc="mode description")
- tag(name: str, desc: str = None) NameDecl ¶
mod.tag("tag_name", desc="tag description")
- talon.actions¶
…
- talon.registry¶
…
- talon.scope¶
…
- talon.settings¶
…
- talon.storage¶
…
talon.app¶
- talon.app.register(topic: str, cb: Callable) None ¶
Register for an application event.
ready
: Talon is ready. Your callback will be called after Talon launch and during script reloads.launch
: Talon launched. Your callback will only be called immediately after Talon launch.startup
: Talon launched during system startup.
from talon import app def app_ready(): print("Talon is ready") app.register("ready", app_ready)
- talon.app.unregister(topic: Any, cb: Callable) None ¶
Unregister a previously registered event:
app.unregister("ready", app_ready)
- talon.app.notify(title: str = None, subtitle: str = None, body: str = None, sound: bool = False) None ¶
Display a desktop notification, optionally playing a sound.
from talon import app app.notify(body="Hello world") app.notify(title="Hello world", subtitle="Welcome to Talon", body="Enjoy your stay.", sound=True)
talon.clip¶
- talon.clip.has_mode(mode: str) bool ¶
Check if a clipboard mode is supported.
Useful modes: “main”, “select”, “find”
- talon.clip.text(*, mode: str = None) str | None ¶
Get the text contents of the clipboard.
- talon.clip.set_text(s: str, *, mode: str = None) None ¶
Set the text contents of the clipboard.
- talon.clip.image(*, mode: str = None) Image | None ¶
Get the image contents of the clipboard.
- talon.clip.set_image(image: Image, *, mode: str = None) None ¶
Set the image contents of the clipboard.
- talon.clip.clear(*, mode: str = None) None ¶
Clear the clipboard.
- exception talon.clip.NoChange¶
- talon.clip.revert(*, old: PyMimeData = None, mode: str = None) Generator[None, None, None] ¶
Restore the old contents of the clipboard after running a block:
from talon import clip with clip.revert(): clip.set_text("this will only be set temporarily")
- talon.clip.capture(timeout: float = 0.5, *, inc: int = 0, mode: str = None, formats: list[str] = None) Generator[ChangePromise, None, None] ¶
Capture a change in the clipboard, then restore the old text contents:
from talon import actions, clip with clip.capture() as s: actions.edit.copy() print(s.get())
talon.fs¶
from talon import fs
def on_change(path, flags):
if flags.renamed:
print("renamed", path)
if flags.exists:
print("changed", path)
else:
print("deleted", path)
fs.watch('/path/to/stuff', on_change)
- class talon.fs.FsEventFlags(exists: bool, renamed: bool, stat: os.stat_result | None)¶
- talon.fs.watch(path: str, cb: Callable[[str, FsEventFlags], None]) None ¶
Watch path for changes and call
cb(path: str, flags: FsEventFlags)
when changes occur.
- talon.fs.unwatch(path: str, cb: Callable[[str, FsEventFlags], None]) None ¶
Remove cb from the set of callbacks being watched for path.
talon.noise¶
- talon.noise.register(topic: Any, cb: Callable) None ¶
Register for a noise event.
""
- an empty string registers the callback for all noises."pop"
"hiss"
from talon import noise def on_pop(active): print("pop") noise.register("pop", on_pop) def on_hiss(active): print("hiss", active) noise.register("hiss", on_hiss)
- talon.noise.unregister(topic: Any, cb: Callable) None ¶
Unregister a previously registered event:
noise.unregister("pop", on_pop)