Automatisations

L’objet automation facultatif d’un fichier .clinkplugin déclare événements, état, réglages, commandes, préréglages et emplacements de raccourcis. Chaque contribution possède un identifiant stable, une version de schéma exacte et des titres localisés. L’hôte crée les noms plugin/<plugin-id>/<kind>/<id>. Un plugin ne peut pas utiliser l’espace de noms du système ou d’un autre plugin.

Automatisations ›

Comment fonctionnent les plugins

Un panneau remplace les touches, et une action s'exécute une fois sur un texte. Un plugin n'a pas d'écran à lui sur le clavier. Clink appelle ses fonctions à des moments précis, par exemple quand le clavier s'ouvre ou que vous terminez un mot, et le plugin peut modifier la barre d'espace ou son propre état enregistré. Ses réglages sont dessinés dans l'app avec les mêmes constructeurs que ceux des panneaux. WPM Spacebar, plus bas, est un plugin complet.

def initial():
    return {"on": False}

def settings(state):
    return section("keys.spacebar", [
        toggle("Show typing speed", state["on"], action="toggle"),
    ], title="WPM Spacebar")

def on_action(action, value, state):
    state["on"] = value
    if value:
        claim("spacebar.text")
    else:
        release("spacebar.text")
        space_text(None)
    return state

def on_word(word, state):
    if state["on"]:
        space_text(f"{stats()['wpm']} wpm")
    return state
WPM Spacebar ajoute un interrupteur sous Touches > Barre d'espace. Tant qu'il est activé, la barre d'espace affiche votre vitesse de frappe actuelle.

Les plugins nécessitent Clink Pro. Avant d'en installer un depuis un dépôt, Clink vous demande d'autoriser le code de ce dépôt, comme pour les panneaux et les actions.

on_key et on_word reçoivent ce que vous tapez, c'est ce qui permet de compter les mots. PyMini n'a accès ni au réseau ni aux fichiers, donc un plugin ne peut pas envoyer ce que vous tapez sur Internet, et ce qu'il enregistre reste dans Clink, sur votre appareil.

Votre premier plugin

Le plus rapide est le script de départ que l'app écrit pour vous. Il place un interrupteur sous Touches > Barre d'espace et, tant qu'il est actif, compte les mots sur la barre d'espace. Cinq étapes, rien à télécharger.

  1. Ouvrez l'onglet Plugins, activez les plugins en haut, puis touchez + pour un nouveau plugin.
  2. L'éditeur s'ouvre sur le script de départ. Lisez-le une fois : initial(), settings(), on_action(), on_open() et on_word(), c'est tout.
  3. Passez à l'aperçu. Basculez l'interrupteur, puis touchez on_open et on_word plusieurs fois et regardez la fausse barre d'espace et la console.
  4. Enregistrez. Le plugin est actif dans la liste, et son interrupteur se trouve maintenant aussi sous Touches > Barre d'espace.
  5. Ouvrez le clavier n'importe où et tapez. La barre d'espace compte avec vous.
def initial():
    return {"on": False, "words": 0}

def settings(state):
    return section("keys.spacebar", [
        toggle("Count words on the space bar", state["on"], action="toggle"),
    ], title="Word count")

def on_action(action, value, state):
    if action == "toggle":
        state["on"] = value
        if value:
            claim("spacebar.text")
        else:
            release("spacebar.text")
            space_text(None)
    return state

def on_open(state):
    state["words"] = 0
    if state["on"]:
        space_text("0 words")
    return state

def on_word(word, state):
    state["words"] += 1
    if state["on"]:
        space_text(f"{state['words']} words")
    return state
Le script de départ, tel que l'app l'écrit.

Hooks

Définissez celles de ces 33 fonctions dont vous avez besoin et laissez de côté les autres. Chacune reçoit state en dernier argument. Celles qui réagissent à quelque chose renvoient state, modifié ou non. Celles qui répondent à une question (elements, draw, effects, les hooks d’apparence, haptics, hitboxes, suggestions et correct) renvoient leur réponse, et peuvent quand même modifier state sur place.

HookQuand il s’exécute
on_automation(command, args, state)on_automation(command, args, state) exécute une commande locale approuvée dans le bac à sable existant. Ce hook peut actualiser le texte de la barre d’espace, publier un état déclaré ou émettre un événement déclaré. Il ne peut pas insérer de texte, accéder au presse-papiers, modifier des réglages persistants ou envoyer des requêtes externes.
initial()Une seule fois, avant qu'il existe un état enregistré. Renvoyez un dict avec tout ce que JSON peut stocker.
settings(state)Quand les réglages du plugin sont affichés dans l'app. Renvoyez ses contrôles sous forme d'arbre de nœuds. Ne s'exécute jamais dans le clavier.
on_action(action, value, state)Quand l'un des contrôles du plugin est utilisé. on_action(action, state), sans value, fonctionne aussi.
on_open(state)Quand le clavier apparaît. Un bon endroit pour lire stats() ou régler la barre d'espace.
on_close(state)Quand le clavier est fermé. L'état est enregistré juste après.
on_key(key, state)Chaque fois qu'une touche saisit quelque chose. Cela s'exécute à chaque frappe, alors faites court.
on_word(word, state)Quand un mot est terminé, que ce soit par une espace, une suggestion ou un glissé.
on_backspace(state)Quand la touche d'effacement est pressée. Ne reçoit pas de texte, seulement l'état.
on_suggestion(word, state)Quand une suggestion est touchée. word est celle qui a été touchée.
on_language(code, state)Quand la langue de frappe change. code est la nouvelle, comme en ou de.
on_field(kind, state)Quand le clavier se connecte à un champ. kind vaut default, email, url, number, phone, password ou search.
on_tick(state)Une fois par seconde tant que le clavier est affiché, que vous tapiez ou non. Le hook pour tout ce qui doit changer tout seul : une horloge, un compte à rebours, une vitesse qui doit retomber à zéro quand vous vous arrêtez.
on_swipe(direction, state)Un geste qui commence sur une touche de lettre : "left", "right", "up", "up_left" ou "up_right". Il n'est lu que lorsque la saisie par glissement est désactivée. Si le hook ne demande rien, le geste reste une frappe ordinaire ; s'il demande quelque chose, la lettre où il a commencé est d'abord retirée.
elements(state)Ce que ce plugin propose à une disposition personnalisée. Renvoyez des entrées element(id, name, icon=, width=), ou omettez le hook.
draw(id, state)Le visage d'un élément, sous forme d'arbre de nœuds. S'exécute environ une fois par seconde tant que le clavier est affiché.
effects(state)Les effets lumineux que ce plugin propose à la page Effets. Renvoyez des entrées light_effect(...), ou omettez le hook.
key_styles(state)Des styles de touches pour l’éditeur de thème. Renvoyez des entrées key_style(...) ; voir Apparences plus bas.
themes(state)Des thèmes complets pour l’onglet Plugins de l’éditeur de thème. Renvoyez des entrées theme(...).
popups(state)Des styles de fenêtre contextuelle de touche. Renvoyez des entrées popup_style(...).
animations(state)Des animations : n’importe quel mélange de entrance(...), press_animation(...), letter_animation(...) et transition(...).
backgrounds(state)Des arrière-plans animés. Renvoyez des entrées background(...) faites de calques particles(...).
layouts(state)Des dispositions de clavier. Renvoyez des entrées layout(...).
trails(state)Traînées de glissement que ce plugin propose au sélecteur de traînées. Renvoyez des entrées trail(...) ; voir Apparences plus bas.
haptics(state)Un retour haptique par touche, sous forme de dict des noms de touches vers des ressentis. Lu à l’ouverture du clavier puis environ une fois par seconde.
hitboxes(state)Une zone de touche par touche, sous forme de dict des noms de touches vers hitbox(...). Lu au même rythme que haptics.
on_touch(key, x, y, state)Après chaque appui : la touche qui l’a reçu, et l’endroit de cette touche où le doigt s’est posé. x et y vont de -0.5 à 0.5, avec 0 au centre.
suggestions(word, state)Des mots pour la barre de suggestions pendant la saisie de word. S’exécute chaque fois que la barre se stabilise, pas à chaque touche.
correct(word, fix, state)L’espace vient de terminer word. Renvoyez un mot à insérer à la place, False pour le garder tel que tapé, ou None pour laisser la correction du clavier.
bar_items(state)Boutons et molettes que ce plugin propose à la barre supérieure. Renvoyez des entrées bar_button(...) et bar_knob(...), ou omettez le hook.
key_art(state)Le dessin à peindre sur les touches, sous forme de dict des noms de touches vers des formes. Relu après chaque événement entendu par le plugin, ainsi un état modifié dans un hook se voit sur les touches.
events(state)Les noms d’événements que on_event veut entendre, lus une fois au chargement du plugin. Sans ce hook, le plugin entend tout sauf les événements fréquents.
on_event(name, info, state)Un seul hook pour tout ce qui arrive : open, close, word, backspace, suggestion, language, field, shift et plane, plus les fréquents key, key_down, key_up, predictions et tick, qu’il faut demander par leur nom dans events(state).
def on_key(key, state):
    if key == " " and setting("language") == "en":
        state["spaces"] = state.get("spaces", 0) + 1
    return state

def on_close(state):
    haptic("light")
    return state
Deux hooks, une lecture et une commande de panneau.

L'app et le clavier partagent une seule copie de l'état. Activez un interrupteur dans l'app et il sera actif à la prochaine ouverture du clavier. Ce que le clavier compte est là la prochaine fois que vous ouvrez les réglages du plugin. Le clavier enregistre l'état à sa fermeture, pas après chaque touche.

Suggestion actuelle sur la barre d’espace

Lisez la suggestion principale affichée avec context()["suggestion"]. Abonnez-vous à l’événement predictions pour recevoir les changements dans info["suggestion"]. Ces deux opérations nécessitent l’accès aux données de saisie. on_suggestion s’exécute après l’acceptation d’une suggestion, pas lorsque les prédictions changent. Appelez space_text(value or None) pour définir le libellé ou le rétablir lorsqu’il n’y a plus de suggestions. L’action de la barre d’espace ne change pas.

def initial():
    return {}

def events(state):
    return ["predictions"]

def on_open(state):
    space_text(context()["suggestion"] or None)
    return state

def on_event(name, info, state):
    if name == "predictions":
        space_text(info["suggestion"] or None)
    return state

def on_close(state):
    space_text(None)
    return state

État

L'état est un seul dict. initial() le construit la première fois ; ensuite chaque hook reçoit le même dict, le modifie et le rend. Il contient tout ce que JSON peut contenir : nombres, chaînes, listes, dicts imbriqués. L'app l'enregistre après chaque commande que vous utilisez, le clavier à sa fermeture, et les deux lisent le même fichier, donc ils ne divergent jamais longtemps.

def initial():
    return {"session": 0, "total": 0, "longest": ""}

def on_open(state):
    state["session"] = 0        # starts over each time the keyboard opens
    return state

def on_word(word, state):
    state["session"] += 1
    state["total"] += 1         # survives, it is saved when the keyboard closes
    if len(word) > len(state["longest"]):
        state["longest"] = word
    return state

def settings(state):
    return vstack([
        text(f"{state['total']:,} words so far"),
        text(f"Longest: {state['longest'] or '...'}", size=13, color="gray"),
        button("Start over", "reset", style="destructive"),
    ])

def on_action(action, value, state):
    if action == "reset":
        return initial()
    return state
Un compteur par session que on_open remet à zéro, et un total qui reste.

Renvoyer l'état est une bonne habitude, mais ce n'est pas strictement obligatoire : le dict est une référence, donc le modifier sur place marche aussi. Renvoyez un autre dict, comme initial() ci-dessus, et il devient l'état.

Réglages et sections

settings(state) renvoie un arbre de nœuds construit avec les constructeurs de panneau : text, toggle, slider, stepper, segmented, button, row, field et les dispositions. Il apparaît sur la page du plugin dans l'onglet Plugins. Enveloppez-en une partie dans section(anchor, children, title) et cette partie apparaît aussi sur l'un des écrans de réglages de Clink, à côté du réglage qu'elle concerne.

def settings(state):
    return vstack([
        text("Counts words as you type.", size=13),
        section("keys.spacebar", [
            toggle("Count on the space bar", state["on"], action="toggle"),
            stepper(state["goal"], min=10, max=500, step=10, label="Goal", key="goal"),
        ], title="Word count"),
    ])
L'interrupteur et le stepper apparaissent sous Touches > Barre d'espace. La légende n'apparaît que sur la page du plugin.

Les commandes dont un plugin a le plus souvent besoin. Chacune écrit sa nouvelle valeur dans l'état sous key, ou nomme une action pour on_action, ou les deux. La liste complète des constructeurs, dispositions comprises, est sur la page des panneaux.

ConstructeurCe qu’il dessine
toggle(label, on=False, key="", action="")Un interrupteur. key écrit True ou False dans cette clé d'état.
slider(value, min=0, max=100, step=1, label="", key="", action="")Un curseur entre min et max. key écrit la position dans cette clé d'état.
stepper(value, min=0, max=100, step=1, label="", key="", action="")Une valeur avec − et + à côté, entre min et max.
segmented(options, value=None, key="", action="")Un choix dans une liste. key écrit l'option retenue dans cette clé d'état.
field(key, placeholder="", action="", submit="")Une zone de texte liée à state[key]. En la touchant, les touches écrivent dans cette clé ; submit nomme le gestionnaire que déclenche Entrée.
button(label, action="", value=None, insert="", set=None, style="plain", icon="", enabled=True)Un bouton. insert écrit son texte dans ce que vous êtes en train d'écrire, set fusionne des clés dans l'état, et action nomme un gestionnaire pour on_action. style accepte plain, primary, tinted, quiet ou destructive.
row(title, subtitle="", detail="", icon="", action="", value=None, insert="")Une ligne que l'on touche : un titre, une deuxième ligne, une icône et un détail à droite.
text(s, size=17, weight="regular", color="", align="leading", lines=0, mono=False)Une ligne de texte. weight accepte regular, medium, semibold, bold, heavy, light ou thin ; align accepte leading, center ou trailing ; lines limite le nombre de lignes ; color accepte un nom de couleur ou #RRGGBB.
Panneaux ›

Où vont les sections

Une ancre est l'identifiant d'une carte sur l'un des écrans de réglages de Clink. Nommez-la dans section() et les commandes du plugin sont dessinées juste sous cette carte, avec le nom du plugin au-dessus. Le plus simple pour en trouver une : dans l'app, ouvrez Plus > Développeur et activez Afficher les identifiants. Chaque carte reçoit un petit badge d'information qui donne son identifiant et le copie d'une touche ; les pages affichent le leur dans la barre de titre.

Toutes les ancres qu'une section peut nommer

  • analytics.heatmap
  • analytics.privacy
  • analytics.trends
  • analytics.typing-test
  • automations.rules
  • gestures.accents
  • gestures.cursor
  • gestures.delete
  • gestures.general
  • gestures.suggestions
  • gestures.swipe
  • haptics.feel
  • keys.adaptive
  • keys.faces
  • keys.hitboxes
  • keys.hitmap
  • keys.long-press
  • keys.numberrow
  • keys.onehanded
  • keys.roundness
  • keys.size
  • keys.spacebar
  • keys.spacing
  • keys.split
  • languages.app
  • languages.custom
  • languages.manage
  • languages.packs
  • languages.switch
  • languages.typing
  • layout.arrange
  • layout.arrangement
  • layout.build
  • layout.longpress
  • layout.presets
  • layout.topbar
  • motion.delete
  • motion.entrance
  • motion.glow
  • motion.key-press
  • motion.key-response
  • motion.letters
  • motion.space-response
  • motion.transition
  • popups.style
  • sound.keysounds
  • text.automation
  • text.content
  • text.corrections
  • text.history
  • text.punctuation
  • text.speed
  • text.suggestions
  • text.symbols
  • themes.background
  • themes.canvas
  • themes.theme

keys.spacebar a une place à elle, sur l'écran de la barre d'espace lui-même. Une section dont l'ancre n'est pas dans cette liste s'affiche quand même sur la page du plugin ; une faute de frappe vous coûte donc une carte, pas le plugin.

Prises de contrôle

Quand un plugin pilote un réglage de Clink, la personne doit le voir, et les deux ne doivent pas se battre. claim(control) déclare que le plugin en est propriétaire : l'app nomme le plugin sur la carte de ce réglage, et le champ du texte de la barre d'espace est verrouillé tant qu'il le détient. release(control) le rend. Prenez-le dans le on_action qui active votre fonction, rendez-le dans celui qui la désactive, et remettez la valeur à None ou à son ancienne valeur en même temps. Un plugin désactivé dans la liste libère tout ce qu'il tenait.

def on_action(action, value, state):
    if action == "toggle":
        state["on"] = value
        if value:
            claim("spacebar.text")
            space_text("...")
        else:
            release("spacebar.text")
            space_text(None)      # hand the person's own text back
    return state
La paire que possède tout plugin qui prend le contrôle : prendre à l'activation, rendre à la désactivation.

Deux plugins peuvent prendre la même commande ; l'app nomme celui qui l'a prise en dernier. Les prises de contrôle sont enregistrées par l'app, donc prenez-les depuis on_action, qui s'exécute là. Une prise faite dans le clavier n'est pas mémorisée.

Commandes et lectures

Les plugins peuvent utiliser toutes les commandes de panneau, plus six commandes et deux lectures qui leur sont propres. Les commandes sont mises en file et appliquées après le retour de votre fonction, pour que rien ne change sur le clavier au milieu d'un appel. Les lectures renvoient les valeurs telles qu'elles étaient juste avant l'appel.

AppelCe qu’il fait
space_text(text)Affiche une légende sur la barre d'espace, jusqu'à 32 caractères. Passez None pour revenir au texte de barre d'espace choisi par la personne.
space_language_text(text)Remplace le badge de langue dans le coin de la barre d’espace, à lui seul et même lorsque le badge natif est désactivé. Une ligne, jusqu’à 12 caractères ; "" le cache et None rend le texte natif.
space_language_flag(language)Met un drapeau sur ce badge pour un identifiant de langue comme en_GB, tiré des drapeaux fournis. None rend le comportement natif.
space_language_emoji(language)Le même badge sous forme d’emoji de drapeau de la région. Les trois commandes de badge se partagent une seule place : activer un plugin de badge désactive les autres.
set_setting(name, value)Modifie un réglage de Clink par son nom, avec une valeur du bon type : un booléen, un nombre dans sa plage, l'un de ses choix ou du texte. Un nom inconnu lève KeyError. Une valeur du mauvais type ou hors plage n'est pas appliquée, et la console de l'éditeur indique ce que le réglage attend.
claim(control)Prend en charge un des réglages de Clink. Sa carte dans l'app indique le plugin qui le détient.
release(control)Rend le réglage. Désactiver un plugin libère tout ce qu'il avait pris en charge.
suggest(words)Place jusqu'à dix mots à vous en tête de la barre de suggestions. Ils restent jusqu'à ce qu'on en touche un, qu'on appuie sur effacer ou que le champ change, et suggest([]) les retire plus tôt. En toucher un le tape.
banner(text)Affiche brièvement un court message sur le clavier. Pour un signal, pas pour une conversation.
press(key)Déclenche l'une des touches du clavier comme si elle avait été touchée : "space" ou "delete". Tout autre nom lève ValueError.
pick_suggestion(slot)Prend la suggestion située dans la partie "left", "center" ou "right" de la barre, comme si on la touchait. Pour une barre défilée, c'est ce qui est à l'écran qui compte. Dans on_swipe, le choix se fait dans la barre telle qu'elle était quand le doigt s'est posé.
stats()Renvoie un dict avec wpm, peak_wpm, keystrokes, words et streak. wpm est mis à jour en direct tant que les plugins sont activés. Les totaux viennent d'Analyse et ne sont plus mis à jour si Analyse est désactivé.
setting(name)Lit par son nom l'un des réglages listés ci-dessous. Tout autre nom lève une KeyError.
context()Le même instantané que lit un panneau, avec en plus suggestion, shift (off, on ou locked) et plane pour un plugin. C’est l’accès aux données de frappe qui remplit les clés du document ; shift reste lisible sans lui.
CléCe qu’il contient
stats()["wpm"]Mots par minute sur les dernières secondes, comptés à partir des caractères qui arrivent dans le champ. Le nombre apparaît une ou deux secondes après le début, baisse pendant une pause et atteint 0 à l'arrêt. Effacer n'y ajoute jamais rien.
stats()["peak_wpm"]La meilleure vitesse jamais enregistrée par les Statistiques.
stats()["keystrokes"]Touches pressées au total, telles que les Statistiques les comptent.
stats()["words"]Mots validés au total.
stats()["streak"]Jours d'affilée avec de la frappe, jusqu'à aujourd'hui.

setting(name) et set_setting(name, value) partagent une même liste de noms, et claim(control) accepte aussi n'importe lequel d'entre eux. Les booléens se lisent True ou False, les nombres comme des nombres, les choix par leur identifiant. La liste est longue à dessein : un plugin peut réagir à presque tout ce qu'une personne peut régler dans l'app, ou le piloter.

Noms acceptés par setting()

  • analytics
  • emoji.skin_tone
  • emoji.trailing_space
  • gestures.cursor
  • gestures.cursor_style
  • gestures.highlight_shift
  • gestures.plane_slide
  • gestures.predictive_flick
  • gestures.quick_accent
  • gestures.swipe
  • gestures.swipe_delete
  • gestures.swipe_multi_word
  • gestures.swipe_space_commit
  • gestures.swipe_two_thumb
  • gestures.trail
  • gestures.trail_style
  • gestures.trail_width
  • haptics.enabled
  • haptics.intensity
  • haptics.sharpness
  • keys.accents
  • keys.glyph_scale
  • keys.height
  • keys.long_press_hint
  • keys.popup_style
  • keys.popups
  • keys.radius
  • keys.row_spacing
  • keys.spacing
  • keys.uppercase
  • keys.width
  • language
  • language.bar_key
  • language.bar_key_style
  • language.mode
  • layout
  • layout.dismiss_shortcut
  • layout.number_row
  • layout.number_row_scale
  • layout.one_handed
  • layout.one_handed_shortcut
  • layout.one_handed_side
  • layout.one_handed_width
  • layout.split
  • layout.split_gap
  • layout.split_number_row
  • layout.split_shortcut
  • layout.split_space_bar
  • pet.corner
  • pet.enabled
  • pet.species
  • settings.accentHoldDelay
  • settings.accentMoveCancel
  • settings.activateWithIcon
  • settings.adaptiveGrow
  • settings.adaptiveHitboxes
  • settings.adaptivePredictAtWordStart
  • settings.adaptivePredictionWeight
  • settings.adaptiveShrink
  • settings.adaptiveSpace
  • settings.aiAutocorrect
  • settings.aiCompletions
  • settings.aiExtensionEnabled
  • settings.aiSearch
  • settings.aiToolsColorOverrides
  • settings.aiToolsDiffStyle
  • settings.aiToolsDisabled
  • settings.aiToolsLayoutStyle
  • settings.aiToolsOrder
  • settings.aiToolsPromptOverrides
  • settings.aiTranslate
  • settings.alternatingSplit
  • settings.arabicIndicNumerals
  • settings.backgroundEffectOverride
  • settings.clipboardCloseOnPaste
  • settings.clipboardDeleteOnPaste
  • settings.clipboardIgnoreImages
  • settings.clipboardIgnorePinsOnDelete
  • settings.clipboardStyle
  • settings.cursorActivationHaptic
  • settings.cursorLineStride
  • settings.cursorStepHaptic
  • settings.customPanelsStandalone
  • settings.customPetID
  • settings.deleteWordSwipeEngage
  • settings.deleteWordSwipeStride
  • settings.dictationAssist
  • settings.dictationAssistCustom
  • settings.dictationAssistLevel
  • settings.dictationColorScheme
  • settings.dictationGlassCapsule
  • settings.dictationSmartActions
  • settings.dictationStyle
  • settings.dictationVisualStyle
  • settings.dragUpThreshold
  • settings.emojiCategoryOrder
  • settings.emojiCellSpacing
  • settings.emojiColumnCount
  • settings.emojiCrossAxisSwitchesTab
  • settings.emojiCustomSets
  • settings.emojiGlyphScale
  • settings.emojiHiddenCategories
  • settings.emojiHiddenFromPanels
  • settings.emojiRecentsCap
  • settings.emojiRecentsSort
  • settings.emojiRememberCategory
  • settings.emojiRowCount
  • settings.emojiScrollDirection
  • settings.emojiSearchSlot
  • settings.emojiShowABCKey
  • settings.emojiShowBackspaceKey
  • settings.emojiStartCategoryID
  • settings.emojiTabBarSlot
  • settings.emojiTabIconStyle
  • settings.emojiToneHoldDelay
  • settings.extensionOrder
  • settings.extraTopBars
  • settings.formFreeCorners
  • settings.formLayoutEnabled
  • settings.gifShareAsLink
  • settings.glassPerRowMerge
  • settings.glassReleaseResponse
  • settings.gridSwitchAnimation
  • settings.gridSwitchDuration
  • settings.handwritingInkColor
  • settings.handwritingInkGlow
  • settings.handwritingInkStyle
  • settings.handwritingInkWidth
  • settings.hitboxScale
  • settings.iconPickerStyle
  • settings.keyBloomScale
  • settings.keyLighting
  • settings.keyPressGlow
  • settings.keyPressInstant
  • settings.keyPressLinger
  • settings.keySpringDamping
  • settings.keySpringResponse
  • settings.keyboardBottomPadding
  • settings.keyboardLanguages
  • settings.keyboardTopPadding
  • settings.longPressGlyphScale
  • settings.minPressVisible
  • settings.notepadBrowseStyle
  • settings.notepadMode
  • settings.numberRowFontSize
  • settings.numberRowLeadingKeys
  • settings.numberRowTrailingKeys
  • settings.oneHandedCustomKeys
  • settings.oneHandedExtraKeys
  • settings.panelButtonHitboxScale
  • settings.persistentLeadingKeys
  • settings.persistentTrailingKeys
  • settings.petIdleMotion
  • settings.petScale
  • settings.pinyinFuzzyEnabled
  • settings.pluginLooks
  • settings.popupSpringDamping
  • settings.popupSpringResponse
  • settings.predictiveFlickSuggestionPosition
  • settings.predictiveFlickSuggestionsSeparate
  • settings.reduceEffectsOnLowPower
  • settings.repeatAccelStep
  • settings.repeatHoldDelay
  • settings.repeatInitialInterval
  • settings.repeatMinInterval
  • settings.replacementsLayout
  • settings.rowInsets
  • settings.secondaryNeuralModelsEnabled
  • settings.separateActivation
  • settings.separateLanguageLayouts
  • settings.showCaptureOverlay
  • settings.showCorrectionField
  • settings.showHitboxOverlay
  • settings.showIconsBeforeTyping
  • settings.showRecentEmoji
  • settings.showTouchHeatmap
  • settings.showTouchSurfaceBounds
  • settings.showTouchTelemetryOverlay
  • settings.slideUpPickerStyle
  • settings.solidPopupOpacity
  • settings.soundVoice
  • settings.spaceBloomScale
  • settings.spaceCursorActivationDelay
  • settings.spaceCursorDragScale
  • settings.spaceCursorStride
  • settings.spaceLeanMultiplier
  • settings.spaceSpringDamping
  • settings.spaceSpringResponse
  • settings.spatialBiasEnabled
  • settings.spatialBiasGain
  • settings.splitIndices
  • settings.splitOtherPlanes
  • settings.stickerGridSnap
  • settings.stickerPlacements
  • settings.suggestionDebounceDelay
  • settings.suggestionHitboxScale
  • settings.suggestionSegmentLiquidGlass
  • settings.suggestionSegmentStyle
  • settings.suggestionSegmentsFollowTheme
  • settings.suggestionSeparatorColor
  • settings.suggestionSeparatorStyle
  • settings.suggestionTopPadding
  • settings.swipeKeyMorph
  • settings.swipeMorphRadius
  • settings.swipeMorphStrength
  • settings.swipeTrailEndWidth
  • settings.swipeTrailMaxLength
  • settings.swipeTrailStartWidth
  • settings.swipeTrailTrim
  • settings.toolsButtonStyle
  • settings.toolsHiddenFromPanels
  • settings.topBarItems
  • settings.topBarOrder
  • settings.translateLanguageOrder
  • settings.translateLanguagesDisabled
  • settings.translateStyle
  • settings.translateTone
  • settings.vietnameseInputMethod
  • sound.enabled
  • sound.pack
  • sound.volume
  • spacebar.corner
  • spacebar.language_code
  • spacebar.size
  • spacebar.text
  • stickers.enabled
  • text.arithmetic
  • text.auto_capitalize
  • text.auto_punctuation
  • text.autocorrect
  • text.autocorrect_everywhere
  • text.contacts
  • text.conversions
  • text.double_space_period
  • text.learning
  • text.punctuation_spacing
  • text.return_to_letters
  • text.revert_on_delete
  • text.smart_quotes
  • text.suggestions
  • text.suggestions_animation
  • text.suggestions_height
  • text.suggestions_scroll
  • text.suggestions_style
  • theme
  • theme.background
  • theme.dark
  • theme.delete_glyph
  • theme.entrance
  • theme.glyph_press
  • theme.light
  • theme.match_system
  • theme.press_style
  • theme.reactive_background
  • tools.calculator
  • tools.cling
  • tools.clipboard
  • tools.conversion
  • tools.dictation
  • tools.dictionary
  • tools.emoji
  • tools.gif
  • tools.handwriting
  • tools.layout_switcher
  • tools.notepad
  • tools.plugin_switcher
  • tools.profiles
  • tools.replacements
  • tools.textfx
  • tools.theme_switcher
  • tools.translate

Commandes

  • insert()
  • backspace()
  • delete_word()
  • replace()
  • move_cursor()
  • copy()
  • close()
  • haptic()
  • toast()

Comme le view() d'un panneau, settings() ne doit que décrire des contrôles. Les commandes appelées depuis cette fonction sont ignorées et notées dans la console.

Exemples

Cinq petits plugins, chacun complet. Collez-en un dans un nouveau plugin, enregistrez, et il tourne.

def initial():
    return {"on": True}

def settings(state):
    return section("keys.spacebar", [
        toggle("Language on the space bar", state["on"], action="toggle"),
    ], title="Language badge")

def on_action(action, value, state):
    if action != "toggle":
        return state
    state["on"] = value
    if value:
        claim("spacebar.text")
        space_text(setting("language").upper())
    else:
        release("spacebar.text")
        space_text(None)
    return state

def on_open(state):
    if state["on"]:
        space_text(setting("language").upper())
    return state

def on_language(code, state):
    if state["on"]:
        space_text(code.upper())
    return state
Badge de langue : la langue de frappe sur la barre d'espace, mise à jour dès qu'elle change.
SHORTCUTS = {"omw": "on my way", "brb": "be right back", "ty": "thank you"}

def on_key(key, state):
    if key != " ":
        return state
    words = context()["before"].split()
    if words and words[-1] in SHORTCUTS:
        # the space is already in the text, so it goes too and comes back after
        replace(len(words[-1]) + 1, SHORTCUTS[words[-1]] + " ")
    return state
Raccourcis : tapez omw et une espace, obtenez on my way. on_key voit l'espace après qu'elle a été tapée, donc le remplacement passe par-dessus.
def initial():
    return {"goal": 200, "count": 0}

def settings(state):
    return vstack([
        stepper(state["goal"], min=50, max=2000, step=50, label="Words per session", key="goal"),
        progress(state["count"], total=state["goal"], label=f"{state['count']} of {state['goal']}"),
    ])

def on_open(state):
    state["count"] = 0
    return state

def on_word(word, state):
    state["count"] += 1
    if state["count"] == state["goal"]:
        haptic("medium")
        banner("Goal reached")
    return state
Objectif de mots : un stepper sur la page du plugin, une barre de progression, et une vibration avec une bannière quand la session l'atteint.
def initial():
    return {"muted": False, "was_on": True}

def on_field(kind, state):
    # a password or a number field is not a place for key sounds
    quiet = kind in ("password", "number", "phone")
    if quiet and not state["muted"]:
        state["was_on"] = setting("sound.enabled")
        state["muted"] = True
        set_setting("sound.enabled", False)
    elif not quiet and state["muted"]:
        state["muted"] = False
        if state["was_on"]:
            set_setting("sound.enabled", True)
    return state
Champs silencieux : les sons de touches se coupent dans un champ mot de passe ou numérique et reviennent ensuite, via on_field, setting() et set_setting().
NAMES = ["sam", "ana", "team"]

def on_key(key, state):
    typing = context()["before"].split(" ")[-1]
    if typing.startswith("@"):
        start = typing[1:].lower()
        suggest([n for n in NAMES if n.startswith(start)])
        state["showing"] = True
    elif state.get("showing"):
        suggest([])                 # the mention is over, give the bar back
        state["showing"] = False
    return state
Mentions : taper @ place vos propres noms dans la barre de suggestions.
def on_swipe(direction, state):
    if direction == "left":
        delete_word()
    elif direction == "right":
        press("space")              # the space bar's own path, so it autocorrects
    elif direction == "up":
        pick_suggestion("center")
    elif direction == "up_left":
        pick_suggestion("left")
    elif direction == "up_right":
        pick_suggestion("right")
    return state
Des gestes à la Fleksy, le cœur du plugin Flick Gestures : vers la gauche efface un mot, vers la droite tape une espace, vers le haut choisit une suggestion. Les gestes n'arrivent que lorsque la saisie par glissement est désactivée.

Éléments dans une disposition

Une disposition personnalisée se construit avec des touches et des éléments : une bande d'emojis récents, une rangée de chiffres, un pavé de curseur. Un plugin peut ajouter les siens. elements(state) dit ce qu'il propose et draw(id, state) en dessine un, si bien qu'une touche peut montrer tout ce que le plugin sait calculer : une courbe de vitesse, une horloge, un compte à rebours, votre propre batterie.

Deux hooks en construisent un. elements(state) énumère ce que vous proposez, une entrée par élément, et l'app le lit pour remplir la palette de l'éditeur de disposition. draw(id, state) reçoit l'un de ces identifiants et renvoie quoi peindre. Un plugin peut en proposer plusieurs ; draw est appelé pour chacun, par son nom.

AppelCe qu’il fait
element(id, name, icon="", width=2)Un élément proposé. width se compte en largeurs de touche, comme dans une disposition, et icon est le SF Symbol affiché par l'éditeur dans sa palette.
sparkline(values, min=None, max=None, fill=False)Une ligne à travers une série de nombres, qui remplit l'espace qu'on lui donne. Sans min ni max, elle s'ajuste à ses valeurs. Seul un plugin peut en dessiner une, et elle est faite pour une touche.
POINTS = 30

def initial():
    return {"history": []}

def elements(state):
    return [element("sparkline", "WPM sparkline", icon="waveform.path.ecg", width=3)]

def on_tick(state):
    history = state["history"]
    history.append(stats()["wpm"])
    state["history"] = history[-POINTS:]
    return state

def draw(id, state):
    rate = state["history"][-1] if state["history"] else 0
    return hstack([
        sparkline(state["history"], min=0, max=max(60, max(state["history"] or [0])), fill=True),
        text(f"{rate}", size=12, weight="semibold"),
    ], spacing=5, align="center")
Le plugin WPM Sparkline, complet : il propose un élément et y dessine les trente dernières secondes de votre vitesse.

Un visage est un arbre de nœuds, comme une page de réglages, mais une touche n'est pas une page de réglages : seuls les nœuds qui dessinent servent, et tout ce qui se touche est ignoré. La touche appartient déjà à la disposition, il n'y a donc pas de place pour un bouton à l'intérieur.

CE QU'UN VISAGE PEUT DESSINER

  • sparkline
  • text
  • badge
  • icon
  • progress
  • hstack
  • vstack
  • spacer
  • divider

Dimensionnez-le pour une touche. width se compte en largeurs de touche : 1 est une lettre, 3 environ un tiers de rangée, et la personne peut le changer ensuite. Le texte tient sur une ligne et rétrécit pour entrer ; les couleurs suivent par défaut celle du texte de la touche, si bien qu'un élément s'accorde aux touches voisines sauf demande contraire. Une sparkline garde ses 120 derniers points, plus qu'une touche ne peut montrer.

import time

def elements(state):
    return [element("clock", "Clock", icon="clock", width=2)]

def draw(id, state):
    return text(time.format("HH:mm"), size=15, weight="semibold")
Une horloge sur une touche : pas d'état, pas de hooks, juste l'heure à chaque demande. Les éléments se redessinent tout seuls environ une fois par seconde.
GOAL = 200

def initial():
    return {"words": 0}

def elements(state):
    return [element("goal", "Word goal", icon="target", width=2)]

def on_open(state):
    state["words"] = 0
    return state

def on_word(word, state):
    state["words"] += 1
    return state

def draw(id, state):
    return vstack([
        text(f"{state['words']}/{GOAL}", size=11),
        progress(state["words"], total=GOAL),
    ], spacing=2)
Un objectif de mots pour la session, compté par on_word et dessiné en barre. Les hooks tiennent l'état ; draw ne fait que le lire.

Les éléments vivent dans les dispositions personnalisées : il faut donc d'abord en construire une. Les préréglages n'en acceptent pas ; dupliquer un préréglage en votre propre disposition est la première chose que propose l'éditeur.

  1. Installez le plugin et activez-le. Ses éléments apparaissent aussitôt.
  2. Ouvrez Disposition, créez ou dupliquez une disposition personnalisée, puis allez dans Agencer.
  3. Touchez Élément, choisissez l'élément du plugin dans la bande et réglez sa largeur.

L'aperçu de l'éditeur dessine chaque élément proposé par le script, à peu près à la taille de la touche qui l'accueillera, sous la maquette de barre d'espace. Enregistrez et placez-le une fois : le clavier dessine la même chose.

Un élément ne fait que montrer ce que le plugin dessine : le toucher ne fait rien, car la touche appartient déjà à la disposition. La disposition garde l'élément même quand son plugin est désactivé ou désinstallé, et la touche se remplit à nouveau dès son retour.

Boutons et molettes dans la barre supérieure

La bande au-dessus des touches se compose dans Disposition > Barre supérieure, et un plugin peut y proposer ses propres commandes. bar_items(state) les renvoie : bar_button(...) pour ce qui se touche, bar_knob(...) pour ce qui se tourne. La liste est lue à l’ouverture du clavier puis après chaque appui, un élément peut donc apparaître et disparaître avec l’état du plugin.

AppelCe qu’il fait
bar_items(state)Tout ce que ce plugin propose à la barre, sous forme de liste. Un id n’a besoin d’être unique qu’au sein du plugin ; la barre le conserve à côté de l’id du plugin. En cas d’id répété, le premier l’emporte, et un élément que le hook cesse de renvoyer n’est plus dessiné.
bar_button(id, name, icon="", title="")Un bouton. name est ce que l’éditeur affiche et ce que lit VoiceOver. icon est un SF Symbol et title jusqu’à 12 caractères de libellé à côté : avec une icône et sans title, l’icône reste seule ; sans icône, c’est le texte qui reste seul. Un bouton n’a ni valeur ni plage. Un appui appelle on_action(id, None, state), et tout ce qu’il fait se passe là.
bar_knob(id, name, icon="", min=0, max=1, step=0, value=0, setting=None, art=None, rotor=None)Une molette. min et max donnent sa plage, step la rend crantée dès qu’il dépasse zéro, et value indique son point de départ. setting= la relie plutôt à l’une des commandes numériques de l’app, qui fournit alors la plage et la position. art= et rotor= la dessinent en Python.
def initial():
    return {"width": 2.0}

def bar_items(state):
    return [
        bar_button("sig", "Sign off", icon="signature", title="Sign off"),
        bar_knob("volume", "Key volume", icon="speaker.wave.2", setting="sound.volume"),
        bar_knob("width", "Trail width", icon="scribble", min=1, max=6, step=0.5, value=state["width"]),
    ]

def on_action(action, value, state):
    if action == "sig":
        insert(" sent from Clink")
    elif action == "width":
        state["width"] = value
    return state
Un bouton et deux molettes, dont une reliée au volume des touches

Les deux reviennent par on_action(action, value, state), identifiés par l’id de l’élément : les éléments de barre d’un plugin et ses réglages partagent donc le même hook. Un appui envoie None. Une molette envoie sa valeur à chaque cran tant que le doigt est posé, puis une dernière fois au relâchement, un plugin peut donc suivre le geste ou attendre la valeur finale.

Une molette avec setting= tourne un réglage que l’app possède déjà, nommé dans la liste Commandes et lectures plus haut, comme sound.volume ou haptics.intensity. Un nom que l’app ne connaît pas donne un KeyError dans la console. La plage et la position de départ viennent de ce réglage, la copie propre au clavier change pendant le glissement, si bien que la touche suivante est déjà au nouveau niveau, et la valeur est enregistrée au relâchement. Une molette de volume ou de retour haptique remontée depuis zéro rallume aussi le son ou l’haptique le temps du geste, pour qu’on entende et sente les crans en montant ; c’est au plugin d’enregistrer cet interrupteur avec set_setting dans on_action.

Une molette peut être dessinée en Python. art= est la partie qui ne bouge pas, la lunette ou le corps, et rotor= la face qui tourne, de -135 degrés au minimum à +135 degrés au maximum. Les deux acceptent les mêmes formes, peintures, dégradés et ombres que le dessin de touche, jusqu’à seize formes chacune, et unit="key" met les coordonnées à l’échelle du cadran carré, où une aiguille à y négatif pointe vers le haut. Le dessin reçoit la couleur de texte de la barre sous le nom "text" et l’accent du thème sous le nom "accent". Si vous omettez les deux, la molette est un anneau avec l’icône au centre ; l’éditeur permet aussi de donner à une molette déjà posée n’importe quelle finition intégrée.

Rien n’arrive tout seul dans la barre. Un élément se place à la main, comme le menu et les suggestions.

  1. Installez le plugin et activez-le. Ses éléments de barre apparaissent aussitôt.
  2. Ouvrez Disposition > Barre supérieure.
  3. Ajoutez le bouton ou la molette du plugin et faites-le glisser à sa place. Une molette reçoit en plus une finition à choisir.

Les constructeurs ignorent sans erreur un mot-clé qu’ils ne connaissent pas. setting=, min=, max=, step=, value=, art= et rotor= n’appartiennent qu’à bar_knob : un bar_button à qui on en passe un est quand même construit en simple bouton, sans rien signaler ; le travail d’un bouton se fait dans on_action. À l’inverse, title= appartient au bouton, et une molette l’ignore.

Effets lumineux

Un plugin peut ajouter son propre éclairage des touches. effects(state) renvoie des entrées light_effect(...), et chacune apparaît dans l’app sous Effets > Des plugins, à côté des styles intégrés et des effets que les gens créent eux-mêmes. Un effet, ce sont des données et non un dessin : une pile de calques que le clavier anime lui-même. Rien ne tourne donc dans le script à chaque image, et les faces, les lettres, le halo, l’éclat à la frappe et la mise en veille fonctionnent comme pour les styles intégrés.

AppelCe qu’il fait
light_effect(id, name, layers=[...], colors=[], icon="")Un effet. id doit seulement être unique au sein du plugin. layers est une liste de light_layer(...), appliquée de haut en bas. colors contient jusqu’à huit chaînes "#rrggbb" que la couleur parcourt en boucle ; sans elles, l’effet suit le réglage Couleur de la personne.
light_layer(pattern, ...)Un calque : un motif de la liste ci-dessous. Tous les mots-clés sont facultatifs.
COLORS = ["#00e5ff", "#7c4dff", "#ff4081"]

def effects(state):
    return [
        light_effect("tide", "Tide", colors=COLORS, layers=[
            light_layer("solid", low=0.2, high=0.2),
            light_layer("wave", moves="both", speed=0.8, size=1.5,
                        direction="up"),
        ]),
    ]
Une base stable, avec par-dessus une vague lente qui traverse trois couleurs. La vague ajoute sa lumière à la base et fait varier la couleur au passage.

Motifs

  • solid
  • pulse
  • wave
  • gradient
  • twinkle
  • sweep
  • rain
  • flicker
  • checker
CléCe qu’il fait
moves="light"Ce que le motif fait varier : "light" pour la luminosité, "color", ou "both".
mix="add"Comment sa luminosité se combine avec les calques du dessus : "add", "max" pour garder la plus vive, ou "multiply" pour les assombrir comme un masque.
shape="smooth"La montée et la descente de pulse, wave et gradient : "smooth", "ramp", "step" ou "spike".
direction="right"Le sens dans lequel avancent wave, gradient, sweep et rain : "right", "left", "down", "up", ou "out" depuis le centre.
speed=1De 0 à 4. À 0, le motif reste immobile.
size=1De 0,25 à 4 : combien de fois le motif se répète sur le clavier. Pour twinkle, cela règle le nombre de touches allumées, pour sweep la longueur de la traîne.
low=0, high=1La luminosité entre laquelle varie le motif, chacune de 0 à 1. Une valeur low au-dessus de high l’inverse.
color_span=1, color_offset=0Jusqu’où le motif avance dans les couleurs, et où il commence, chacun de 0 à 1.
def effects(state):
    return [
        light_effect("scanner", "Scanner", layers=[
            light_layer("wave", moves="color", speed=0.3),
            light_layer("sweep", mix="multiply", speed=1.2, size=1.5),
        ]),
    ]
Un sweep utilisé comme masque. Le premier calque ne fait varier que la couleur, et le sweep décide quelles touches s’allument. Sans colors, il suit le réglage Couleur : avec Spectre, il devient un arc-en-ciel.

Choisir un effet le copie dans les réglages de la personne, il continue donc de fonctionner plugin désactivé. Tant que le plugin est actif et qu’un de ses effets tourne, le clavier relit effects(state) à l’ouverture puis environ une fois par seconde, et reprend ce qui a changé. C’est ainsi qu’un effet suit l’état, l’heure ou stats().

def effects(state):
    # Rounded, so the effect only changes when the pace really does.
    tempo = round(0.3 + min(stats()["wpm"], 120) / 40, 1)
    return [
        light_effect("tempo", "Tempo", colors=["#ff3d7f", "#ffb000"], layers=[
            light_layer("wave", moves="both", speed=tempo, low=0.15),
        ]),
    ]
Tempo, du plugin Light Show : une vague qui accélère à mesure que vous tapez plus vite. Un calque peut changer de vitesse sans que son motif saute.

Le bouton effects de l’éditeur liste ce que propose le script. Pour voir un effet bouger, enregistrez le plugin, choisissez l’effet sous Effets > Des plugins et regardez le clavier en haut de cet écran.

Arrondissez tout ce qui fluctue, comme une vitesse de frappe. Un effet qui revient différent à chaque seconde est remplacé à chaque seconde pour rien. Un motif ou un mot-clé inconnu donne une ValueError dans la console de l’éditeur, et un effet sans calque est ignoré.

Apparences

Sept hooks proposent à l’app des choses à choisir : key_styles, themes, popups, animations, backgrounds, trails et layouts. Chacun renvoie des entrées construites avec les appels ci-dessous, et elles apparaissent sous Des plugins à côté des choix intégrés. Les thèmes font exception : ils ont leur propre onglet Plugins dans l’éditeur de thème. Une apparence, ce sont des nombres et des mots, pas du code de dessin, donc rien dans le script ne s’exécute à chaque image. La choisir la copie dans les réglages de la personne, elle continue donc de fonctionner plugin désactivé, et choisir ensuite un choix intégré la met de côté.

AppelCe qu’il fait
key_style(id, name, material=, variant=, shape=, shadow=, cap=cap(...))Un style de touche, appliqué au thème ouvert dans l’éditeur de thème, depuis sa carte Style. Seuls les champs qu’il nomme changent : material, variant, shape, glass, fan, les inner_radius, face_inset, edges, raised et light_angle mécaniques, shadow (0 donne un rendu plat) et outline. Les couleurs restent celles du thème.
cap(outline="round", corner=None, travel=2, layers=[cap_layer(...)])Une touche peinte pour un style de touche, empilée à partir d’entrées cap_layer(...). outline vaut round ou rect, corner remplace le rayon, et travel dit de combien la touche s’enfonce quand on appuie.
cap_layer(kind, paint, inset=0, x=0, y=0, blur=0, fade=None, when=[...])Un calque d’une touche peinte. kind vaut fill, stroke ou inner, paint accepte la grammaire de couleur de touche, un color(...) ou un gradient(...), et when limite le calque à pale, dark, pressed, resting et highlighted. Avec moves=False, le calque reste immobile pendant que la touche s’enfonce.
theme(id, name, background=, keys=, key_text=, style=key_style(...), ...)Un thème complet, dans l’onglet Plugins de l’éditeur de thème. background, keys et key_text sont obligatoires, en couleurs "#rrggbb" ; special, special_text, accent, background_bottom (un dégradé vers le bas), dark, font et weight sont facultatifs. style=key_style(...) définit sa finition. Le choisir l’installe comme thème personnalisé.
popup_style(id, name, shape="tile", width=48, height=56, lift=30, ...)La bulle au-dessus d’une touche enfoncée, sous Apparence > Fenêtres contextuelles. shape vaut "tile", "round" ou "balloon" ; width, height, lift et font_size sont en points, et response et damping règlent son ressort.
entrance(id, name, opacity=0, x=0, y=0, scale=1, tilt=0, spin=0, ...)L’arrivée du clavier, sous Apparence > Entrée. opacity, x, y, scale, tilt et spin sont son point de départ ; il revient au repos en ressort selon response et damping.
press_animation(id, name, scale=, x=0, y=0, rotation=0, ...)La forme d’une touche maintenue, sous Réactions > Géométrie : scale (ou scale_x et scale_y), x, y et rotation à pleine pression.
letter_animation(id, name, scale=, x=0, y=0, rotation=0, anchor="center")L’effet ponctuel qu’une lettre joue à chaque appui, sous Réactions > Lettres : les mêmes nombres à leur sommet, plus anchor.
transition(id, name, x=0, y=0, scale=1, tilt=0, fade=True, duration=None)Le passage entre les lettres, 123 et #+=, sous Apparence > Transition. x et y indiquent jusqu’où partent les anciennes touches, en part du clavier, et les nouvelles arrivent en miroir. Elle accepte aussi scale, tilt, fade et duration.
background(id, name, layers=[...], colors=[])Un arrière-plan animé, sous Apparence > Arrière-plan : jusqu’à quatre calques particles(...) et jusqu’à huit couleurs "#rrggbb".
particles(shape="glow", count=40, size=4, speed=20, direction="none", ...)Un calque d’arrière-plan. shape vaut "dot", "glow", "streak", "ring" ou "square" ; count, size, speed, direction, spread, gravity, wobble, life, twinkle et opacity façonnent le mouvement, et burst projette des particules depuis chaque touche enfoncée.
layout(id, name, rows=[...], left=[], right=[])Une disposition, sous Disposition > Disposition. Chaque rangée est une liste de touches : une chaîne est une lettre, layout_key(...) tout le reste. left et right placent jusqu’à trois touches à côté de la barre d’espace. La choisir installe une disposition personnalisée ordinaire.
layout_key(glyph, action="insert", width=1)Une touche qui n’est pas une simple lettre. action vaut insert, spacer, shift, delete, space, return, numbers, emoji, globe, tab, left, right, undo, redo ou dismiss, et width se compte en touches.
trail(id, name, layers=[...], colors=[])Une traînée de glissement. layers regroupe des entrées trail_line, trail_stamps et trail_head, dessinées dans l’ordre, et colors est la palette qu’elles indexent.
trail_line(width=1, tail_width=1, color=-1, glow=0, dash=0, gap=0, band=0, flow=0)Le trait qui suit le glissement. tail_width affine l’extrémité ancienne, color=-1 fond toute la palette le long du trait, et dash, gap, band et flow le découpent ou le mettent en mouvement.
trail_stamps(shape="dot", size=1, spacing=14, scatter=0, spin=0, twinkle=0, color=-1)Des formes semées le long du glissement : dot, ring, square, diamond, star, spark ou heart. spacing est l’écart entre elles en points, scatter les écarte du trait, spin les fait tourner et twinkle les fait apparaître et disparaître.
trail_head(shape="dot", size=1.5, pulse=0, opacity=1, color=-1, glow=0)La marque au bout du doigt. pulse la fait respirer et glow répand de la lumière autour d’elle.
def animations(state):
    return [
        entrance("swoop", "Swoop", y=90, scale=0.94, tilt=-20,
                 response=0.5, damping=0.72),
        press_animation("dip", "Dip", scale=0.93, y=2),
        letter_animation("bounce", "Bounce", y=-7, scale=1.12,
                         anchor="bottom"),
        transition("glide", "Glide", x=0.3, scale=0.96),
    ]
Une animation de chaque sorte. Chacune apparaît sous Des plugins sur sa propre carte dans Apparence.
def backgrounds(state):
    return [
        background("snowfall", "Snowfall", colors=["#ffffff", "#cfe8ff"], layers=[
            particles(shape="dot", count=70, size=2.2, speed=28,
                      direction="down", spread=12, wobble=10, life=9),
            particles(shape="glow", count=0, size=3, life=1,
                      burst=8, burst_speed=90),
        ]),
    ]
Snowfall : des flocons qui tombent en se balançant, et une bouffée de lumière à chaque touche enfoncée.

Renvoyez des nœuds trail depuis trails(state). Chaque traînée possède une palette colors et des couches comme trail_line. Avec color=-1, la ligne forme un dégradé le long du glissement. Activez le plugin, puis choisissez sa traînée dans Des plugins, dans le sélecteur de traînées. Stocker des éléments graphiques dans l’état ne dessine pas de traînée.

def trails(state):
    return [trail("rainbow", "Rainbow", colors=["#ff0000", "#ff8800", "#ffff00", "#00cc44", "#0066ff", "#9900ff"],
        layers=[trail_line(width=1.3, tail_width=0.2, color=-1)])]
Traînée arc-en-ciel

Une apparence est copiée au moment où on la choisit, donc modifier le script plus tard ne change pas une apparence déjà utilisée ; il faut la choisir à nouveau. Un mot-clé mal orthographié, un mot hors de sa liste ou du texte à la place d’un nombre donne une erreur dans la console de l’éditeur, et les nombres restent dans les plages des réglages de l’app.

Du dessin sur les touches

key_art(state) renvoie un dict des noms de touches vers du dessin, que le clavier peint à l’intérieur de la touche. Les noms de touches sont ceux de haptics, avec les replis letters et keys. Le dessin est une liste de shape(...), une forme seule, un calque art([...]) ou une liste de calques, et chaque calque passe à son dessin suivant à son propre rythme : une touche peut donc porter deux choses qui bougent à des vitesses différentes.

AppelCe qu’il fait
art(shapes, animate=0, curve="ease_out")Un calque animé. Donnez-lui un nouveau dessin et il y passe en animate secondes le long de curve : ease_out, linear, ease_in, ease_in_out ou spring. Plusieurs calques sur une touche s’animent chacun de son côté.
shape(kind, anchor="center", unit="pt", x=0, y=0, size=, width=, height=, fill=, stroke=, ...)Une chose dessinée : circle, rect, capsule, line, path, text ou icon. Elle se place en x et y depuis un anchor de la touche, en points ou, avec unit="key", en fractions de la touche. fill et stroke acceptent une couleur hexadécimale, "text", "accent", un color(...) ou un gradient(...), et il y a aussi line_width, corner, trim_from, trim_to, rotation, opacity, blur et jusqu’à trois ombres.
graph(values, min=None, max=None, width=0.8, height=0.25, stroke=, fill=)Une série de nombres dépliée en formes ordinaires, à joindre à une liste de formes ou à utiliser seule. Les 60 derniers échantillons finis sont gardés, les bornes viennent sinon de la série elle-même, et fill ajoute un tracé fermé jusqu’à la base sous la ligne.
color(value, opacity=1)Une peinture : une couleur hexadécimale, ou "text" ou "accent" résolu par rapport à la touche où elle atterrit, à opacity.
gradient(kind, colors, stops=[], start=, end=, center=, radius=0.5)Un dégradé linéaire ou radial à travers une liste de couleurs. stops les place, start et end orientent un dégradé linéaire, center et radius situent un dégradé radial.
shadow(color, radius=4, x=0, y=0)Une ombre sous une forme, jusqu’à trois. Un voile qui remplit la touche suit les coins de celle-ci au lieu de les carrer ; une lueur, elle, déborde de la touche.

Le dessin est relu après chaque événement entendu par le plugin, et c’est toute la boucle : changer l’état dans on_event(name, info, state), dessiner à partir de lui dans key_art(state). events(state) nomme les événements voulus et est lu une fois au chargement du plugin. Sans lui, un plugin entend tout sauf key, key_down, key_up, predictions et tick, qui font tourner un script à chaque appui ou chaque seconde et doivent être demandés. Les changements de majuscule et de plan redessinent le dessin même si personne n’écoute, et ils arrivent aussi dans context().

def initial():
    return {"shift": "off"}

def events(state):
    return ["shift"]

def on_event(name, info, state):
    state["shift"] = info["state"]
    return state

def key_art(state):
    if state["shift"] != "locked":
        return {}
    lamp = shape("circle", anchor="top_right", x=-7, y=7, size=5,
                 fill="accent", shadow=shadow("accent", radius=4))
    return {"shift": art([lamp], animate=0.12)}
Un témoin de verrouillage des majuscules, dessiné par le plugin et non intégré au clavier

Formes

  • circle
  • rect
  • capsule
  • line
  • path
  • text
  • icon

Seize formes par touche, comptées sur tous ses calques, et 64 points par tracé ; le reste est abandonné. Une forme est dessinée dans une boîte de ses propres width et height, et une boîte sans épaisseur ne peint rien : une ligne horizontale a donc besoin d’un petit height à elle, avec ses points au milieu (height=0.03, points=[[0, 0.5], [1, 0.5]]), sinon elle n’apparaît pas, sans rien dire.

Retour haptique, zones de touche et corrections

haptics(state) et hitboxes(state) renvoient des dicts indexés par nom de touche : la lettre elle-même, "space", "delete", "return", "shift" ou "globe", puis "letters" pour toute lettre non nommée et "keys" pour tout le reste. Les touches qu’un plugin omet gardent les réglages de la personne. Les deux tables sont lues à l’ouverture du clavier puis environ une fois par seconde, donc rien ne s’exécute à chaque frappe pour elles.

AppelCe qu’il fait
feel(style=None, intensity=None, sharpness=None)Un retour haptique : style vaut "soft", "light", "medium", "heavy", "rigid" ou "off", et intensity et sharpness (de 0 à 1) l’ajustent. Un mot de style seul fonctionne aussi.
hitbox(scale=1, x=0, y=0)Une zone de touche : x et y déplacent la cible de la touche de cette part de sa taille, une demi-touche au plus, et scale l’agrandit ou la réduit. Un simple nombre est un scale.
def haptics(state):
    return {
        "space": "heavy",
        "return": "rigid",
        "delete": feel(intensity=0.45, sharpness=0.9),
    }
Heavy Space : une barre d’espace plus lourde, un retour net et une suppression légère.

on_touch(key, x, y, state) s’exécute après chaque appui, une fois l’appui traité, avec l’endroit de la touche où le doigt s’est posé. La position est mesurée par rapport à la touche dessinée, pas à sa cible déplacée, donc un plugin peut déplacer chaque touche vers l’endroit où elle est vraiment touchée sans courir après son propre décalage.

def initial():
    return {"keys": {}}

def on_touch(key, x, y, state):
    if len(key) != 1:
        return state
    n, ax, ay = state["keys"].get(key, [0, 0.0, 0.0])
    n = min(n + 1, 50)
    ax += (x - ax) / n
    ay += (y - ay) / n
    state["keys"][key] = [n, ax, ay]
    return state

def hitboxes(state):
    boxes = {}
    for key in state["keys"]:
        n, x, y = state["keys"][key]
        if n >= 12:
            boxes[key] = hitbox(x=round(x * 0.6, 2), y=round(y * 0.6, 2))
    return boxes
Adaptive Hitbox : apprend où chaque touche est vraiment touchée et déplace sa cible d’une partie du chemin.

suggestions(word, state) place des mots en tête de la barre pendant la saisie d’un mot. correct(word, fix, state) s’exécute une fois par mot quand l’espace le termine, avec la correction du clavier ou None, même avec la correction automatique désactivée. Renvoyez un mot pour l’insérer, False pour garder le mot tel que tapé, ou None pour laisser faire le clavier. Le premier plugin qui répond l’emporte, et une suppression juste après l’annule comme toute correction automatique.

SHORT = {"brb": "be right back", "omw": "on my way", "idk": "I don't know"}

def suggestions(word, state):
    long = SHORT.get(word.lower())
    return [long] if long else []

def correct(word, fix, state):
    if len(word) > 1 and word.isupper():
        return False
    return None
Shorthand : propose « be right back » pendant qu’on tape brb, et tient la correction automatique à l’écart des mots en majuscules.

Aucun des deux hooks ne s’exécute dans les champs de mot de passe. La puce de correction de la barre montre la correction du clavier, pas ce que correct renverrait, et une table modifiée dans on_touch atteint le clavier au rythme suivant.

Tester dans l'éditeur

L'aperçu de l'éditeur est un clavier de substitution. Il dessine settings(state) en direct, déclenche n'importe quel hook d'une touche avec un mot, une touche ou un type de champ d'exemple, montre la barre d'espace telle que le plugin l'a laissée, et liste tout ce que le plugin a demandé au clavier. stats() y renvoie des chiffres inventés, donc une vitesse s'affiche sans taper.

  1. Utilisez les commandes de l'aperçu. Chacune exécute on_action et redessine.
  2. Touchez les boutons de hook dans l'ordre où le clavier le ferait : on_open, puis on_word ou on_key quelques fois, puis on_close.
  3. Lisez la console. Les commandes apparaissent telles que vous les avez écrites, les lignes de print() en dessous, et une erreur indique sa ligne.
  4. Rechargez pour repartir d'un état neuf. Modifier le script ne le réinitialise pas tout seul.

L'aperçu n'a pas de document, donc insert() et replace() ne font que journaliser. Pour les essayer, enregistrez et tapez dans n'importe quel champ avec le clavier ouvert.

Limites

Quand un plugin arrive sous forme de fichier ou depuis un dépôt, Clink le vérifie avant de l'enregistrer. Il doit faire moins de 64 000 octets et 1 600 lignes, définir au moins un hook, n'importer que les modules autorisés et ne contenir aucun des éléments suivants :

Interdit dans les plugins partagés

  • __
  • exec(
  • eval(
  • open(
  • compile(

Modules qu'un plugin partagé peut importer

  • json
  • math
  • random
  • re
  • sys
  • time

Chaque appel de hook dispose de 2 000 000 pas d'interprète, comme un rendu de panneau. Mais on_key s'exécute à chaque frappe, donc un traitement lourd à cet endroit ralentira la saisie bien avant d'atteindre cette limite.

Le fichier

Un plugin est un seul document JSON. id reste stable d'une mise à jour à l'autre, version est du texte libre affiché dans la liste, icon est un nom de SF Symbol, et source est le script avec ses retours à la ligne échappés. Partagez-le depuis l'inspecteur de l'éditeur, ou importez-en un avec le bouton flèche de l'onglet Plugins.

{
  "id": "word-count",
  "name": "Word count",
  "icon": "text.word.spacing",
  "summary": "Counts words on the space bar",
  "version": "1.0",
  "author": "You",
  "enabled": true,
  "source": "def initial():\n    return {\"on\": False}\n..."
}
Un .clinkplugin, abrégé.

Les plugins se partagent sous forme de fichiers .clinkplugin, un document JSON qui contient le script. Ils se publient via un dépôt, comme les panneaux, avec un dossier de fichiers, un manifeste et une version. Le dépôt officiel est anti-ltd/clink-plugins.

Dépôts ›

Quand rien ne se passe

La plupart du temps, c'est l'un de ces cas.

SymptômeCe qu'il faut vérifier
Rien ne se passeLes plugins sont désactivés en haut de l'onglet Plugins, le plugin est désactivé dans la liste, ou il n'y a pas d'abonnement Clink Pro. Dans les trois cas, le clavier ne fait tourner aucun plugin.
Le texte de la barre d'espace est griséUn plugin l'a prise. La ligne sous le champ dit lequel ; désactivez l'interrupteur de ce plugin, ou le plugin lui-même, pour récupérer le champ.
La section n'apparaît pasL'ancre est mal orthographiée. Comparez-la avec Afficher les identifiants dans l'app, et rappelez-vous que la section s'affiche quand même sur la page du plugin, là où il faut regarder en premier.
Une touche d'élément est videLe plugin derrière est désactivé, désinstallé, ou son draw(id, state) a levé une erreur. La disposition garde la touche dans tous les cas : elle se remplit dès que le plugin est réactivé. Cherchez l'erreur dans la console de l'éditeur.
Un élément ne change jamaisdraw est toujours appelé : c'est donc l'état derrière qui ne bouge pas. Ce qui alimente le visage doit être mis à jour depuis un hook : on_tick pour ce qui change tout seul, on_word ou on_key pour ce qui suit la frappe.
set_setting() n'a rien faitUn nom absent de la liste ci-dessus lève KeyError. Une valeur du mauvais type ou hors plage est ignorée, et la console de l'éditeur indique ce que le réglage attend. Un réglage réservé revient aussi en arrière sans abonnement.
Le compteur est revenu à zéroLe clavier enregistre l'état à sa fermeture, pas à chaque touche. Un clavier tué par le système en pleine session perd ce que ses plugins ont compté depuis son ouverture. Gardez les totaux dans les hooks côté app quand cela compte.
La frappe semble lenteQuelque chose de lourd tourne dans on_key. Déplacez-le dans on_word, allégez-le, ou mettez en cache ce qu'il calcule dans l'état.
Un effet manque sous Des pluginsPlugins doit être activé ainsi que le plugin, et effects(state) doit renvoyer un light_effect avec au moins un calque. Touchez effects dans l’éditeur pour voir ce qui est revenu, et cherchez une ValueError dans la console.
Un élément de la barre supérieure ne fait rien quand on l’utiliseUn appui comme une molette relâchée arrivent tous deux dans on_action(action, value, state), identifiés par l’id de l’élément et non par son nom. Une molette n’écrit un réglage que si bar_knob en nomme un dans setting= ; sinon la valeur revient au plugin, et un bouton n’écrit jamais de réglage.
PyMini ›
Télécharger dans l’App Store