packages feed

kmonad 0.4.2 → 0.4.3

raw patch · 26 files changed

+2325/−310 lines, 26 filesdep ~basedep ~cerealdep ~hspecPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base, cereal, hspec, lens, megaparsec, mtl, optparse-applicative, resourcet, rio, template-haskell, time, unix, unliftio

API changes (from Hackage documentation)

- KMonad.Args: initStr :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
- KMonad.Args.Cmd: [_initStr] :: Cmd -> Maybe DefSetting
- KMonad.Args.Cmd: initStr :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
- KMonad.Args.Types: SInitStr :: Text -> DefSetting
- KMonad.Args.Types: [_buttons] :: DefLayer -> [DefButton]
- KMonad.Args.Types: _SInitStr :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 Text
- KMonad.Args.Types: type DefSrc = [Keycode]
- KMonad.Prelude.Imports: ap :: Monad m => m (a -> b) -> m a -> m b
- KMonad.Prelude.Imports: liftM3 :: Monad m => (a1 -> a2 -> a3 -> r) -> m a1 -> m a2 -> m a3 -> m r
- KMonad.Prelude.Imports: liftM4 :: Monad m => (a1 -> a2 -> a3 -> a4 -> r) -> m a1 -> m a2 -> m a3 -> m a4 -> m r
- KMonad.Prelude.Imports: liftM5 :: Monad m => (a1 -> a2 -> a3 -> a4 -> a5 -> r) -> m a1 -> m a2 -> m a3 -> m a4 -> m a5 -> m r
- KMonad.Prelude.Imports: mapAndUnzipM :: Applicative m => (a -> m (b, c)) -> [a] -> m ([b], [c])
- KMonad.Prelude.Imports: replicateM :: Applicative m => Int -> m a -> m [a]
+ KMonad.Args: implArnd :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
+ KMonad.Args.Cmd: [_implArnd] :: Cmd -> Maybe DefSetting
+ KMonad.Args.Cmd: implArnd :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
+ KMonad.Args.Parser: buttonP :: Parser DefButton
+ KMonad.Args.Parser: implArndButtons :: [(Text, ImplArnd, DefButton -> DefButton -> DefButton)]
+ KMonad.Args.Types: DefSrc :: Maybe Text -> [Keycode] -> DefSrc
+ KMonad.Args.Types: IAAround :: ImplArnd
+ KMonad.Args.Types: IAAroundOnly :: ImplArnd
+ KMonad.Args.Types: IAAroundWhenAlone :: ImplArnd
+ KMonad.Args.Types: IADisabled :: ImplArnd
+ KMonad.Args.Types: KAroundImplicit :: DefButton -> DefButton -> DefButton
+ KMonad.Args.Types: KAroundOnly :: DefButton -> DefButton -> DefButton
+ KMonad.Args.Types: KAroundWhenAlone :: DefButton -> DefButton -> DefButton
+ KMonad.Args.Types: KStepped :: [DefButton] -> DefButton
+ KMonad.Args.Types: LButton :: DefButton -> DefLayerSetting
+ KMonad.Args.Types: LImplArnd :: ImplArnd -> DefLayerSetting
+ KMonad.Args.Types: LSrcName :: Text -> DefLayerSetting
+ KMonad.Args.Types: SImplArnd :: ImplArnd -> DefSetting
+ KMonad.Args.Types: [_keycodes] :: DefSrc -> [Keycode]
+ KMonad.Args.Types: [_layerSettings] :: DefLayer -> [DefLayerSetting]
+ KMonad.Args.Types: [_srcName] :: DefSrc -> Maybe Text
+ KMonad.Args.Types: _DefLayerSetting :: AsDefLayerSetting r_a1TUx => Prism' r_a1TUx DefLayerSetting
+ KMonad.Args.Types: _LButton :: AsDefLayerSetting r_a1TUx => Prism' r_a1TUx DefButton
+ KMonad.Args.Types: _LImplArnd :: AsDefLayerSetting r_a1TUx => Prism' r_a1TUx ImplArnd
+ KMonad.Args.Types: _LSrcName :: AsDefLayerSetting r_a1TUx => Prism' r_a1TUx Text
+ KMonad.Args.Types: _SImplArnd :: AsDefSetting r_a1U7B => Prism' r_a1U7B ImplArnd
+ KMonad.Args.Types: class AsDefLayerSetting r_a1TUx
+ KMonad.Args.Types: class HasDefSrc c_a1TPe
+ KMonad.Args.Types: data DefLayerSetting
+ KMonad.Args.Types: data DefSrc
+ KMonad.Args.Types: data ImplArnd
+ KMonad.Args.Types: defSrc :: HasDefSrc c_a1TPe => Lens' c_a1TPe DefSrc
+ KMonad.Args.Types: instance Control.Lens.Plated.Plated KMonad.Args.Types.DefButton
+ KMonad.Args.Types: instance Data.Data.Data KMonad.Args.Types.DefButton
+ KMonad.Args.Types: instance GHC.Classes.Eq KMonad.Args.Types.DefButton
+ KMonad.Args.Types: instance GHC.Classes.Eq KMonad.Args.Types.DefLayer
+ KMonad.Args.Types: instance GHC.Classes.Eq KMonad.Args.Types.DefLayerSetting
+ KMonad.Args.Types: instance GHC.Classes.Eq KMonad.Args.Types.DefSrc
+ KMonad.Args.Types: instance GHC.Classes.Eq KMonad.Args.Types.ImplArnd
+ KMonad.Args.Types: instance GHC.Classes.Eq KMonad.Args.Types.KExpr
+ KMonad.Args.Types: instance GHC.Show.Show KMonad.Args.Types.DefLayerSetting
+ KMonad.Args.Types: instance GHC.Show.Show KMonad.Args.Types.DefSrc
+ KMonad.Args.Types: instance GHC.Show.Show KMonad.Args.Types.ImplArnd
+ KMonad.Args.Types: instance KMonad.Args.Types.AsDefLayerSetting KMonad.Args.Types.DefLayerSetting
+ KMonad.Args.Types: instance KMonad.Args.Types.HasDefSrc KMonad.Args.Types.DefSrc
+ KMonad.Args.Types: keycodes :: HasDefSrc c_a1TPe => Lens' c_a1TPe [Keycode]
+ KMonad.Args.Types: srcName :: HasDefSrc c_a1TPe => Lens' c_a1TPe (Maybe Text)
+ KMonad.Keyboard.Keycode: KeyDictation :: Keycode
+ KMonad.Keyboard.Keycode: KeyFn :: Keycode
+ KMonad.Keyboard.Keycode: KeyLaunchpad :: Keycode
+ KMonad.Keyboard.Keycode: KeyMissionCtrl :: Keycode
+ KMonad.Keyboard.Keycode: KeySpotlight :: Keycode
+ KMonad.Keyboard.Keycode: instance Data.Data.Data KMonad.Keyboard.Keycode.Keycode
+ KMonad.Model.Button: aroundOnly :: Button -> Button -> Button
+ KMonad.Model.Button: aroundWhenAlone :: Button -> Button -> Button
+ KMonad.Model.Button: onTap :: AnyK () -> Button
+ KMonad.Model.Button: steppedButton :: [Button] -> Button
+ KMonad.Model.Button: tapAction :: HasButton c_a1Dn1 => Lens' c_a1Dn1 Action
+ KMonad.Prelude.Imports: evalCont :: Cont r r -> r
+ KMonad.Prelude.Imports: evalContT :: Monad m => ContT r m r -> m r
+ KMonad.Prelude.Imports: label :: MonadCont m => a -> m (a -> m b, a)
+ KMonad.Prelude.Imports: label_ :: MonadCont m => m (m a)
+ KMonad.Util: instance Data.Data.Data KMonad.Util.Milliseconds
- KMonad.App.Types: allowCmd :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V Bool
+ KMonad.App.Types: allowCmd :: HasAppCfg c_a2bqo => Lens' c_a2bqo Bool
- KMonad.App.Types: appCfg :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V AppCfg
+ KMonad.App.Types: appCfg :: HasAppCfg c_a2bqo => Lens' c_a2bqo AppCfg
- KMonad.App.Types: appEnv :: HasAppEnv c_a1Yed => Lens' c_a1Yed AppEnv
+ KMonad.App.Types: appEnv :: HasAppEnv c_a2bzF => Lens' c_a2bzF AppEnv
- KMonad.App.Types: class HasAppCfg c_a1Y4V
+ KMonad.App.Types: class HasAppCfg c_a2bqo
- KMonad.App.Types: class HasAppEnv c_a1Yed
+ KMonad.App.Types: class HasAppEnv c_a2bzF
- KMonad.App.Types: class HasKEnv c_a1YsL
+ KMonad.App.Types: class HasKEnv c_a2bOc
- KMonad.App.Types: dispatch :: HasAppEnv c_a1Yed => Lens' c_a1Yed Dispatch
+ KMonad.App.Types: dispatch :: HasAppEnv c_a2bzF => Lens' c_a2bzF Dispatch
- KMonad.App.Types: fallThrough :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V Bool
+ KMonad.App.Types: fallThrough :: HasAppCfg c_a2bqo => Lens' c_a2bqo Bool
- KMonad.App.Types: firstLayer :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V LayerTag
+ KMonad.App.Types: firstLayer :: HasAppCfg c_a2bqo => Lens' c_a2bqo LayerTag
- KMonad.App.Types: inHooks :: HasAppEnv c_a1Yed => Lens' c_a1Yed Hooks
+ KMonad.App.Types: inHooks :: HasAppEnv c_a2bzF => Lens' c_a2bzF Hooks
- KMonad.App.Types: kAppEnv :: HasKEnv c_a1YsL => Lens' c_a1YsL AppEnv
+ KMonad.App.Types: kAppEnv :: HasKEnv c_a2bOc => Lens' c_a2bOc AppEnv
- KMonad.App.Types: kBEnv :: HasKEnv c_a1YsL => Lens' c_a1YsL BEnv
+ KMonad.App.Types: kBEnv :: HasKEnv c_a2bOc => Lens' c_a2bOc BEnv
- KMonad.App.Types: kEnv :: HasKEnv c_a1YsL => Lens' c_a1YsL KEnv
+ KMonad.App.Types: kEnv :: HasKEnv c_a2bOc => Lens' c_a2bOc KEnv
- KMonad.App.Types: keAppCfg :: HasAppEnv c_a1Yed => Lens' c_a1Yed AppCfg
+ KMonad.App.Types: keAppCfg :: HasAppEnv c_a2bzF => Lens' c_a2bzF AppCfg
- KMonad.App.Types: keLogFunc :: HasAppEnv c_a1Yed => Lens' c_a1Yed LogFunc
+ KMonad.App.Types: keLogFunc :: HasAppEnv c_a2bzF => Lens' c_a2bzF LogFunc
- KMonad.App.Types: keySink :: HasAppEnv c_a1Yed => Lens' c_a1Yed KeySink
+ KMonad.App.Types: keySink :: HasAppEnv c_a2bzF => Lens' c_a2bzF KeySink
- KMonad.App.Types: keySinkDev :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V (Acquire KeySink)
+ KMonad.App.Types: keySinkDev :: HasAppCfg c_a2bqo => Lens' c_a2bqo (Acquire KeySink)
- KMonad.App.Types: keySource :: HasAppEnv c_a1Yed => Lens' c_a1Yed KeySource
+ KMonad.App.Types: keySource :: HasAppEnv c_a2bzF => Lens' c_a2bzF KeySource
- KMonad.App.Types: keySourceDev :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V (Acquire KeySource)
+ KMonad.App.Types: keySourceDev :: HasAppCfg c_a2bqo => Lens' c_a2bqo (Acquire KeySource)
- KMonad.App.Types: keymap :: HasAppEnv c_a1Yed => Lens' c_a1Yed Keymap
+ KMonad.App.Types: keymap :: HasAppEnv c_a2bzF => Lens' c_a2bzF Keymap
- KMonad.App.Types: keymapCfg :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V (LMap Button)
+ KMonad.App.Types: keymapCfg :: HasAppCfg c_a2bqo => Lens' c_a2bqo (LMap Button)
- KMonad.App.Types: outHooks :: HasAppEnv c_a1Yed => Lens' c_a1Yed Hooks
+ KMonad.App.Types: outHooks :: HasAppEnv c_a2bzF => Lens' c_a2bzF Hooks
- KMonad.App.Types: outVar :: HasAppEnv c_a1Yed => Lens' c_a1Yed (TMVar KeyEvent)
+ KMonad.App.Types: outVar :: HasAppEnv c_a2bzF => Lens' c_a2bzF (TMVar KeyEvent)
- KMonad.App.Types: sluice :: HasAppEnv c_a1Yed => Lens' c_a1Yed Sluice
+ KMonad.App.Types: sluice :: HasAppEnv c_a2bzF => Lens' c_a2bzF Sluice
- KMonad.App.Types: startDelay :: HasAppCfg c_a1Y4V => Lens' c_a1Y4V Milliseconds
+ KMonad.App.Types: startDelay :: HasAppCfg c_a2bqo => Lens' c_a2bqo Milliseconds
- KMonad.Args: cfgFile :: HasCmd c_a28ii => Lens' c_a28ii FilePath
+ KMonad.Args: cfgFile :: HasCmd c_a2m7o => Lens' c_a2m7o FilePath
- KMonad.Args: class HasCmd c_a28ii
+ KMonad.Args: class HasCmd c_a2m7o
- KMonad.Args: cmd :: HasCmd c_a28ii => Lens' c_a28ii Cmd
+ KMonad.Args: cmd :: HasCmd c_a2m7o => Lens' c_a2m7o Cmd
- KMonad.Args: cmdAllow :: HasCmd c_a28ii => Lens' c_a28ii DefSetting
+ KMonad.Args: cmdAllow :: HasCmd c_a2m7o => Lens' c_a2m7o DefSetting
- KMonad.Args: cmpSeq :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
+ KMonad.Args: cmpSeq :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
- KMonad.Args: dryRun :: HasCmd c_a28ii => Lens' c_a28ii Bool
+ KMonad.Args: dryRun :: HasCmd c_a2m7o => Lens' c_a2m7o Bool
- KMonad.Args: fallThrgh :: HasCmd c_a28ii => Lens' c_a28ii DefSetting
+ KMonad.Args: fallThrgh :: HasCmd c_a2m7o => Lens' c_a2m7o DefSetting
- KMonad.Args: iToken :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
+ KMonad.Args: iToken :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
- KMonad.Args: logLvl :: HasCmd c_a28ii => Lens' c_a28ii LogLevel
+ KMonad.Args: logLvl :: HasCmd c_a2m7o => Lens' c_a2m7o LogLevel
- KMonad.Args: oToken :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
+ KMonad.Args: oToken :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
- KMonad.Args: strtDel :: HasCmd c_a28ii => Lens' c_a28ii Milliseconds
+ KMonad.Args: strtDel :: HasCmd c_a2m7o => Lens' c_a2m7o Milliseconds
- KMonad.Args.Cmd: cfgFile :: HasCmd c_a28ii => Lens' c_a28ii FilePath
+ KMonad.Args.Cmd: cfgFile :: HasCmd c_a2m7o => Lens' c_a2m7o FilePath
- KMonad.Args.Cmd: class HasCmd c_a28ii
+ KMonad.Args.Cmd: class HasCmd c_a2m7o
- KMonad.Args.Cmd: cmd :: HasCmd c_a28ii => Lens' c_a28ii Cmd
+ KMonad.Args.Cmd: cmd :: HasCmd c_a2m7o => Lens' c_a2m7o Cmd
- KMonad.Args.Cmd: cmdAllow :: HasCmd c_a28ii => Lens' c_a28ii DefSetting
+ KMonad.Args.Cmd: cmdAllow :: HasCmd c_a2m7o => Lens' c_a2m7o DefSetting
- KMonad.Args.Cmd: cmpSeq :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
+ KMonad.Args.Cmd: cmpSeq :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
- KMonad.Args.Cmd: dryRun :: HasCmd c_a28ii => Lens' c_a28ii Bool
+ KMonad.Args.Cmd: dryRun :: HasCmd c_a2m7o => Lens' c_a2m7o Bool
- KMonad.Args.Cmd: fallThrgh :: HasCmd c_a28ii => Lens' c_a28ii DefSetting
+ KMonad.Args.Cmd: fallThrgh :: HasCmd c_a2m7o => Lens' c_a2m7o DefSetting
- KMonad.Args.Cmd: iToken :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
+ KMonad.Args.Cmd: iToken :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
- KMonad.Args.Cmd: logLvl :: HasCmd c_a28ii => Lens' c_a28ii LogLevel
+ KMonad.Args.Cmd: logLvl :: HasCmd c_a2m7o => Lens' c_a2m7o LogLevel
- KMonad.Args.Cmd: oToken :: HasCmd c_a28ii => Lens' c_a28ii (Maybe DefSetting)
+ KMonad.Args.Cmd: oToken :: HasCmd c_a2m7o => Lens' c_a2m7o (Maybe DefSetting)
- KMonad.Args.Cmd: strtDel :: HasCmd c_a28ii => Lens' c_a28ii Milliseconds
+ KMonad.Args.Cmd: strtDel :: HasCmd c_a2m7o => Lens' c_a2m7o Milliseconds
- KMonad.Args.Types: DefLayer :: Text -> [DefButton] -> DefLayer
+ KMonad.Args.Types: DefLayer :: Text -> [DefLayerSetting] -> DefLayer
- KMonad.Args.Types: _DefSetting :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 DefSetting
+ KMonad.Args.Types: _DefSetting :: AsDefSetting r_a1U7B => Prism' r_a1U7B DefSetting
- KMonad.Args.Types: _KDefAlias :: AsKExpr r_a1O56 => Prism' r_a1O56 DefAlias
+ KMonad.Args.Types: _KDefAlias :: AsKExpr r_a1UlQ => Prism' r_a1UlQ DefAlias
- KMonad.Args.Types: _KDefCfg :: AsKExpr r_a1O56 => Prism' r_a1O56 DefSettings
+ KMonad.Args.Types: _KDefCfg :: AsKExpr r_a1UlQ => Prism' r_a1UlQ DefSettings
- KMonad.Args.Types: _KDefLayer :: AsKExpr r_a1O56 => Prism' r_a1O56 DefLayer
+ KMonad.Args.Types: _KDefLayer :: AsKExpr r_a1UlQ => Prism' r_a1UlQ DefLayer
- KMonad.Args.Types: _KDefSrc :: AsKExpr r_a1O56 => Prism' r_a1O56 DefSrc
+ KMonad.Args.Types: _KDefSrc :: AsKExpr r_a1UlQ => Prism' r_a1UlQ DefSrc
- KMonad.Args.Types: _KExpr :: AsKExpr r_a1O56 => Prism' r_a1O56 KExpr
+ KMonad.Args.Types: _KExpr :: AsKExpr r_a1UlQ => Prism' r_a1UlQ KExpr
- KMonad.Args.Types: _SAllowCmd :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 Bool
+ KMonad.Args.Types: _SAllowCmd :: AsDefSetting r_a1U7B => Prism' r_a1U7B Bool
- KMonad.Args.Types: _SCmpSeq :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 DefButton
+ KMonad.Args.Types: _SCmpSeq :: AsDefSetting r_a1U7B => Prism' r_a1U7B DefButton
- KMonad.Args.Types: _SCmpSeqDelay :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 Int
+ KMonad.Args.Types: _SCmpSeqDelay :: AsDefSetting r_a1U7B => Prism' r_a1U7B Int
- KMonad.Args.Types: _SFallThrough :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 Bool
+ KMonad.Args.Types: _SFallThrough :: AsDefSetting r_a1U7B => Prism' r_a1U7B Bool
- KMonad.Args.Types: _SIToken :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 IToken
+ KMonad.Args.Types: _SIToken :: AsDefSetting r_a1U7B => Prism' r_a1U7B IToken
- KMonad.Args.Types: _SOToken :: AsDefSetting r_a1NS7 => Prism' r_a1NS7 OToken
+ KMonad.Args.Types: _SOToken :: AsDefSetting r_a1U7B => Prism' r_a1U7B OToken
- KMonad.Args.Types: class AsDefSetting r_a1NS7
+ KMonad.Args.Types: class AsDefSetting r_a1U7B
- KMonad.Args.Types: class AsKExpr r_a1O56
+ KMonad.Args.Types: class AsKExpr r_a1UlQ
- KMonad.Keyboard.IO.Linux.UinputSink: keyboardName :: HasUinputCfg c_a1HAz => Lens' c_a1HAz String
+ KMonad.Keyboard.IO.Linux.UinputSink: keyboardName :: HasUinputCfg c_a1N1h => Lens' c_a1N1h String
- KMonad.Keyboard.IO.Linux.UinputSink: postInit :: HasUinputCfg c_a1HAz => Lens' c_a1HAz (Maybe String)
+ KMonad.Keyboard.IO.Linux.UinputSink: postInit :: HasUinputCfg c_a1N1h => Lens' c_a1N1h (Maybe String)
- KMonad.Keyboard.IO.Linux.UinputSink: productCode :: HasUinputCfg c_a1HAz => Lens' c_a1HAz CInt
+ KMonad.Keyboard.IO.Linux.UinputSink: productCode :: HasUinputCfg c_a1N1h => Lens' c_a1N1h CInt
- KMonad.Keyboard.IO.Linux.UinputSink: productVersion :: HasUinputCfg c_a1HAz => Lens' c_a1HAz CInt
+ KMonad.Keyboard.IO.Linux.UinputSink: productVersion :: HasUinputCfg c_a1N1h => Lens' c_a1N1h CInt
- KMonad.Keyboard.IO.Linux.UinputSink: vendorCode :: HasUinputCfg c_a1HAz => Lens' c_a1HAz CInt
+ KMonad.Keyboard.IO.Linux.UinputSink: vendorCode :: HasUinputCfg c_a1N1h => Lens' c_a1N1h CInt
- KMonad.Keyboard.Ops: keycode :: HasKeyEvent c_a1prS => Lens' c_a1prS Keycode
+ KMonad.Keyboard.Ops: keycode :: HasKeyEvent c_a1tbW => Lens' c_a1tbW Keycode
- KMonad.Keyboard.Ops: switch :: HasKeyEvent c_a1prS => Lens' c_a1prS Switch
+ KMonad.Keyboard.Ops: switch :: HasKeyEvent c_a1tbW => Lens' c_a1tbW Switch
- KMonad.Keyboard.Types: class HasKeyEvent c_a1prS
+ KMonad.Keyboard.Types: class HasKeyEvent c_a1tbW
- KMonad.Keyboard.Types: keyEvent :: HasKeyEvent c_a1prS => Lens' c_a1prS KeyEvent
+ KMonad.Keyboard.Types: keyEvent :: HasKeyEvent c_a1tbW => Lens' c_a1tbW KeyEvent
- KMonad.Keyboard.Types: keycode :: HasKeyEvent c_a1prS => Lens' c_a1prS Keycode
+ KMonad.Keyboard.Types: keycode :: HasKeyEvent c_a1tbW => Lens' c_a1tbW Keycode
- KMonad.Keyboard.Types: switch :: HasKeyEvent c_a1prS => Lens' c_a1prS Switch
+ KMonad.Keyboard.Types: switch :: HasKeyEvent c_a1tbW => Lens' c_a1tbW Switch
- KMonad.Model.Action: action :: HasTimeout c_a1vhD m_a1vdl => Lens' c_a1vhD (m_a1vdl ())
+ KMonad.Model.Action: action :: HasTimeout c_a1yW3 m_a1yRM => Lens' c_a1yW3 (m_a1yRM ())
- KMonad.Model.Action: class HasHook c_a1vlg m_a1vhU | c_a1vlg -> m_a1vhU
+ KMonad.Model.Action: class HasHook c_a1yZF m_a1yWk | c_a1yZF -> m_a1yWk
- KMonad.Model.Action: class HasTimeout c_a1vhD m_a1vdl | c_a1vhD -> m_a1vdl
+ KMonad.Model.Action: class HasTimeout c_a1yW3 m_a1yRM | c_a1yW3 -> m_a1yRM
- KMonad.Model.Action: class HasTrigger c_a1vd1
+ KMonad.Model.Action: class HasTrigger c_a1yRs
- KMonad.Model.Action: delay :: HasTimeout c_a1vhD m_a1vdl => Lens' c_a1vhD Milliseconds
+ KMonad.Model.Action: delay :: HasTimeout c_a1yW3 m_a1yRM => Lens' c_a1yW3 Milliseconds
- KMonad.Model.Action: elapsed :: HasTrigger c_a1vd1 => Lens' c_a1vd1 Milliseconds
+ KMonad.Model.Action: elapsed :: HasTrigger c_a1yRs => Lens' c_a1yRs Milliseconds
- KMonad.Model.Action: event :: HasTrigger c_a1vd1 => Lens' c_a1vd1 KeyEvent
+ KMonad.Model.Action: event :: HasTrigger c_a1yRs => Lens' c_a1yRs KeyEvent
- KMonad.Model.Action: hTimeout :: HasHook c_a1vlg m_a1vhU => Lens' c_a1vlg (Maybe (Timeout m_a1vhU))
+ KMonad.Model.Action: hTimeout :: HasHook c_a1yZF m_a1yWk => Lens' c_a1yZF (Maybe (Timeout m_a1yWk))
- KMonad.Model.Action: hook :: HasHook c_a1vlg m_a1vhU => Lens' c_a1vlg (Hook m_a1vhU)
+ KMonad.Model.Action: hook :: HasHook c_a1yZF m_a1yWk => Lens' c_a1yZF (Hook m_a1yWk)
- KMonad.Model.Action: keyH :: HasHook c_a1vlg m_a1vhU => Lens' c_a1vlg (Trigger -> m_a1vhU Catch)
+ KMonad.Model.Action: keyH :: HasHook c_a1yZF m_a1yWk => Lens' c_a1yZF (Trigger -> m_a1yWk Catch)
- KMonad.Model.Action: timeout :: HasTimeout c_a1vhD m_a1vdl => Lens' c_a1vhD (Timeout m_a1vdl)
+ KMonad.Model.Action: timeout :: HasTimeout c_a1yW3 m_a1yRM => Lens' c_a1yW3 (Timeout m_a1yRM)
- KMonad.Model.Action: trigger :: HasTrigger c_a1vd1 => Lens' c_a1vd1 Trigger
+ KMonad.Model.Action: trigger :: HasTrigger c_a1yRs => Lens' c_a1yRs Trigger
- KMonad.Model.BEnv: bEnv :: HasBEnv c_a1Ehn => Lens' c_a1Ehn BEnv
+ KMonad.Model.BEnv: bEnv :: HasBEnv c_a1JHr => Lens' c_a1JHr BEnv
- KMonad.Model.BEnv: beButton :: HasBEnv c_a1Ehn => Lens' c_a1Ehn Button
+ KMonad.Model.BEnv: beButton :: HasBEnv c_a1JHr => Lens' c_a1JHr Button
- KMonad.Model.BEnv: binding :: HasBEnv c_a1Ehn => Lens' c_a1Ehn Keycode
+ KMonad.Model.BEnv: binding :: HasBEnv c_a1JHr => Lens' c_a1JHr Keycode
- KMonad.Model.BEnv: class HasBEnv c_a1Ehn
+ KMonad.Model.BEnv: class HasBEnv c_a1JHr
- KMonad.Model.BEnv: lastSwitch :: HasBEnv c_a1Ehn => Lens' c_a1Ehn (MVar Switch)
+ KMonad.Model.BEnv: lastSwitch :: HasBEnv c_a1JHr => Lens' c_a1JHr (MVar Switch)
- KMonad.Model.Button: button :: HasButton c_a1zNb => Lens' c_a1zNb Button
+ KMonad.Model.Button: button :: HasButton c_a1Dn1 => Lens' c_a1Dn1 Button
- KMonad.Model.Button: class HasButton c_a1zNb
+ KMonad.Model.Button: class HasButton c_a1Dn1
- KMonad.Model.Button: pressAction :: HasButton c_a1zNb => Lens' c_a1zNb Action
+ KMonad.Model.Button: pressAction :: HasButton c_a1Dn1 => Lens' c_a1Dn1 Action
- KMonad.Model.Button: releaseAction :: HasButton c_a1zNb => Lens' c_a1zNb Action
+ KMonad.Model.Button: releaseAction :: HasButton c_a1Dn1 => Lens' c_a1Dn1 Action
- KMonad.Prelude.Imports: class () => Bifunctor (p :: Type -> Type -> Type)
+ KMonad.Prelude.Imports: class forall a. () => Functor p a => Bifunctor (p :: Type -> Type -> Type)
- KMonad.Prelude.Imports: class () => MonadTrans (t :: Type -> Type -> Type -> Type)
+ KMonad.Prelude.Imports: class forall (m :: Type -> Type). Monad m => Monad t m => MonadTrans (t :: Type -> Type -> Type -> Type)
- KMonad.Prelude.Imports: infixl 6 -
+ KMonad.Prelude.Imports: infixl 6 +
- KMonad.Prelude.Imports: throwM :: (MonadThrow m, Exception e) => e -> m a
+ KMonad.Prelude.Imports: throwM :: (MonadThrow m, HasCallStack, Exception e) => e -> m a
- KMonad.Util.LayerStack: _LayerDoesNotExist :: AsLayerStackError r_aSKO l_aSIE => Prism' r_aSKO l_aSIE
+ KMonad.Util.LayerStack: _LayerDoesNotExist :: AsLayerStackError r_aPgX l_aPeN => Prism' r_aPgX l_aPeN
- KMonad.Util.LayerStack: _LayerNotOnStack :: AsLayerStackError r_aSKO l_aSIE => Prism' r_aSKO l_aSIE
+ KMonad.Util.LayerStack: _LayerNotOnStack :: AsLayerStackError r_aPgX l_aPeN => Prism' r_aPgX l_aPeN
- KMonad.Util.LayerStack: _LayerStackError :: AsLayerStackError r_aSKO l_aSIE => Prism' r_aSKO (LayerStackError l_aSIE)
+ KMonad.Util.LayerStack: _LayerStackError :: AsLayerStackError r_aPgX l_aPeN => Prism' r_aPgX (LayerStackError l_aPeN)
- KMonad.Util.LayerStack: class AsLayerStackError r_aSKO l_aSIE | r_aSKO -> l_aSIE
+ KMonad.Util.LayerStack: class AsLayerStackError r_aPgX l_aPeN | r_aPgX -> l_aPeN
- KMonad.Util.LayerStack: items :: forall l_aSL9 k_aSLa a_aSLb k_aTqS a_aTqT. Lens (LayerStack l_aSL9 k_aSLa a_aSLb) (LayerStack l_aSL9 k_aTqS a_aTqT) (HashMap (l_aSL9, k_aSLa) a_aSLb) (HashMap (l_aSL9, k_aTqS) a_aTqT)
+ KMonad.Util.LayerStack: items :: forall l_aPhi k_aPhj a_aPhk k_aPXK a_aPXL. Lens (LayerStack l_aPhi k_aPhj a_aPhk) (LayerStack l_aPhi k_aPXK a_aPXL) (HashMap (l_aPhi, k_aPhj) a_aPhk) (HashMap (l_aPhi, k_aPXK) a_aPXL)
- KMonad.Util.LayerStack: maps :: forall l_aSL9 k_aSLa a_aSLb. Lens' (LayerStack l_aSL9 k_aSLa a_aSLb) (HashSet l_aSL9)
+ KMonad.Util.LayerStack: maps :: forall l_aPhi k_aPhj a_aPhk. Lens' (LayerStack l_aPhi k_aPhj a_aPhk) (HashSet l_aPhi)
- KMonad.Util.LayerStack: stack :: forall l_aSL9 k_aSLa a_aSLb. Lens' (LayerStack l_aSL9 k_aSLa a_aSLb) [l_aSL9]
+ KMonad.Util.LayerStack: stack :: forall l_aPhi k_aPhj a_aPhk. Lens' (LayerStack l_aPhi k_aPhj a_aPhk) [l_aPhi]

Files

c_src/keyio.c view
@@ -12,7 +12,7 @@   return ioctl(fd, EVIOCGRAB, grab); } -// Acquire a filedescriptor as a uinput keyboard+// Acquire a file descriptor as a uinput keyboard int acquire_uinput_keysink(int fd, char *name, int vendor, int product, int version) {    // Designate fd as a keyboard of all keys
c_src/mac/dext.cpp view
@@ -1,3 +1,5 @@+#include <filesystem> // Include this before virtual_hid_device_service.hpp to avoid compile error+ #include "keyio_mac.hpp"  #include "virtual_hid_device_driver.hpp"@@ -16,13 +18,8 @@  int init_sink() {     pqrs::dispatcher::extra::initialize_shared_dispatcher();-    std::filesystem::path client_socket_file_path("/tmp/karabiner_driverkit_virtual_hid_device_service_client.sock");-    client = new pqrs::karabiner::driverkit::virtual_hid_device_service::client(client_socket_file_path);+    client = new pqrs::karabiner::driverkit::virtual_hid_device_service::client();     auto copy = client;-    client->async_driver_loaded();-    client->async_driver_version_matched();-    client->async_virtual_hid_keyboard_ready();-    client->async_virtual_hid_pointing_ready();     /**/     client->connected.connect([copy] {                                   std::cout << "connected" << std::endl;@@ -37,19 +34,20 @@     client->error_occurred.connect([](auto&& error_code) {                                        std::cout << "error_occurred " << error_code << std::endl;                                    });-    client->driver_loaded_response.connect([](auto&& driver_loaded) {+    client->driver_connected.connect([](auto&& driver_connected) {                                                static std::optional<bool> previous_value; -                                               if (previous_value != driver_loaded) {-                                                   std::cout << "driver_loaded " << driver_loaded << std::endl;-                                                   previous_value = driver_loaded;+                                               if (previous_value != driver_connected) {+                                                   std::cout << "driver_connected " << driver_connected << std::endl;+                                                   previous_value = driver_connected;                                                }                                            });-    client->driver_version_matched_response.connect([](auto&& driver_version_matched) {+    client->driver_version_mismatched.connect([](auto&& driver_version_mismatched) {                                                         static std::optional<bool> previous_value;-                                                        if (previous_value != driver_version_matched) {-                                                            std::cout << "driver_version_matched " << driver_version_matched << std::endl;-                                                            previous_value = driver_version_matched;++                                                        if (previous_value != driver_version_mismatched) {+                                                            std::cout << "driver_version_mismatched " << driver_version_mismatched << std::endl;+                                                            previous_value = driver_version_mismatched;                                                         }                                                     });     /**/
changelog.md view
@@ -12,6 +12,39 @@  ### Fixed +## 0.4.3 – 2024-09-11++### Added++- Added `stepped`. It performs the next button from a circular sequence+  whenever it is pressed.+- Implemented named source blocks.+  To name a source block add `:name <name>` at the beginning of the+  `defsrc` block. To use it add `:source <name>` after the layer name to the+  `deflayer` block. (#831)+- Added `around-only`, which works like `around` but releases the outer button+  when others are pressed. (#859)+- Added `around-when-alone`, which also represses the outer button when only+  the inner button is pressed and all others have been released. (#859)+- Allow customization of implicit `around`s (#859)++### Changed++- Update Karabiner-DriverKit to 3.1.0 (#780)+- Added tests to check that every button has documentation (#857)+- `defsrc` with duplicate keycodes are now forbidden (#860)++### Fixed++- Fixed crash on non-US backslash under MacOS (#766)+- Fixed broken keyboard due to circular event handling under MacOS (#781)+- Fixed crash on unhandled buttons by ignoring them (#807)+- Fixed parse errors relating to whitespace (#796, #875)+- Fixed broken compose sequences (#823, #869)+- Fixed parse errors when using keys only available on Darwin OS (#828)+- Fixed `around-next` wasn't parsable (#857)+- Fixed most buttons which behave weird in nested tap situations (#873)+ ## 0.4.2 – 2023-10-07  ### Added
+ doc/quick-reference.md view
@@ -0,0 +1,463 @@+<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->+**Table of Contents**++- [Introduction](#introduction)+- [Defcfg](#defcfg)+    - [Input and Output](#input-and-output)+    - [Other Configuration Options](#other-configuration-options)+    - [Full Example](#full-example)+- [Buttons](#buttons)+    - [Modded Buttons](#modded-buttons)+    - [General Purpose Buttons](#general-purpose-buttons)+    - [Tap buttons](#tap-buttons)+- [Layers](#layers)+    - [Operating on Layers](#operating-on-layers)++<!-- markdown-toc end -->++# Introduction+Welcome to the KMonad quick reference guide. This document aims to provide a+simple definition and a usable example for all possible configuration options in+the KMonad program to act as a quick reference that you look into when forgetting+a function or/and wanting to know something quickly. Note that this document+does not try to be a full guide or a detailed documentation, this is the aim of+[tutorial.kbd](../keymap/tutorial.kbd).++# Defcfg++The `defcfg` block is where you can define all of your configuration+rules like setting up in- and output devices or certain global+configuration options.++A block can take the following arguments:++## Input and Output+++ `input`: define the input keyboard which the program will capture.+++ `output`: define the output keyboard which KMonad will create, with+  additional options to execute upon starting KMonad.++Here is how you would define the basic input and output settings on all+supported systems:++  - GNU/Linux:++    ```clojure+    input  (device-file "/dev/input/by-id/usb-04d9_daskeyboard-event-kbd")+    output (uinput-sink "My KMonad output")+    ```++  - Windows:++    ```clojure+    input  (low-level-hook)+    output (send-event-sink)+    ```++  - MacOS:++    ```clojure+    input  (iokit-name "my-keyboard-product-string")+    output (kext)+    ```++## Other Configuration Options++The following are all global config options that one can set in the+`defcfg` block.+++ `fallthrough` (boolean, defaults to `false`): re-emit keys that are+  not defined in the corresponding `defsrc` block.++  This allows one to only specify certain parts of a layout, with all+  other keys having their "default" meaning.+++ `allow-cmd` (boolean, defaults to `false`): allow arbitrary shell+  commands to be run as keys.++  NOTE: This can be dangerous since someone with access to your keyboard+  file could bind `rm -rf ~/` to a key. Think about whether you really+  want this behaviour.+++ `cmp-seq` (key, defaults to `RightAlt`): compose key for Unicode input+  (X11 specific).+++ `cmp-seq-delay` (natural number): delay between each pressed key in a+  compose-key sequence.+++ `implicit-around` (around variant, defaults to `around`):+  Specifies the variant of `around` to use in implicit around constructs+  like `A` or `S-a`.+  You can also specify `disabled`, which causes implicit arounds to+  result in errors.++## Full Example++Below is an example of a full `defcfg` block for a GNU/Linux system.++```clojure+(defcfg+  input  (device-file "/dev/input/by-id/usb-04d9_daskeyboard-event-kbd")+  output (uinput-sink+          "My KMonad output"                           ;; name of the created keyboard+          "sleep 1 && setxkbmap -option compose:ralt") ;; additional, environment-specific, information+  cmp-seq ralt+  cmp-seq-delay 5+  fallthrough true+  allow-cmd true+)+```++# Buttons++Defining fancy buttons is why we're here, right? There are a variety of+these things here, as well as some helpers to make entering them into+layers (more below) much easier.++## Press- or release-only buttons++- `(press-only x)` : Send the *press* of x when this button is tapped+- `(release-only x)` : Send the *release* of x when this button is tapped++It is possible to define buttons that only press or release a virtual output+button. They are useful in tap-macros, especially ones that are going to be+executed in a 'known context'. Assume you want to use your physical alt button+to *both* open a layer, but also to function as a basic `alt` key. This can be+achieved by `(around met (layer-toggle my-layer))`. However, if you have a macro+inside `my-layer` that taps alt, then this would release alt until the layer is+reactivated (by physically releasing and repressing the `alt` key). The macro,+however, can instead be affixed by `(press-only met)`, making the last step of+the macro the reactivation of the `alt` key, solving the problem.++## Modded Buttons++To make key-entry easier, KMonad already provides some syntax for+Emacs-like specification of key chords. They are defined like this:++  - `C-` : `(around-implicit lctl X)`+  - `A-` : `(around-implicit lalt X)`+  - `M-` : `(around-implicit lmet X)`+  - `S-` : `(around-implicit lsft X)`++Then `RC-`, `RA-`, `RM-`, and `RS-` behave exactly the same, except+using the right-modifier.++The definition of a key chord then looks like this:++```clojure+(defalias Ca C-a) ;; this is equivalent to (defalias Ca (around-implicit Ctl a))+```++## General Purpose Buttons+++ `defalias`: define a name for a button. This can then be referenced in+  a layer using the `@name` syntax (see [layers](#layers) below).++  ```clojure+  (defalias+    num (layer-toggle numbers) ;; Bind num to a button that switches to a layer+    kil C-A-del                ;; Bind kil to a button that Ctrl+Alt+deletes+  )+  ```+++ `around`: combine two keys into one.++  ```clojure+  (defalias ad (around alt del)) ;; this is like pressing Alt+Del+  ```+++ `around-only`: `around` but release the outer button as soon as others are pressed.++    ```clojure+    (defalias+      A (around lsft a)+      A' (around-only lsft a)+    )+    ```++    `@A'` is simply an uppercase letter but with the following difference to the `@A`:++    P@A Pb R@A Rb -> AB+    P@A' Pb R@A' Rb -> Ab+++ `around-when-alone`: similar to `around-only` but when all other buttons have been released+    the outer button is repressed.++    ```clojure+    (defalias+        A'' (around-when-alone lsft a)+    )+    ```++    P@A'' Tb Tc R@A'' -> Plsft Pa Rlsft Tb Tlsft Tc Plsft Ra Rlsft+++ `around-implicit`: the around variant used implicit++    ```closure+    (defalias+        =A (around-implicit lsft a)+    )+    ```++    `@=A` is the desugared form of `A`+++ `around-next`: perform the next button-press inside some context (like+  `layer-next` but more generalized)++  ```clojure+  (defalias ns  (around-next sft))  ;; Shift the next press+  ```+++ `around-next-single`: perform only the next event (keypress or release)+  inside some context.+++ `before-after-next`: tap a button prior to key press and tap another+  after key release.+  ```clojure+  (before-after-next tab S-tab a)+  ```+++ `around-next-timeout`: like `around-next` except that if other button press is not detected within+  some timeout, some other button is tapped.++  ```clojure+  (around-next-timeout 500 sft XX)+  ```+++ `sticky-key`: act like the key is held temporarily after just one+  press for the given amount of time (in ms).++  ```clojure+  (defalias slc (sticky-key 500 lctl))+  ```+++ `stepped`: perform the next button in the circular sequence+  whenever it is pressed.+  ```clojure+  (stepped (press-only lctl) (release-only lctl))+  ```++  This button:+  - presses control the first time it is tapped+  - releases control the second time it is tapped++++ `pause`: pause for the given number of ms.++  ```clojure+  (defalias+    ta3 (tap-macro K P5 M P4 o P3 n P6 a (pause 5) d) ;; P5 = (pause 5)+  )+  ```+++ `cmd-button`: take two arguments, the second one of which is+  optional. These represent the commands to be executed on pressing and+  releasing the button respectively++  ```clojure+  (defalias+    dat (cmd-button "date >> /tmp/kmonad_example.txt")   ;; Append date to tmpfile+    pth (cmd-button "echo $PATH > /tmp/kmonad_path.txt") ;; Write out PATH+    ;; `dat' on press and `pth' on release+    bth (cmd-button "date >> /tmp/kmonad_example.txt"+                    "echo $PATH > /tmp/kmonad_path.txt")+  )+  ```++## Tap buttons++Tap buttons are an integral part of KMonad. Everyone has different+preferences—that's why there are so many! Particularly when using+home-row modifiers, you will find some of the more crazy seeming buttons+to be the most comfortable.+++ `tap-macro`: take a sequence of keys and tap them, but don’t release+  the last key until the button is released.++  ```clojure+  (defalias ta1 (tap-macro K M o n a d))+  ```++  A `tap-macro` can take an optional `:delay` keyword (in ms)—this will+  wait for that amount of time after each keypress:++  ```clojure+  (defalias+    ta1 (tap-macro K M o n a d :delay 5)+    ;; equivalent to: (tap-macro K P5 M P5 o P5 n P5 a P5 d)+  )+  ```+++ `tap-macro-release`: like `tap-macro` but don’t press the last key+  until the button is released.++  ```clojure+  (defalias ta2 (tap-macro-release K M o n a d))+  ```+++ `multi-tap`: combine a sequence of keys into one key with a timeout+  between each key, as well as a last (default) button without a delay.++  ```clojure+  (defalias mt  (multi-tap 300 a 300 b 300 c 300 d e))+  ```+++ `tap-next`: combine 2 buttons, one for when the button is tapped and+  one for when it is held. The decision of what to execute is based upon+  whether the next button is the buttons own release or not.++  ```clojure+  (defalias tan (tap-next a sft))+  ```+++ `tap-hold`: like `tap-next` but with a timeout for tapping. If the key+  is released before the timeout it taps, if not then it holds.++  ```clojure+  (defalias tah (tap-hold 200 a sft))+  ```+++ `tap-hold-next`: a combination of `tap-next` and `tap-hold`: like+  `tap-next`, but _also_ switch to hold after a period of time.++  ```clojure+  (defalias thn (tap-hold-next 1000 a sft))+  ```+++ `tap-next-release`: like `tap-next` but decide whether to tap or hold+  based on the next release of a key (that was not already pressed when+  this button was pressed).++  ```clojure+  (defalias tnr (tap-next-release a sft))+  ```+++ `tap-next-press`: like `tap-next` but decide whether to tap or hold+  based on whether another key is pressed before this one is released.++  ```clojure+  (defalias tnp (tap-next-press a sft))+  ```+++ `tap-hold-next-release`: like `tap-next-release` but with an+  additional timeout. This is just like `tap-next-release`, but with+  `tap-next` swapped out for `tap-next-release`.++  ```clojure+  (defalias thr (tap-hold-next-release 1000 a sft))+  ```+++ `tap-hold-next` and `tap-hold-next-release` can take an optional+  `:timeout-button` keyword to specify a button other than the+  hold button which should be held when the timeout expires.++  ```clojure+  (defalias+    thn (tap-hold-next 200 a lsft :timeout-button x)+    thr (tap-hold-next-release 200 a lsft :timeout-button x)+  )+  ```++# Layers++KMonad allows you to define and operate on several _layers_; these are+just collections of keys.+++ `defsrc`: define the input keys of your physical keyboard. This should+  reflect the keys on your keyboard as closely as possible. Although a+  `defsrc` is very similar to a layer visually, it is not a one and will+  thus not be used as one! It only serves to define where the different+  keys are and what kind of layout kmonad is initially dealing with.+  It also supports giving a name via `:name <my-source-name>` as the+  first argument.++  For example, an ANSI 60% keyboard may be represented as:++  ```+  (defsrc+    grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc+    tab  q    w    e    r    t    y    u    i    o    p    [    ]    \+    caps a    s    d    f    g    h    j    k    l    ;    '    ret+    lsft z    x    c    v    b    n    m    ,    .    /    rsft+    lctl lmet lalt           spc            ralt rmet cmp  rctl+  )+  ```++  See the [keyboard templates](../keymap/template) for further+  inspiration.+++ `deflayer`: defines a layer to be used later.++   For example, defining a qwerty layer, as well as one for special+   symbols and numbers:++   To use a named source block add `:source <my-source-name>` after+   the layer name.++   you can also overwrite the `implicit-around` setting by adding+   `:implicit-around <setting>`.++  ```+  (deflayer qwerty+    grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc+    tab  q    w    e    r    t    y    u    i    o    p    [    ]    \+    caps a    s    d    f    g    h    j    k    l    ;    '    ret+    lsft z    x    c    v    b    n    m    ,    .    /    rsft+    lctl @num lalt           spc            ralt rmet @sym @kil+  )++  (deflayer numbers+    _    _    _    _    _    _    _    _    _    _    _    _    _    _+    _    _    _    _    _    XX   /    7    8    9    -    _    _    _+    _    _    _    _    _    XX   *    4    5    6    +    _    _+    _    _    \(   \)   .    XX   0    1    2    3    _    _+    _    _    _              _              _    _    _    _+  )+  ```++## Operating on Layers++There are many buttons that can operate on layers.+++ `layer-toggle`: toggles to a specific layer as long as the key is held.++  ```clojure+  (defalias ket (layer-toggle toggled-layer))+  ```+++ `layer-delay`: change to the layer temporarily for the given amount of+  time (in ms).++  ```clojure+  (defalias ked (layer-delay 500 delayed-layer))+  ```+++ `layer-next`: the next key press is handled by another layer+  (leader-key style).++  ```clojure+  (defalias ken (layer-next next-layer))+  ```+++ `layer-switch`: change the base layer; i.e., deregister the+  bottom-most layer and swap it out with another one.++  ```clojure+  (defalias kes (layer-switch switch-layer))+  ```+++ `layer-add` and `layer-rem`: overlay a layer on top of the current+  layer resp. remove the overlayed layer from the current layer.++  NOTE: The above two commands can be particularly dangerous if used+  alone and should really only be used together. Be careful that you+  don't make a configuration in which you can't switch back to your base+  layer!++  ```clojure+  (defalias+    add (layer-add multi-overlay)+    rem (layer-rem multi-overlay)+  )+  ```
+ keymap/tutorial.kbd view
@@ -0,0 +1,1119 @@+#| --------------------------------------------------------------------------++                              KMonad: Guided tour++  Welcome to the KMonad configuration tutorial. This document aims to explain:+  1. The configuration syntax+  2. The required information+  3. All possible configuration options++  This document should be a runnable configuration, so with some tweaks to the+  `defcfg` block (see below) you should be able to try out all the examples+  interactively.++  -------------------------------------------------------------------------- |#+++#| --------------------------------------------------------------------------++                     Basic syntax: comments and parentheses++  KMonad's configuration language is styled on various lisps, like scheme or+  Common Lisp. In a lisp, every statement is entered between '(' and ')'s. If+  you are more used to Fortran style languages (python, ruby, C, Java, etc.), the+  change is quite straightforward: the function name moves into the parentheses,+  and you don't use commas to separate arguments. I.e.++  This:     my_function(a, 3, "Alakazam")+  Becomes:  (my_function a 3 "Alakazam")++  The reason for this is because Lisp-style languages are very easy to parse and+  write syntax-highlighters for.++  We also provide standard Lisp syntax for comments:+  - block comments between: #| and its reverse+  - line comments following: ;;++  Unlike standard lisp, a single ; does not denote a comment, but instead the+  keycode for semicolon.++  Also, as you might have noticed, whitespace is possible anywhere.++  To check for syntax errors while editing, invoke KMonad with the -d option.++  -------------------------------------------------------------------------- |#+++#| --------------------------------------------------------------------------+                         Necessary: the `defcfg` block++  There are a few bits of information that are required to be present in a+  KMonad configuration file. One of these is the existence of exactly 1 `defcfg`+  statement. This statement is used to customize various configuration settings.+  Many of these settings have default values, but a minimal definition must+  include at least an 'input' field and an 'output' field. These describe how+  KMonad captures its inputs and how it emits its outputs.++  First, let's go over the optional, non-OS specific settings. Currently there+  are only 2:++  - fallthrough: `true` or `false`, defaults to `false`++    KMonad catches input events and tries to match them to various handlers. If+    it cannot match an event to any handler (for example, if it isn't included+    in the corresponding `defsrc` block, or if it is, but the current keymap+    does not map any buttons to it), then the event gets quietly ignored. If+    `fallthrough` is set to `true`, any unhandled events simply get reemitted.++    In more practical terms, this allows you to only specify the keys that+    you want to overwrite in your `defsrc' block. For example, the following+    configuration would rebind Caps Lock to Escape only when tapped.++      (defcfg+        input … output …+        fallthrough true)+      (defsrc caps)+      (deflayer my-layer (tap-next esc caps))++  - allow-cmd: `true` or `false`, defaults to `false`++    If this is set to `false`, any action that runs a shell-command will simply+    log to `stdout` without ever running (log-level info). Don't ever enable+    this on a configuration that you do not trust, because:++      (cmd-button "rm -rf ~/*")++    is a thing. For more information on the `cmd-button' function, see the+    section on Command buttons below.++  - implicit-around: `around` (default), `around-only`, `around-when-alone` or `disabled`++    An implicit around may be in `A` or `_` but also in modded buttons (`S-a`).+    This changes how implicit arounds are handled, for more details see `around`+    below.+    `disabled` results in an error when implicit around is used.++  There are also some optional OS specific settings that we support:++  - `cmp-seq': KEY, defaults to `RightAlt' (Linux X11 specific)++    This sets your compose key for Unicode input. For more information, as well+    as a workaround to also make this work on windows, see the section on+    Compose-key sequences below.++  - `cmp-seq-delay': NUMBER (in milliseconds)++    This sets a delay between each pressed key in a compose-key sequence.  Some+    environments may have troubles recognizing the key sequence if it's pressed+    too rapidly; if you experience any problems in this direction, you can try+    setting this value to `5' or `10' and see if that helps.++  Secondly, let's go over how to specify the `input` and `output` fields of a+  `defcfg` block. This differs between OSes, and so do the capabilities of+  these interfaces.+++  -- Linux ------++  In Linux we deal with input by performing an ioctl-grab on a specific+  device-file. This allows us to hook KMonad on the input of exactly 1 keyboard,+  and allows you to run multiple instances of KMonad for different keyboards. We+  make an input using:+    (device-file "/dev/input/by-id/my-keyboard-kbd")++  NOTE: Any valid path to a device-file will work, but it is recommended to use+  the 'by-id' directory, since these names will not change if you replug the+  device.++  We deal with output by creating a 'uinput' device. This requires that the+  'uinput' kernel module is loaded. The easiest way to ensure this is by calling+  'sudo modprobe uinput'. We create a uinput device using:+    (uinput-sink "name" "optional post-init command")+++  -- Windows ----++  In Windows we do not get such fine-grained control. We use a low-level+  keyboard hook to intercept all non-injected keyboard events. There is+  currently an open issue to improve the C-bindings used to capture windows+  keyevents, and if you have a better way to approach this issue, help is deeply+  appreciated. You specify a windows input using:+    (low-level-hook)++  Similarly, the output in Windows lacks the fine-grained control. We use the+  SendEvent API to emit key events directly to Windows. Since these are+  'artificial' events we won't end up catching them again by the+  `low-level-hook`. It is very likely that KMonad does not play well with other+  programs that capture keyboard input like AHK. You specify windows output using:+    (send-event-sink)++  Specific to Windows, KMonad also handles key auto-repeat.  Therefore your+  Windows system settings for key repeat delay and key repeat rate will have no+  effect when KMonad is running.  To set the repeat delay and rate from KMonad,+  pass the optional arguments pair to `send-event-sink`:+    (send-event-sink [ <delay> <rate> ])+  where:+    <delay> : how many ms before a key starts repeating+    <rate>  : how many ms between each repeat event+  A value of 500 ms delay and 30 ms rate should mimic the default Windows+  settings pretty well:+    (send-event-sink 500 30)+++  -- Mac OS -----++  For Mac questions I suggest filing an issue and tagging @thoelze1, he wrote+  the MacOS API. However, input using:+    (iokit-name "optional product string")++  By default this should grab all keyboards, however if a product string is+  provided, KMonad will only capture those devices that match the provided+  product string. If you would like to provide a product string, you can run+  `make; ./list-keyboards' in c_src/mac to list the product strings of all+  connected keyboards.++  You initialize output on MacOS using:+    (kext)++  -------------------------------------------------------------------------- |#++(defcfg+  ;; For Linux+  input  (device-file "/dev/input/by-id/usb-04d9_daskeyboard-event-kbd")+  output (uinput-sink "My KMonad output"+    ;; To understand the importance of the following line, see the section on+    ;; Compose-key sequences at the near-bottom of this file.+    "/run/current-system/sw/bin/sleep 1 && /run/current-system/sw/bin/setxkbmap -option compose:ralt")+  cmp-seq ralt    ;; Set the compose key to `RightAlt'+  cmp-seq-delay 5 ;; 5ms delay between each compose-key sequence press++  ;; For Windows+  ;; input  (low-level-hook)+  ;; output (send-event-sink)++  ;; For MacOS+  ;; input  (iokit-name "my-keyboard-product-string")+  ;; output (kext)++  ;; Comment this if you want unhandled events not to be emitted+  fallthrough true++  ;; Set this to false to disable any command-execution in KMonad+  allow-cmd true++  ;; Set the implicit around to `around`+  implicit-around around+)+++#| --------------------------------------------------------------------------+                      Necessary: at least 1 `defsrc` block++  It is difficult to explain the `defsrc` block without immediately going into+  `deflayer` blocks as well. Essentially, KMonad maps input-events to various+  internal actions, many of which generate output events. The `defsrc` block+  explains the layout on which we specify our `deflayer`s down the line.++  It is important to realize that the `defsrc` block doesn't *necessarily* have+  to coincide with your actual input keyboard. You can specify a full 100%+  `defsrc` block, but only use a 40% keyboard. This will mean that every+  `deflayer` you specify will also have to match your 100% `defsrc`, and that+  your actual keyboard would be physically unable to trigger about 60% of your+  keymap, but it would be perfectly valid syntax.++  The dual of this (and more useful) is that it is also perfectly valid to only+  specify that part of your keyboard in `defsrc` that you want to remap. If you+  use a 100% keyboard, but don't want to remap the numpad at all you can simply+  leave the numpad out of your `defsrc`, and it should work just fine. In that+  particular case you probably want to set `fallthrough` to `true` in your+  `defcfg` block though.++  There is also support for named `defsrc` blocks. They contain `:name <my-source>`+  as the first argument in their definition.++  The layouting in the `defsrc` block is completely free, whitespace simply gets+  ignored. We strive to provide a name for every keycode that is no longer than+  4 characters, so we find that laying out your keymap in columns of 5 works out+  quite nicely (although wider columns will allow for more informative aliases,+  see below).++  Most keycodes should be obvious. If you are unsure, check+  './src/KMonad/Keyboard/Keycode.hs'. Every Keycode has a name corresponding to+  its Keycode name, but all lower-case and with the 'Key' prefix removed. There+  are also various aliases for Keycodes starting around line 350. If you are+  trying to bind a key and there is not a 4-letter alias, please file an issue,+  or better yet, a pull-request, and it will be added promptly.++  Also, you can consult './keymap/template/' for various input templates to use+  directly or to look up keycodes by position. Here we use the input-template+  for 'us_ansi_60.kbd'++  -------------------------------------------------------------------------- |#++(defsrc+  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc+  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \+  caps a    s    d    f    g    h    j    k    l    ;    '    ret+  lsft z    x    c    v    b    n    m    ,    .    /    rsft+  lctl lmet lalt           spc            ralt rmet cmp  rctl+)++(defsrc :name numpad+  nlck kp/ kp* kp-+  kp7  kp8 kp9 kp++  kp4  kp5 kp6+  kp1  kp2 kp3 kprt+  kp0    kp.+)+++#| --------------------------------------------------------------------------+                        Optional : `defalias` statements++  KMonad will let you specify some very specific, crazy buttons. These+  definitions can get pretty long, though, and would make `deflayer` blocks+  nearly impossible to read. Therefore we provide the ability to alias names to+  these buttons, to keep the actual `deflayer` statements orderly.++  A `defalias` can contain any number of aliases, and it can refer backwards or+  forwards to layers without issue. The only sequencing that needs to be kept in+  mind is that a `defalias` cannot refer forward to another `defalias` that is+  not yet defined.++  Here we define a few aliases, but we will define more later. Notice that we+  try to only use 3 letter names for aliases. If that is not enough to be clear,+  consider widening all columns to 6 or 7 characters (or be content with a messy+  config).++  -------------------------------------------------------------------------- |#++(defalias+  num  (layer-toggle numbers) ;; Bind num to a button that switches to a layer+  kil  C-A-del                ;; Bind kil to a button that Ctrl-Alt-deletes+)+++#| NOTE: The above code could just as easily have been written as:+(defalias num (layer-toggle numbers))+(defalias kil C-A-del)+|#+++#| --------------------------------------------------------------------------+                     Necessary: at least 1 `deflayer` block++  As explained in the `defsrc` section, a `deflayer` will define a button for+  each corresponding entry in the `defsrc` definition. A `deflayer` statement+  consists of the `deflayer` keyword, followed by the name used to identify this+  layer, followed by N 'statements-that-evaluate-to-a-button', where N is+  exactly how many entries are defined in the `defsrc` statement.++  Optionally you can add a `:source <my-source>` parameter after the name to map+  a layer using a named `defsrc` block.++  You can also overwrite the option `implicit-around` for a layer by adding+  `:implicit-around <setting>`.++  It is also important to mention that the 'keymap' in KMonad is modelled as a+  stack of layers (just like in QMK). When an event is registered we look in the+  top-most layer for a handler. If we don't find one we try the next layer, and+  then the next.++  Exactly what 'evaluates-to-a-button' will be expanded on in more detail below.+  There are very many different specialist buttons in KMonad that we will touch+  upon. However, for now, these 4 are a good place to begin:++  1. Any keycode evaluates to a button that, on press, emits the press of that+     keycode, and on release, emits the release of that keycode. Just a 'normal'+     button. The exception is '\', which gets used as an escape character. Use+     '\\' instead. Other characters that need to be escaped to match the literal+     character are '(', ')', and '_'.++  2. An @-prefixed name evaluates to an alias lookup. We named two buttons in+     the `defalias` block above, we could now refer to these buttons using+     `@num` and `@kil`. This is also why we only use alias-names no longer than+     3 characters in this tutorial. Also, note that we are already referencing+     some aliases that have not yet been defined, this is not an issue.++  3. The '_' character evaluates to transparent. I.e. no handler for that+     key-event in this layer, causing this event to be handed down the layer+     stack to perhaps be handled by the next layer.++  4. The 'XX' character evaluates to blocked. I.e. no action bound to that+     key-event in this layer, but do actually catch event, preventing any+     underlying layer from handling it.++  Finally, it is important to note that the *first* `deflayer` statement in a+  KMonad config will be the layer that is active when KMonad starts up.++  -------------------------------------------------------------------------- |#+++(deflayer qwerty+  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc+  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \+  caps a    s    d    f    g    h    j    k    l    ;    '    ret+  lsft z    x    c    v    b    n    m    ,    .    /    rsft+  lctl @num lalt           spc            ralt rmet @sym @tst+)++(deflayer phone :source numpad+  :implicit-around disabled+  nlck kp/ kp* kp-+  kp1  kp2 kp3 kp++  kp4  kp5 kp6+  kp7  kp8 kp9 kprt+  kp0    kp.+)+++#| --------------------------------------------------------------------------+                     Optional: as many layers as you please++  We had already defined `num` as referring to a `(layer-toggle numbers)`. We+  will get into layer-manipulation soon, but first, let's just create a second+  layer that overlays a numpad under our right-hand.++  To easily specify layers it is highly recommended to create an empty+  `deflayer` statement as a comment at the top of your config, so you can simply+  copy-paste this template. There are also various empty layer templates+  available in the './keymap/template' directory.++  -------------------------------------------------------------------------- |#++(deflayer numbers+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    XX   /    7    8    9    -    _    _    _+  _    _    _    _    _    XX   *    4    5    6    +    _    _+  _    _    \(   \)   .    XX   0    1    2    3    _    _+  _    _    _              _              _    _    _    _+)+++#| --------------------------------------------------------------------------+                  Optional: press-only / release-only buttons++  - `(press-only x)` : Send the *press* of x when this button is tapped+  - `(release-only x)` : Send the *release* of x when this button is tapped++  It is possible to define buttons that only press or release a virtual output+  button. They are useful in tap-macros, especially ones that are going to be+  executed in a 'known context'. Assume you want to use your physical alt button+  to *both* open a layer, but also to function as a basic `alt` key. This can be+  achieved by `(around met (layer-toggle my-layer))`. However, if you have a macro+  inside `my-layer` that taps alt, then this would release alt until the layer is+  reactivated (by physically releasing and repressing the `alt` key). The macro,+  however, can instead be affixed by `(press-only met)`, making the last step of+  the macro the reactivation of the `alt` key, solving the problem.++  -------------------------------------------------------------------------- |#+++#| --------------------------------------------------------------------------+                          Optional: modded buttons++  Let's start by exploring the various special buttons that are supported by+  KMonad by looking at 'modded' buttons, that is to say, buttons that activate+  some kind of 'mod', then perform some button, and finally release that 'mod'+  again.++  We have already seen an example of this style of button, our `kil` button is+  one such button. Let's look at it in more detail:+    C-A-del++  This looks like a simple declarative statement, but it's helpful to realize+  that is simply syntactic sugar around 2 function calls. This statement is+  equivalent to:+    (around-implicit ctl (around alt del))++  This highlights a core design principle in KMonad: we try to provide very+  simple buttons, and then we provide rules and functions for combining them+  into new buttons. Although note: still very much a work in progress.++  So, looking at this statement:+    (around foo bar)++  Here, `around` is a function that takes two buttons and creates a new button.+  This new button will, on a press, first press foo, then press bar, and on a+  release first release bar, and then foo. Once created, this new button can be+  passed to anything in KMonad that expects a button.++  There also exists some variants like `around-only` or `around-when-alone`.+  `around-only` also releases the outer button just before another button is+  pressed. If no other button has been pressed prior to it's release it releases+  the inner than the outer button similarly to `around`.++  `around-when-alone` is another variant which works like `around-only` but+  represses the outer button when all other which have been pressed after+  itself have been released.++  `around-implicit` is one of the above variants and is specified `defcfg` block.++  We have already seen other examples of modded buttons, \(, \), *, and +. There+  are no keycodes for these buttons in KMonad, but they are buttons. They simply+  evaluate to `(around-implicit lsft x)`. All shifted numbers have their+  corresponding characters, the same is true for all capitals,+  and < > : ~ " | { } \_ + and ?.++  To wrap up 'modded-buttons', let's look back at C-A-del. We have 8 variants:+    C- : (around-implicit lctl X)+    A- : (around-implicit lalt X)+    M- : (around-implicit lmet X)+    S- : (around-implicit lsft X)++  Then RC-, RA-, RM-, and RS- behave exactly the same, except using the+  right-modifier.++  These can be combined however you please:+    C-A-M-S-x          ;; Perfectly valid+    C-%                ;; Perfectly valid: same as C-S-5+    C-RC-RA-A-M-S-RS-m ;; Sure, but why would you?++  Also, note that although we provide special syntax for certain modifiers,+  these buttons are in no way 'special' in KMonad. There is no concept of+  'modifier':+    (around a (around b c)) ;; Perfectly valid++  -------------------------------------------------------------------------- |#++(defalias++  ;; Something useful+  cpy C-c+  pst C-v+  cut C-x++  ;; Something silly+  md1 (around a (around b c))    ;; abc+  md2 (around a (around-only lsft b)) ;; aB+  md3 C-A-M-S-l+  md4 (around % b)               ;; BEWARE: %B, not %b, do you see why?+  md5 (around-when-alone lctl c)+)++#| --------------------------------------------------------------------------+                          Optional: sticky keys++  KMonad also supports so called "sticky keys".  These are keys that will+  behave as if they were pressed after just tapping them.  This behaviour+  wears off after the next button is pressed, which makes them ideal for+  things like a quick control or shift.  For example, tapping a sticky and+  then pressing `abc' will result in `Abc'.++  You can create these keys with the `sticky-key' keyword:++    (defalias+      slc (sticky-key 500 lctl))++  The number after `sticky-key' is the timeout you want, in milliseconds.  If+  a key is tapped and that time has passed, it won't act like it's pressed+  down when we receive the next keypress.++  It is also possible to combine sticky keys.  For example, to+  get a sticky shift+control you can do++    (defalias+      ssc (around+           (sticky-key 500 lsft)+           (sticky-key 500 lctl)))++  -------------------------------------------------------------------------- |#++;; Let's make both shift keys sticky+(defalias+  sl (sticky-key 300 lsft)+  sr (sticky-key 300 rsft))++#| --------------------------------------------------------------------------+                           Optional: stepped buttons++  Stepped buttons are a bit out there. They allow you to define a button+  which does something different depending on how often you have pressed it+  in the past.+  Why is this useful?+  Simple, if you want a ctrl-lock which should work like caps-lock but for+  ctrl you can use `stepped` in combination with `press-only` and create+  `@ctl-lck` (see below).++  -------------------------------------------------------------------------- |#++(defalias+  ctl-lck (stepped (press-only lctl) (release-only lctl)))+++;; Now we define the 'tst' button as opening and closing a bunch of layers at+;; the same time. If you understand why this works, you're starting to grok+;; KMonad.+;;+;; Explanation: we define a bunch of testing-layers with buttons to illustrate+;; the various options in KMonad. Each of these layers makes sure to have its+;; buttons not overlap with the buttons from the other layers, and specifies all+;; its other buttons as transparent. When we use the nested `around` statement,+;; whenever we push the button linked to '@tst' (check `qwerty` layer, we bind+;; it to `rctl`), any button we press when holding `rctl` will be pressed in the+;; context of those 4 layers overlayed on the stack. When we release `rctl`, all+;; these layers will be popped again.+(defalias tst (around (layer-toggle macro-test)+                (around (layer-toggle layer-test)+                  (around (layer-toggle around-next-test)+                    (around (layer-toggle command-test)+                            (layer-toggle modded-test))))))++(deflayer modded-test+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    @md4 _    _    _    _    _    _    _    _    _    _    _+  _    _    @md1 @md2 @md3 _    _    _    _    _    _    _    _+  _    _    @cut @cpy @pst _    _    _    _    _    _    _+  _    _    _              _              _    _    _    _+)++#| --------------------------------------------------------------------------+                              Optional: tap-macros++  Let's look at a button we haven't seen yet, tap-macros.++  `tap-macro` is a function that takes an arbitrary number of buttons and+  returns a new button. When this new button is pressed it rapidly taps all its+  stored buttons in quick succession except for its last button, which it only+  presses. This last button gets released when the `tap-macro` gets released.++  There are two ways to define a `tap-macro`, using the `tap-macro` function+  directly, or through the #() syntactic sugar. Both evaluate to exactly the+  same button.++    (tap-macro K M o n a d)+    #(K M o n a d)++  If you are going to use a `tap-macro` to perform a sequence of actions inside+  some program you probably want to include short pauses between inputs to give+  the program time to register all the key-presses. Therefore we also provide+  the 'pause' function, which simply pauses processing for a certain amount of+  milliseconds. Pauses can be created like this:++    (pause 20)+    P20++  You can also pause between each key stroke by specifying the `:delay' keyword,+  as well as a time in ms, at the end of a `tap-macro':++    (tap-macro K M o n a d :delay 5)+    #(K M o n a d :delay 5)++  The above would be equivalent to++    (tap-macro K P5 M P5 o P5 n P5 a P5 d)++  The `tap-macro-release` is like `tap-macro`, except that it+  waits to press the last button when the `tap-macro-release`+  gets released.  It might be useful when combined with a+  footswitch that sends keyboard scan codes.++    (tap-macro-release i K M o n a d esc)++  WARNING: DO NOT STORE YOUR PASSWORDS IN PLAIN TEXT OR IN YOUR KEYBOARD++  I know it might be tempting to store your password as a macro, but there are 2+  huge risks:+  1. You accidentally leak your config and expose your password+  2. Anyone who knows about the button can get clear-text representation of your+     password with any text editor, shell, or text-input field.++  Support for triggering shell commands directly from KMonad is described in the+  command buttons section below.++  This concludes this public service announcement.++  -------------------------------------------------------------------------- |#++(defalias+  mc1 #(K M o n a d)+  mc2 #(C-c P50 A-tab P50 C-v) ;; Careful, this might do something+  mc3 #(P200 h P150 4 P100 > < P50 > < P20 0 r z 1 ! 1 ! !)+  mc4 (tap-macro a (pause 50) @md2 (pause 50) c)+  mc5 (tap-macro-release esc esc esc)+  mc6 #(@mc3 spc @mc3 spc @mc3)+)++(deflayer macro-test+  _    @mc1 @mc2 @mc3 @mc4 @mc5 @mc6 _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _              _              _    _    _    _+)+++#| --------------------------------------------------------------------------+                          Optional: layer manipulation++  You have already seen the basics of layer-manipulation. The `layer-toggle`+  button. This button adds a layer to the top of KMonad's layer stack when+  pressed, and removes it again when released. There are a number of other ways+  to manipulate the layer stack, some safer than others. Let's go through all of+  them from safest to least safe:++  `layer-toggle` works as described before, 2 things to note:+  1. If you are confused or worried about pressing a key, changing layers, and+     then releasing a key and this causing issues: don't be. KMonad handles+     presses and releases in very different ways. Presses get passed directly to+     the stacked keymap as previously described. When a KMonad button has its+     press-action triggered, it then registers a callback that will catch its+     own release before we ever touch the keymap. This guarantees that the+     button triggered by the press of X *will be* the button whose release is+     triggered by the release of X (the release of X might trigger other things+     as well, but that is besides the point.)+  2. If `layer-toggle` can only ever add and then necessarily remove 1 layer+     from the stack, then it will never cause a permanent change, and is+     perfectly safe.++  `layer-delay`, once pressed, temporarily switches to some layer for some+  milliseconds. Just like `layer-toggle` this will never permanently mess-up the+  layer stack. This button was initially implemented to provide some+  'leader-key' style behavior. Although I think in the future better solutions+  will be available. For now this will temporarily add a layer to the top of the+  stack:+    (layer-delay 500 my-layer)++  `layer-next`, once pressed, primes KMonad to handle the next press from some+  arbitrary layer. This aims to fill the same usecase as `layer-delay`: the+  beginnings of 'leader-key' style behavior. I think this whole button will get+  deleted soon, because the more general `around-next` now exists (see below)+  and this is nothing more than:+    (around-next (layer-toggle layer-name))+  Until then though, use `layer-next` like this:+    (layer-next layer-name)++  `layer-switch`: change the base-layer of KMonad. As described at the top of+  this document, the first `deflayer` statement is the layer that is active when+  KMonad starts. Since `layer-toggle` can only ever add on and remove from the+  top of that, it can never change the base-layer. The following button will+  unregister the bottom-most layer of the keymap, and replace it with another+  layer:+    (layer-switch my-layer)++  This is where things start getting potentially dangerous (i.e. get KMonad into+  an unusable state until a restart has occurred). It is perfectly possible to+  switch into a layer that you can never get out of. Or worse, you could+  theoretically have a layer full of only `XX`s and switch into that, rendering+  your keyboard unusable until you somehow manage to kill KMonad (without using+  your keyboard).++  However, when handled well, `layer-switch` is very useful, letting you switch+  between 'modes' for your keyboard. I have a tiny keyboard with a weird keymap,+  but I switch into a simple 'qwerty' keymap shifted 1 button to the right for+  gaming. Just make sure that any 'mode' you switch into has a button that+  allows you to switch back out of the 'mode' (or content yourself restarting+  KMonad somehow).++  `layer-add` and `layer-rem`. This is where you can very quickly cause yourself+  a big headache. Originally I didn't expose these operations, but someone+  wanted to use them, and I am not one to deny someone else a chainsaw. As the+  names might give away:+    (layer-add name) ;; Add a layer to the top of the stack+    (layer-rem name) ;; Remove a layer by name (noop if no such layer)++  To use `layer-add` and `layer-rem` well, you should take a moment to think+  about how to create a layout that will prevent you from getting into+  situations where you enter a key-configuration you cannot get out of again.+  These two operations together, however, are very useful for activating a+  permanent overlay for a while. This technique is illustrated in the tap-hold+  overlay a bit further down.+++  -------------------------------------------------------------------------- |#++(defalias++  yah (layer-toggle asking-for-trouble) ;; Completely safe+  nah (layer-add asking-for-trouble)    ;; Completely unsafe++  ld1 (layer-delay 500 numbers) ;; One way to get a leader-key+  ld2 (layer-next numbers)      ;; Another way to get a leader key++  ;; NOTE, this is safe because both `qwerty` and `colemak` contain the `@tst`+  ;; button which will get us to the `layer-test` layer, which itself contains+  ;; both `@qwe` and `@col`.+  qwe (layer-switch qwerty) ;; Set qwerty as the base layer+  col (layer-switch colemak) ;; Set colemak as the base layer+)+(deflayer layer-test+  @qwe _    _    _    _    _    _    _    _    _    _    @add _    @nah+  @col _    _    _    _    _    _    _    _    _    _    _    _    @yah+  _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    @ld1 @ld2 _+  _    _    _              _              _    _    _    _+)++;; Exactly like qwerty, but with the letters switched around+(deflayer colemak+  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc+  tab  q    w    f    p    g    j    l    u    y    ;    [    ]    \+  @xcp a    r    s    t    d    h    n    e    i    o    '    ret+  @sl  z    x    c    v    b    k    m    ,    .    /    @sr+  lctl @num lalt           spc            ralt rmet @sym @tst+)++(defalias lol #(: - D))++;; Contrived example+(deflayer asking-for-trouble+  @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol+  @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol+  @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol+  @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol @lol+  @lol @lol @lol           @lol           @lol @lol @lol @lol+)++;; One way to safely use layer-add and layer-rem: the button bound to layer-add+;; is the same button bound to layer-rem in the layer that `add` adds to the+;; stack. I.e., it becomes impossible to add or remove multiple copies of a+;; layer.+(defalias+  add (layer-add multi-overlay) ;; multi-overlay is defined in the next+  rem (layer-rem multi-overlay) ;; section below this+)++#| --------------------------------------------------------------------------+                          Optional: Multi-use buttons++  Perhaps one of the most useful features of KMonad, where a lot of work has+  gone into, but also an area with many buttons that are ever so slightly+  different. The naming and structuring of these buttons might change sometime+  soon, but for now, this is what there is.++  For the next section being able to talk about examples is going to be handy,+  so consider the following scenario and mini-language that will be the same+  between scenarios:++    - We have some button `foo` that will be different between scenarios+    - `foo` is bound to 'Esc' on the input keyboard+    - the letters a s d f are bound to themselves+    - Px signifies the press of button x on the keyboard+    - Rx signifies the release of said button+    - Tx signifies the sequential and near instantaneous press and release of x+    - 100 signifies 100ms pass++  So for example:+    Tesc Ta:+      tap of 'Esc' (triggering `foo`), tap of 'a' triggering `a`+    Pesc 100 Ta Tb Resc:+      press of 'Esc', 100ms pause, tap of 'a', tap of 'b', release of 'Esc'++  The `tap-next` button takes 2 buttons, one for tapping, one for holding, and+  combines them into a single button. When pressed, if the next event is its own+  release, we tap the 'tapping' button. In all other cases we first press the+  'holding' button then we handle the event. Then when the `tap-next` gets+  released, we release the 'holding' button.++  So, using our mini-language, we set foo to:+    (tap-next x lsft)+  Then:+    Tesc            -> x+    Tesc Ta         -> xa+    Pesc Ta Resc    -> A+    Pesc Ta Tr Resc -> AR++  The `tap-hold` button is very similar to `tap-next` (a theme, trust me). The+  difference lies in how the decision is made whether to tap or hold. A+  `tap-hold` waits for a particular timeout, if the `tap-hold` is released+  anywhere before that moment we execute a tap immediately. If the timeout+  occurs and the `tap-hold` is still held, we switch to holding mode.++  The additional feature of a `tap-hold` is that it pauses event-processing+  until it makes its decision and then rolls back processing when the decision+  has been made.++  So, again with the mini-language, we set foo to:+    (tap-hold 200 x lsft) ;; Like tap-next, but with a 200ms timeout+  Then:+    Tesc            -> x+    Tesc Ta         -> xa+    Pesc 300 a      -> A (the moment you press a)+    Pesc a 300      -> A (after 200 ms)+    Pesc a 100 Resc -> xa (both happening immediately on Resc)++  The `tap-hold-next` button is a combination of the previous 2. Essentially,+  think of it as a `tap-next` button, but it also switches to held after a+  period of time. This is useful, because if you have a (tap-next ret ctl) for+  example, and you press it thinking you want to press C-v, but then you change+  your mind, you now cannot release the button without triggering a 'ret', that+  you then have to backspace. With the `tap-hold-next` button, you simply+  outwait the delay, and you're good. I see no benefit of `tap-next` over+  `tap-hold-next` with a decent timeout value.++  You can use the `:timeout-button` keyword to specify a button other than the+  hold button which should be held when the timeout expires. For example, we+  can construct a button which types one x when tapped, multiple x's when held,+  and yet still acts as shift when another button is pressed before the timeout+  expires. So, using the minilanguage and foo as:+    (tap-hold-next 200 x lsft :timeout-button x)+  Then:+    Tesc           -> Tx+    Pesc 100 a     -> A (the moment you press a)+    Pesc 5000 Resc -> xxxxxxx (some number of auto-repeated x's)++  Note that KMonad does not itself auto-repeat the key. In this last example,+  KMonad emits 200 Px 4800 Rx, and the operating system's auto-repeat feature,+  if any, emits multiple x's because it sees that the x key is held for 4800 ms.++  A note about tap action duration:+  For simplicity we reuse the `tap-next` example above, set foo to:+    (tap-next x lsft)+  Now, any keystroke performed by baseline human will have some duration, a+  'Tesc' is actually 'Pesc <some time passed> Resc'.  A true tap 'Tesc' with no+  delay between the press and release will sometime experience registration+  problems in programs.  However the tap action performed by KMonad IS this kind+  of 'true tap', that is:+    Tesc (Pesc 100 Resc) -> Px Rx+  For various reasons we do not want KMonad to have some default duration in the+  tap action it performs.  If you are having issues in programs, you can instead+  use the aforementioned `around` and `pause` function to give the tap action+  some duration.  Set foo to:+    (tap-next (around x (pause 2000)) lsft)+  or equivalently:+    (tap-next (around x P2000) lsft)+  then we have:+    Tesc (Pesc 100 Resc) -> Px 2000 Rx+  2000 ms is just for you to distinctively see the effect, in practice 35 ms+  should be enough for most scenarios (slightly longer than 2 frames in 60 fps).++  The `tap-next-release` is like `tap-next`, except it decides whether to tap or+  hold based on the next release of a key that was *not* pressed before us. This+  also performs rollback like `tap-hold`. So, using the minilanguage and foo as:+    (tap-next-release x lsft)+  Then:+    Tesc Ta         -> xa+    Pa Pesc Ra Resc -> ax (because 'a' was already pressed when we started, so+                           foo decides it is tapping)+    Pesc Pa Resc Ra -> xa (because the first release we encounter is of esc)+    Pesc Ta Resc    -> A (because a was pressed *and* released after we started,+                          so foo decides it is holding)++  `tap-next-press` is also a lot like `tap-next`, but decides whether to tap or+  hold based on whether another key is pressed before this one is released.+  Using the minilanguage:+    (tap-next-press x lsft)+  Then:+    Tesc Ta -> xa+    Pa Pesc Ra Resc -> ax (because esc is released before another key is pressed)+    Pesc Pa Resc Ra -> A (because a is pressed before esc is released)+    Pesc Ta Resc    -> A (a is pressed before esc is released here as well)++  These increasingly stranger buttons are, I think, coming from the stubborn+  drive of some of my more eccentric (and I mean that in the most positive way)+  users to make typing with modifiers on the home-row more comfortable.+  Especially layouts that encourage a lot of rolling motions are nicer to use+  with the `release` style buttons.++  The `tap-hold-next-release` (notice a trend?) is just like `tap-next-release`,+  but it comes with an additional timeout that, just like `tap-hold-next` will+  jump into holding-mode after a timeout.++  I honestly think that `tap-hold-next-release`, although it seems the most+  complicated, probably is the most comfortable to use. But I've put all of them+  in a testing layer down below, so give them a go and see what is nice.++  -------------------------------------------------------------------------- |#+++(defalias+  xtn (tap-next x lsft)         ;; Shift that does 'x' on tap+  xth (tap-hold 400 x lsft)     ;; Long delay for easier testing+  thn (tap-hold-next 400 x lsft)+  tnr (tap-next-release x lsft)+  tnp (tap-next-press x lsft)+  tnh (tap-hold-next-release 2000 x lsft)++  ;; Used it the colemak layer+  xcp (tap-hold-next 400 esc ctl)+)++;; Some of the buttons used here are defined in the next section+(deflayer multi-overlay+  @mt  _    _    _    _    _    _    _    _    _    _    _    @rem _+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  @thn _    _    _    _    _    _    _    _    _    _    _    _+  @xtn _    _    _    _    _    _    _    _    _    _    @xth+  @tnr @tnp _              _              _    _    _    @tnh+)+++#| --------------------------------------------------------------------------+                              Optional: Multi-tap++  Besides the tap-hold style buttons there is another multi-use button (with+  only 1 variant, at the moment). The `multi-tap`.++  A `multi-tap` codes for different buttons depending on how often it is tapped.+  It is defined by a series of delays and buttons, followed by a last button+  without delay. As long as you tap the `multi-tap` within the delay specified,+  it will jump to the next button. Once the delay is exceeded the selected+  button is pressed. If the last button in the list is reached, it is+  immediately pressed. When another key is pressed down while we're tapping,+  `multi-tap' also immediately exits and taps the current button.++  Note that you can actually hold the button, so in the below example, going:+  tap-tap-hold (wait 300ms) will get you a pressed c, until you release again.++  -------------------------------------------------------------------------- |#++(defalias+  mt  (multi-tap 300 a 300 b 300 c 300 d e))+++#| --------------------------------------------------------------------------+                              Optional: Around-next++  The `around-next` function creates a button that primes KMonad to perform the+  next button-press inside some context. This could be the context of 'having+  Shift pressed' or 'being inside some layer' or, less usefully, 'having d+  pressed'. It is a more general and powerful version of `layer-next`.++  There is also an `around-next-timeout` button that does the same thing as+  `around-next`, except that if some other button press is not detected within+  some timeout, some other button is tapped. This can be used to create a+  leader-key that simply times out (by passing a non-button), or a key that can+  still function as a normal key, but also as a leader key when used slowly.++  The variant `around-next-single` does the same thing as `around-next` except+  it really only concerns the next event (press or release).+  Consider the button `@ns` (see below) in the following scenario:++    P@ns Pa Tb Ra Tc -> A B c++  If we had used `@ns'` instead, we would get a result we would expect more:++    P@ns' Pa Tb Ra Tc -> A b c++  but we get the following problem instead, since it handles any following+  event, not just key presses:++    Pa P@ns' Ra Tb -> a b++  Another button which may be seen as a variant is `before-after-next`.+  It taps a button before and another after the actual button.+  It can be used to recreate `(around-next a)` via:++    (before-after-next (press-only a) (release-only a))++  but it can also be used in a more productive sense like `@inn`.+  If you can switch with `tab` and `S-tab` between something,+  you can use `@inn` to type your next key there:++    T@inn Pctl Ta Rctl++  would commonly select everything in the next container.++  I think expansion of this button-style is probably the future of leader-key,+  hydra-style functionality support in KMonad.++  -------------------------------------------------------------------------- |#++(defalias+  ns  (around-next sft)        ;; Shift until the release of the next press+  ns' (around-next-single sft) ;; Shift the next event+  nnm (around-next @num)       ;; Perform next press in numbers layer+  ntm (around-next-timeout 500 sft XX)+  inn (before-after-next tab S-tab)+++)++(deflayer around-next-test+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  @ns  _    _    _    _    _    _    _    _    _    _    _    _+  @nnm _    _    _    _    _    _    _    _    _    _    _+  @ntm _    _              _              _    _    _    _+)++#| --------------------------------------------------------------------------+                        Optional: Compose-key sequences++  Compose-key sequences are series of button-presses that your operating system+  will interpret as the insertion of a special character, like accented+  characters, or various special-languages. In that sense, they are just+  syntactic sugar for keyboard macros.++  To get this to work on Linux you will need to set your compose-key with a tool+  like `setxkbmap', as well as tell KMonad that information. See the `defcfg'+  block at the top of this file for a working example. Note that you need to+  wait ever so slightly for the keyboard to register with Linux before the+  command gets executed, that's why the `sleep 1`. Also, note that all the+  `/run/current-system' stuff is because the author uses NixOS. Just find a+  shell-command that will:++    1. Sleep a moment+    2. Set the compose-key to your desired key++  Please be aware that what `setxkbmap' calls the `menu' key is not actually the+  `menu' key! If you want to use the often suggested++      setxkbmap -option compose:menu++  you will have to set your compose key within KMonad to `compose' and not+  `menu'.++  After this, this should work out of the box under Linux. Windows does not+  recognize the same compose-key sequences, but WinCompose will make most of the+  sequences line up with KMonad: http://wincompose.info/+  This has not in any way been tested on Mac.++  In addition to hard-coded symbols, we also provide 'uncompleted' macros. Since+  a compose-key sequence is literally just a series of keystrokes, we can omit+  the last one, and enter the sequence for 'add an umlaut' and let the user then+  press some letter to add this umlaut to. These are created using the `+"`+  syntax.++  -------------------------------------------------------------------------- |#++(defalias+  sym (layer-toggle symbols)++)++(deflayer symbols+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  _    ä    é    ©    _    _    _    _    _    _    _    _    _    _+  _    +'   +~   +`   +^   _    _    _    _    _    _    _    _+  _    +"   +,   _    _    _    _    _    _    _    _    _+  _    _    _              _              _    _    _    _)+++#| --------------------------------------------------------------------------+                        Optional: Command buttons++  Currently we also provide the ability to launch arbitrary shell-commands from+  inside KMonad. These commands are simply handed off to the command-shell+  without any further checking or waiting.++  NOTE: currently only tested on Linux, but should work on any platform, as long+  as the command is valid for that platform.++  The `cmd-button' function takes two arguments, the second one of which is+  optional. These represent the commands to be executed on pressing and+  releasing the button respectively.++  BEWARE: never run anyone's configuration without looking at it. You wouldn't+  want to push:++    (cmd-button "rm -rf ~/*") ;; Delete all this user's data+++  -------------------------------------------------------------------------- |#++(defalias+  dat (cmd-button "date >> /tmp/kmonad_example.txt")   ;; Append date to tmpfile+  pth (cmd-button "echo $PATH > /tmp/kmonad_path.txt") ;; Write out PATH+  ;; `dat' on press and `pth' on release+  bth (cmd-button "date >> /tmp/kmonad_example.txt"+                  "echo $PATH > /tmp/kmonad_path.txt")+)++(deflayer command-test+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    _    _    _    _+  _    _    _    _    _    _    _    _    _    @dat @pth _+  _    _    _              _              _    _    _    _+)
kmonad.cabal view
@@ -1,150 +1,208 @@-cabal-version:      2.2-name:               kmonad-version:            0.4.2-license:            MIT-license-file:       LICENSE-maintainer:         janssen.dhj@gmail.com-author:             David Janssen-synopsis:           Advanced keyboard remapping utility+cabal-version: 2.2++name:           kmonad+category:       Application+version:        0.4.3+synopsis:       Advanced keyboard remapping utility+author:         David Janssen+maintainer:     janssen.dhj@gmail.com+license:        MIT+license-file:   LICENSE+build-type:     Simple description:-    KMonad is a cross-platform command-line utility that runs as a daemon. It-    captures keyboard input (exactly how depends on the OS) and remaps it. The-    mapping is highly configurable, and provides options like (transparent) layer-    overlays, tap-mod buttons, multi-tap buttons, leader-key style buttons, and-    keyboard macros. Functionality heavily inspired by the QMK-firmware.+  KMonad is a cross-platform command-line utility that runs as a daemon. It+  captures keyboard input (exactly how depends on the OS) and remaps it. The+  mapping is highly configurable, and provides options like (transparent) layer+  overlays, tap-mod buttons, multi-tap buttons, leader-key style buttons, and+  keyboard macros. Functionality heavily inspired by the QMK-firmware. -category:           Application-build-type:         Simple-extra-source-files: changelog.md+extra-source-files:+    changelog.md+    doc/quick-reference.md+    keymap/tutorial.kbd  flag kext     description: build against the kext [macOS only]-    default:     False+    default: False  flag dext     description: build against the dext [macOS only]-    default:     False+    default: False  library-    exposed-modules:-        KMonad.App-        KMonad.App.Main-        KMonad.App.Types-        KMonad.Args-        KMonad.Args.Cmd-        KMonad.Args.Parser-        KMonad.Args.Joiner-        KMonad.Args.TH-        KMonad.Args.Types-        KMonad.Model-        KMonad.Model.Action-        KMonad.Model.BEnv-        KMonad.Model.Button-        KMonad.Model.Dispatch-        KMonad.Model.Hooks-        KMonad.Model.Keymap-        KMonad.Model.Sluice-        KMonad.Gesture-        KMonad.Keyboard-        KMonad.Keyboard.ComposeSeq-        KMonad.Keyboard.IO-        KMonad.Keyboard.Keycode-        KMonad.Keyboard.Ops-        KMonad.Keyboard.Types-        KMonad.Parsing-        KMonad.Prelude-        KMonad.Prelude.Imports-        KMonad.Prelude.Definitions-        KMonad.Util-        KMonad.Util.LayerStack-        KMonad.Util.MultiMap-        Paths_kmonad--    hs-source-dirs:     src-    autogen-modules:    Paths_kmonad-    default-language:   Haskell2010-    default-extensions:-        ConstraintKinds DeriveFunctor DeriveGeneric DeriveTraversable-        FlexibleContexts FlexibleInstances FunctionalDependencies-        GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses-        MultiWayIf NoImplicitPrelude OverloadedStrings RankNTypes-        TemplateHaskell TupleSections TypeFamilies--    ghc-options:        -Wall -Wno-name-shadowing -Wno-unused-imports-    build-depends:-        base <4.18,-        cereal <0.6,-        lens <5.3,-        megaparsec <9.4,-        mtl <2.3,-        optparse-applicative <0.18,-        resourcet <1.3,-        rio <0.2,-        time <1.13,-        unliftio <0.3,-        template-haskell <2.20--    if os(linux)-        exposed-modules:-            KMonad.Keyboard.IO.Linux.DeviceSource-            KMonad.Keyboard.IO.Linux.Types-            KMonad.Keyboard.IO.Linux.UinputSink--        c-sources:       c_src/keyio.c-        build-depends:   unix <2.8--    if os(windows)-        exposed-modules:-            KMonad.Keyboard.IO.Windows.LowLevelHookSource-            KMonad.Keyboard.IO.Windows.SendEventSink-            KMonad.Keyboard.IO.Windows.Types+  default-language:+      Haskell2010+  hs-source-dirs:+      src+  ghc-options:+    -Wall+    -Wno-name-shadowing+    -Wno-unused-imports+  build-depends:+      base >= 4.12 && < 5+    , cereal+    -- , hspec+    , lens+    , megaparsec+    , mtl+    , optparse-applicative+    , resourcet+    , rio+    , time+    , unliftio+    , template-haskell+  default-extensions:+      ConstraintKinds+      DeriveFunctor+      DeriveGeneric+      DeriveDataTypeable+      DeriveTraversable+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GeneralizedNewtypeDeriving+      LambdaCase+      MultiParamTypeClasses+      MultiWayIf+      NamedFieldPuns+      NoImplicitPrelude+      OverloadedStrings+      RankNTypes+      TemplateHaskell+      TupleSections+      TypeFamilies -        c-sources:       c_src/keyio_win.c-        build-depends:   Win32+  autogen-modules:+      Paths_kmonad -    if os(osx)-        exposed-modules:-            KMonad.Keyboard.IO.Mac.IOKitSource-            KMonad.Keyboard.IO.Mac.KextSink-            KMonad.Keyboard.IO.Mac.Types+  exposed-modules:+      KMonad.App+      KMonad.App.Main+      KMonad.App.Types+      KMonad.Args+      KMonad.Args.Cmd+      KMonad.Args.Parser+      KMonad.Args.Joiner+      KMonad.Args.TH+      KMonad.Args.Types+      KMonad.Model+      KMonad.Model.Action+      KMonad.Model.BEnv+      KMonad.Model.Button+      KMonad.Model.Dispatch+      KMonad.Model.Hooks+      KMonad.Model.Keymap+      KMonad.Model.Sluice+      KMonad.Gesture+      KMonad.Keyboard+      KMonad.Keyboard.ComposeSeq+      KMonad.Keyboard.IO+      KMonad.Keyboard.Keycode+      KMonad.Keyboard.Ops+      KMonad.Keyboard.Types+      KMonad.Parsing+      KMonad.Prelude+      KMonad.Prelude.Imports+      KMonad.Prelude.Definitions+      KMonad.Util+      KMonad.Util.LayerStack+      KMonad.Util.MultiMap+      Paths_kmonad -        frameworks:      CoreFoundation IOKit-        extra-libraries: c++-        build-depends:   unix <2.8+  if os(linux)+    exposed-modules:+      KMonad.Keyboard.IO.Linux.DeviceSource+      KMonad.Keyboard.IO.Linux.Types+      KMonad.Keyboard.IO.Linux.UinputSink+    c-sources:+      c_src/keyio.c+    build-depends:+      unix -        if flag(kext)-            cxx-options: -std=c++14-            cxx-sources: c_src/mac/kext.cpp+  if os(windows)+    exposed-modules:+      KMonad.Keyboard.IO.Windows.LowLevelHookSource+      KMonad.Keyboard.IO.Windows.SendEventSink+      KMonad.Keyboard.IO.Windows.Types+    c-sources:+      c_src/keyio_win.c+    build-depends:+      Win32 -        if flag(dext)-            cxx-options: -std=c++2a-            cxx-sources: c_src/mac/dext.cpp+  if os(darwin)+    exposed-modules:+      KMonad.Keyboard.IO.Mac.IOKitSource+      KMonad.Keyboard.IO.Mac.KextSink+      KMonad.Keyboard.IO.Mac.Types+    if flag(kext)+      cxx-sources:+        c_src/mac/kext.cpp+      cxx-options:+        -std=c++14+    if flag(dext)+      cxx-sources:+        c_src/mac/dext.cpp+      cxx-options:+        -std=c++2a+    extra-libraries:+      c+++    build-depends:+      unix+    frameworks:+      CoreFoundation+      IOKit  executable kmonad-    main-is:          Main.hs-    hs-source-dirs:   app-    default-language: Haskell2010-    ghc-options:      -threaded -rtsopts -with-rtsopts=-N-    build-depends:-        base <4.18,-        kmonad+  ghc-options:+      -threaded+      -rtsopts+      -with-rtsopts=-N+  main-is:+      Main.hs+  default-language:+      Haskell2010+  hs-source-dirs:+      app+  build-depends:+      base >= 4.12 && < 5+    , kmonad  test-suite spec-    type:               exitcode-stdio-1.0-    main-is:            Spec.hs-    build-tool-depends: hspec-discover:hspec-discover >=2 && <3-    hs-source-dirs:     test-    other-modules:      KMonad.GestureSpec-    default-language:   Haskell2010-    default-extensions:-        ConstraintKinds DeriveFunctor DeriveGeneric DeriveTraversable-        FlexibleContexts FlexibleInstances FunctionalDependencies-        GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses-        MultiWayIf NoImplicitPrelude OverloadedStrings RankNTypes-        TemplateHaskell TupleSections TypeFamilies--    ghc-options:        -Wall-    build-depends:-        base <4.18,-        kmonad,-        hspec <2.11+  main-is: Spec.hs+  type: exitcode-stdio-1.0+  hs-source-dirs:+      test+  ghc-options:+      -Wall+  build-depends:+      base >= 4.12 && < 5+    , kmonad+    , hspec+    , rio+  other-modules:+      KMonad.ButtonDocSpec+      KMonad.GestureSpec+      KMonad.ComposeSeqSpec+  default-language:+      Haskell2010+  build-tool-depends: hspec-discover:hspec-discover == 2.*+  default-extensions:+      ConstraintKinds+      DeriveFunctor+      DeriveGeneric+      DeriveDataTypeable+      DeriveTraversable+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GeneralizedNewtypeDeriving+      LambdaCase+      MultiParamTypeClasses+      MultiWayIf+      NamedFieldPuns+      NoImplicitPrelude+      OverloadedStrings+      RankNTypes+      TemplateHaskell+      TupleSections+      TypeFamilies
src/KMonad/App/Main.hs view
@@ -55,6 +55,7 @@ -- 3. Maybe start KMonad runCmd :: Cmd -> IO () runCmd c = do+  hSetBuffering stdout LineBuffering   o <- logOptionsHandle stdout False <&> setLogMinLevel (c^.logLvl)   withLogFunc o $ \f -> runRIO f $ do     cfg <- loadConfig c@@ -79,7 +80,7 @@   -- Wait a bit for the user to release the 'Return' key with which they started KMonad   threadDelay $ fromIntegral (cfg^.startDelay) * 1000 -  -- Acquire the keysource and keysink+  -- Acquire the key source and key sink   snk <- using $ cfg^.keySinkDev   src <- using $ cfg^.keySourceDev 
src/KMonad/App/Types.hs view
@@ -144,6 +144,6 @@     spawnCommand cmd = void $ createProcess_ "spawnCommand"       (shell cmd){ -- We don't want the child process to inherit things like                    -- our keyboard grab (this would, for example, make it-                   -- impossible for a command to restart kmonad).+                   -- impossible for a command to restart KMonad).                    close_fds   = True                  }
src/KMonad/Args.hs view
@@ -60,7 +60,7 @@   cliList :: DefSettings   cliList = catMaybes $        map flagToMaybe [cmd^.cmdAllow, cmd^.fallThrgh]-    <> [cmd^.iToken, cmd^.oToken, cmd^.cmpSeq, cmd^.initStr]+    <> [cmd^.iToken, cmd^.oToken, cmd^.cmpSeq, cmd^.implArnd]    -- | Convert command line flags to a 'Maybe' type, where the non-presence, as   -- well as the default value of a flag will be interpreted as @Nothing@
src/KMonad/Args/Cmd.hs view
@@ -17,7 +17,7 @@ where  import KMonad.Prelude hiding (try)-import KMonad.Args.Parser (itokens, keywordButtons, noKeywordButtons, otokens, symbol, numP)+import KMonad.Args.Parser (itokens, keywordButtons, noKeywordButtons, otokens, symbol, numP, implArndButtons) import KMonad.Args.TH (gitHash) import KMonad.Args.Types (DefSetting(..)) import KMonad.Util@@ -44,8 +44,8 @@     -- All 'KDefCfg' options of a 'KExpr'   , _cmdAllow  :: DefSetting       -- ^ Allow execution of arbitrary shell-commands?   , _fallThrgh :: DefSetting       -- ^ Re-emit unhandled events?-  , _initStr   :: Maybe DefSetting -- ^ TODO: What does this do?   , _cmpSeq    :: Maybe DefSetting -- ^ Key to use for compose-key sequences+  , _implArnd  :: Maybe DefSetting -- ^ How to handle implicit `around`s   , _oToken    :: Maybe DefSetting -- ^ How to emit the output   , _iToken    :: Maybe DefSetting -- ^ How to capture the input   }@@ -83,8 +83,8 @@       <*> startDelayP       <*> cmdAllowP       <*> fallThrghP-      <*> initStrP       <*> cmpSeqP+      <*> implArndP       <*> oTokenP       <*> iTokenP @@ -131,15 +131,6 @@   <> help "Whether to simply re-emit unhandled events"   ) --- | TODO what does this do?-initStrP :: Parser (Maybe DefSetting)-initStrP = optional $ SInitStr <$> strOption-  (  long "init"-  <> short 't'-  <> metavar "STRING"-  <> help "TODO"-  )- -- | Key to use for compose-key sequences cmpSeqP :: Parser (Maybe DefSetting) cmpSeqP = optional $ SCmpSeq <$> option@@ -148,6 +139,16 @@   <> short 's'   <> metavar "BUTTON"   <> help "Which key to use to emit compose-key sequences"+  )++-- | How to handle implicit `around`s+implArndP :: Parser (Maybe DefSetting)+implArndP = optional $ SImplArnd <$> option+  (maybeReader $ \x -> implArndButtons ^? each . filtered ((x ==) . unpack . view _1) . _2)+  (  long "implicit-around"+  <> long "ia"+  <> metavar "AROUND"+  <> help "How to translate implicit arounds (`A`, `S-a`)"   )  -- | Where to emit the output
src/KMonad/Args/Joiner.hs view
@@ -50,7 +50,7 @@  import Control.Monad.Except -import RIO.List (headMaybe, intersperse, uncons)+import RIO.List (headMaybe, intersperse, uncons, sort, group) import RIO.Partial (fromJust) import qualified KMonad.Util.LayerStack  as L import qualified RIO.HashMap      as M@@ -65,11 +65,16 @@   | MissingBlock     Text   | DuplicateAlias   Text   | DuplicateLayer   Text+  | DuplicateSource  (Maybe Text)+  | DuplicateKeyInSource (Maybe Text) [Keycode]   | MissingAlias     Text   | MissingLayer     Text+  | MissingSource    (Maybe Text)   | MissingSetting   Text   | DuplicateSetting Text+  | DuplicateLayerSetting Text Text   | InvalidOS        Text+  | ImplArndDisabled   | NestedTrans   | InvalidComposeKey   | LengthMismatch   Text Int Int@@ -80,11 +85,22 @@     MissingBlock      t   -> "Missing at least 1 block of type: "    <> T.unpack t     DuplicateAlias    t   -> "Multiple aliases of the same name: "   <> T.unpack t     DuplicateLayer    t   -> "Multiple layers of the same name: "    <> T.unpack t+    DuplicateSource   t   -> case t of+      Just t  -> "Multiple sources of the same name: " <> T.unpack t+      Nothing -> "Multiple default sources"+    DuplicateKeyInSource   t ks   -> case t of+      Just t  -> "Keycodes appear multiple times in source `" <> T.unpack t <> "`:" <> ((' ' :) . show =<< ks)+      Nothing -> "Keycodes appear multiple times in default source: " <> ((' ' :) . show =<< ks)     MissingAlias      t   -> "Reference to non-existent alias: "     <> T.unpack t     MissingLayer      t   -> "Reference to non-existent layer: "     <> T.unpack t+    MissingSource     t   -> case t of+      Just t  -> "Reference to non-existent source: " <> T.unpack t+      Nothing -> "Reference to non-existent default source"     MissingSetting    t   -> "Missing setting in 'defcfg': "         <> T.unpack t     DuplicateSetting  t   -> "Duplicate setting in 'defcfg': "       <> T.unpack t+    DuplicateLayerSetting t s -> "Duplicate setting in 'deflayer '"  <> T.unpack t <> "': " <> T.unpack s     InvalidOS         t   -> "Not available under this OS: "         <> T.unpack t+    ImplArndDisabled      -> "Implicit around via `A` or `S-a` are disabled in your config"     NestedTrans           -> "Encountered 'Transparent' ouside of top-level layer"     InvalidComposeKey     -> "Encountered invalid button as Compose key"     LengthMismatch t l s  -> mconcat@@ -98,6 +114,7 @@ -- | Joining Config data JCfg = JCfg   { _cmpKey  :: Button  -- ^ How to prefix compose-sequences+  , _implArnd :: ImplArnd -- ^ How to handle implicit `around`s   , _kes     :: [KExpr] -- ^ The source expresions we operate on   } makeLenses ''JCfg@@ -105,6 +122,7 @@ defJCfg :: [KExpr] ->JCfg defJCfg = JCfg   (emitB KeyRightAlt)+  IAAround  -- | Monad in which we join, just Except over Reader newtype J a = J { unJ :: ExceptT JoinError (Reader JCfg) a }@@ -168,8 +186,8 @@   -- Extract the other blocks and join them into a keymap   let als = extract _KDefAlias es   let lys = extract _KDefLayer es-  src      <- oneBlock "defsrc" _KDefSrc-  (km, fl) <- joinKeymap src als lys+  let srcs = extract _KDefSrc es+  (km, fl) <- joinKeymap srcs als lys    pure $ CfgToken     { _snk   = o@@ -189,12 +207,14 @@ -- | Return a JCfg with all settings from defcfg applied to the env's JCfg getOverride :: J JCfg getOverride = do+  -- FIXME: duplicates don't throw errors   env <- ask   cfg <- oneBlock "defcfg" _KDefCfg   let getB = joinButton [] M.empty   let go e v = case v of         SCmpSeq b  -> getB b >>= maybe (throwError InvalidComposeKey)                                        (\b' -> pure $ set cmpKey b' e)+        SImplArnd ia -> pure $ set implArnd ia e         _ -> pure e   foldM go env cfg @@ -320,6 +340,12 @@ unnest :: J (Maybe Button) -> J Button unnest = (maybe (throwError NestedTrans) pure =<<) +fromImplArnd :: DefButton -> DefButton -> ImplArnd -> J DefButton+fromImplArnd _ _ IADisabled        = throwError ImplArndDisabled+fromImplArnd o i IAAround          = pure $ KAround o i+fromImplArnd o i IAAroundOnly      = pure $ KAroundOnly o i+fromImplArnd o i IAAroundWhenAlone = pure $ KAroundWhenAlone o i+ -- | Turn a button token into an actual KMonad `Button` value joinButton :: LNames -> Aliases -> DefButton -> J (Maybe Button) joinButton ns als =@@ -377,12 +403,16 @@     KTapHoldNextRelease ms t h mtb       -> jst $ tapHoldNextRelease (fi ms) <$> go t <*> go h <*> traverse go mtb     KTapNextPress t h  -> jst $ tapNextPress       <$> go t <*> go h+    KAroundOnly o i    -> jst $ aroundOnly         <$> go o <*> go i+    KAroundWhenAlone o i -> jst $ aroundWhenAlone  <$> go o <*> go i+    KAroundImplicit o i  -> joinButton ns als =<< fromImplArnd o i =<< view implArnd     KAroundNext b      -> jst $ aroundNext         <$> go b     KAroundNextSingle b -> jst $ aroundNextSingle <$> go b     KAroundNextTimeout ms b t -> jst $ aroundNextTimeout (fi ms) <$> go b <*> go t     KPause ms          -> jst . pure $ onPress (pause ms)     KMultiTap bs d     -> jst $ multiTap <$> go d <*> mapM f bs       where f (ms, b) = (fi ms,) <$> go b+    KStepped bs        -> jst $ steppedButton <$> mapM go bs     KStickyKey s d     -> jst $ stickyKey (fi s) <$> go d      -- Non-action buttons@@ -391,17 +421,37 @@   --------------------------------------------------------------------------------+-- $src++type Sources = M.HashMap (Maybe Text) DefSrc++-- | Build up a hashmap of text to source mappings.+joinSources :: [DefSrc] -> J Sources+joinSources = foldM joiner mempty+  where+   joiner :: Sources -> DefSrc -> J Sources+   joiner sources src@DefSrc{ _srcName = n, _keycodes = ks }+     | n `M.member` sources = throwError $ DuplicateSource n+     | not (null dups)      = throwError $ DuplicateKeyInSource n dups+     | otherwise            = pure $ M.insert n src sources+    where+     dups :: [Keycode]+     dups = concatMap (take 1) . filter ((> 1) . length) . group . sort $ ks++-------------------------------------------------------------------------------- -- $kmap  -- | Join the defsrc, defalias, and deflayer layers into a Keymap of buttons and -- the name signifying the initial layer to load.-joinKeymap :: DefSrc -> [DefAlias] -> [DefLayer] -> J (LMap Button, LayerTag)-joinKeymap _   _   []  = throwError $ MissingBlock "deflayer"-joinKeymap src als lys = do+joinKeymap :: [DefSrc] -> [DefAlias] -> [DefLayer] -> J (LMap Button, LayerTag)+joinKeymap []   _   _   = throwError $ MissingBlock "defsrc"+joinKeymap _    _   []  = throwError $ MissingBlock "deflayer"+joinKeymap srcs als lys = do   let f acc x = if x `elem` acc then throwError $ DuplicateLayer x else pure (x:acc)-  nms  <- foldM f [] $ map _layerName lys   -- Extract all names-  als' <- joinAliases nms als               -- Join aliases into 1 hashmap-  lys' <- mapM (joinLayer als' nms src) lys -- Join all layers+  nms   <- foldM f [] $ map _layerName lys     -- Extract all names+  als'  <- joinAliases nms als                 -- Join aliases into 1 hashmap+  srcs' <- joinSources  srcs                   -- Join all sources into 1 hashmap+  lys'  <- mapM (joinLayer als' nms srcs') lys -- Join all layers   -- Return the layerstack and the name of the first layer   pure (L.mkLayerStack lys', _layerName . fromJust . headMaybe $ lys) @@ -409,11 +459,17 @@ joinLayer ::      Aliases                       -- ^ Mapping of names to buttons   -> LNames                        -- ^ List of valid layer names-  -> DefSrc                        -- ^ Layout of the source layer+  -> Sources                       -- ^ Mapping of names to source layer   -> DefLayer                      -- ^ The layer token to join   -> J (Text, [(Keycode, Button)]) -- ^ The resulting tuple-joinLayer als ns src DefLayer{_layerName=n, _buttons=bs} = do+joinLayer als ns srcs l@(DefLayer n settings) = do+  let bs = settings ^.. each . _LButton+  assocSrc <- getAssocSrc l+  implAround <- getImplAround l +  src <- case M.lookup assocSrc srcs of+    Just src -> pure $ src^.keycodes+    Nothing  -> throwError $ MissingSource assocSrc   -- Ensure length-match between src and buttons   when (length bs /= length src) $     throwError $ LengthMismatch n (length bs) (length src)@@ -422,8 +478,20 @@   let f acc (kc, b) = joinButton ns als b >>= \case         Nothing -> pure acc         Just b' -> pure $ (kc, b') : acc-  (n,) <$> foldM f [] (zip src bs)+  maybe id (local . set implArnd) implAround $+    (n,) <$> foldM f [] (zip src bs) +getAssocSrc :: DefLayer -> J (Maybe Text)+getAssocSrc (DefLayer n settings) = case onlyOne (settings ^.. each . _LSrcName) of+  Right x        -> pure $ Just x+  Left None      -> pure Nothing+  Left Duplicate -> throwError $ DuplicateLayerSetting n "source"++getImplAround :: DefLayer -> J (Maybe ImplArnd)+getImplAround (DefLayer n settings) = case onlyOne (settings ^.. each . _LImplArnd) of+  Right x        -> pure $ Just x+  Left None      -> pure Nothing+  Left Duplicate -> throwError $ DuplicateLayerSetting n "implicit-around"  -------------------------------------------------------------------------------- -- $test
src/KMonad/Args/Parser.hs view
@@ -27,6 +27,8 @@   -- * Parsers for Tokens and Buttons   , otokens   , itokens+  , buttonP+  , implArndButtons   , keywordButtons   , noKeywordButtons   )@@ -182,7 +184,7 @@  -- | Different ways to refer to shifted versions of keycodes shiftedNames :: [(Text, DefButton)]-shiftedNames = let f = second $ \kc -> KAround (KEmit KeyLeftShift) (KEmit kc) in+shiftedNames = let f = second $ \kc -> KAroundImplicit (KEmit KeyLeftShift) (KEmit kc) in                  map f $ cps <> num <> oth <> lng   where     cps = zip (map T.singleton ['A'..'Z'])@@ -200,7 +202,7 @@ buttonNames :: [(Text, DefButton)] buttonNames = shiftedNames <> escp <> util   where-    emitS c = KAround (KEmit KeyLeftShift) (KEmit c)+    emitS c = KAroundImplicit (KEmit KeyLeftShift) (KEmit c)     -- Escaped versions for reserved characters     escp = [ ("\\(", emitS Key9), ("\\)", emitS Key0)            , ("\\_", emitS KeyMinus), ("\\\\", KEmit KeyBackslash)]@@ -212,7 +214,7 @@  -- | Parse "X-b" style modded-sequences moddedP :: Parser DefButton-moddedP = KAround <$> prfx <*> buttonP+moddedP = KAroundImplicit <$> prfx <*> buttonP   where mods = [ ("S-", KeyLeftShift), ("C-", KeyLeftCtrl)                , ("A-", KeyLeftAlt),   ("M-", KeyLeftMeta)                , ("RS-", KeyRightShift), ("RC-", KeyRightCtrl)@@ -240,6 +242,7 @@    -- If matching, parse a button-sequence from the stored text   case runParser (some buttonP) "" s of+    -- Parse error never reaches the user. They simply get a message about an unexpected character.     Left  _ -> fail "Could not parse compose sequence"     Right b -> pure b @@ -262,10 +265,11 @@ -- @(keyword, how to parse the token)@ keywordButtons :: [(Text, Parser DefButton)] keywordButtons =-  [ ("around"         , KAround      <$> buttonP     <*> buttonP)+  [ ("around-implicit", KAroundImplicit <$> buttonP <*> buttonP)   , ("press-only"     , KPressOnly   <$> keycodeP)   , ("release-only"   , KReleaseOnly <$> keycodeP)   , ("multi-tap"      , KMultiTap    <$> timed       <*> buttonP)+  , ("stepped"        , KStepped     <$> some buttonP)   , ("tap-hold"       , KTapHold     <$> lexeme numP <*> buttonP <*> buttonP)   , ("tap-hold-next"     , KTapHoldNext <$> lexeme numP <*> buttonP <*> buttonP@@ -287,6 +291,7 @@   , ("layer-delay"    , KLayerDelay  <$> lexeme numP <*> lexeme word)   , ("layer-next"     , KLayerNext   <$> lexeme word)   , ("around-next"    , KAroundNext  <$> buttonP)+  , ("around-next-single", KAroundNextSingle <$> buttonP)   , ("before-after-next", KBeforeAfterNext <$> buttonP <*> buttonP)   , ("around-next-timeout", KAroundNextTimeout <$> lexeme numP <*> buttonP <*> buttonP)   , ("tap-macro"@@ -294,13 +299,21 @@   , ("tap-macro-release"     , KTapMacroRelease <$> lexeme (some buttonP) <*> optional (keywordP "delay" numP))   , ("cmd-button"     , KCommand     <$> lexeme textP <*> optional (lexeme textP))-  , ("pause"          , KPause . fromIntegral <$> numP)+  , ("pause"          , KPause . fromIntegral <$> lexeme numP)   , ("sticky-key"     , KStickyKey   <$> lexeme numP <*> buttonP)   ]+  ++ map (\(nm,_,btn) -> (nm, btn <$> buttonP <*> buttonP)) implArndButtons  where   timed :: Parser [(Int, DefButton)]   timed = many ((,) <$> lexeme numP <*> lexeme buttonP) +implArndButtons :: [(Text, ImplArnd, DefButton -> DefButton -> DefButton)]+implArndButtons = sortBy (flip compare `on` (T.length . view _1)) -- Prevents early return due to `around`+  [ ("around"           , IAAround         , KAround)+  , ("around-only"      , IAAroundOnly     , KAroundOnly)+  , ("around-when-alone", IAAroundWhenAlone, KAroundWhenAlone)+  ]+ -- | Parsers for buttons that do __not__ have a keyword at the start noKeywordButtons :: [Parser DefButton] noKeywordButtons =@@ -324,9 +337,9 @@ -- | Input tokens to parse; the format is @(keyword, how to parse the token)@ itokens :: [(Text, Parser IToken)] itokens =-  [ ("device-file"   , KDeviceSource <$> (T.unpack <$> textP))+  [ ("device-file"   , KDeviceSource <$> (T.unpack <$> lexeme textP))   , ("low-level-hook", pure KLowLevelHookSource)-  , ("iokit-name"    , KIOKitSource <$> optional textP)]+  , ("iokit-name"    , KIOKitSource <$> optional (lexeme textP))]  -- | Parse an output token otokenP :: Parser OToken@@ -335,10 +348,16 @@ -- | Output tokens to parse; the format is @(keyword, how to parse the token)@ otokens :: [(Text, Parser OToken)] otokens =-  [ ("uinput-sink"    , KUinputSink <$> lexeme textP <*> optional textP)-  , ("send-event-sink", KSendEventSink <$> optional ((,) <$> lexeme numP <*> numP))+  [ ("uinput-sink"    , KUinputSink <$> lexeme textP <*> optional (lexeme textP))+  , ("send-event-sink", KSendEventSink <$> optional ((,) <$> lexeme numP <*> lexeme numP))   , ("kext"           , pure KKextSink)] +-- | Parse an impl arnd token+implArndP :: Parser ImplArnd+implArndP = lexeme . choice $+  try (IADisabled <$ symbol "disabled")+  : map (\(s, v, _) -> try $ v <$ symbol s) implArndButtons+ -- | Parse the DefCfg token defcfgP :: Parser DefSettings defcfgP = some (lexeme settingP)@@ -350,10 +369,10 @@     [ SIToken      <$> f "input"         itokenP     , SOToken      <$> f "output"        otokenP     , SCmpSeq      <$> f "cmp-seq"       buttonP-    , SInitStr     <$> f "init"          textP     , SFallThrough <$> f "fallthrough"   bool     , SAllowCmd    <$> f "allow-cmd"     bool     , SCmpSeqDelay <$> f "cmp-seq-delay" numP+    , SImplArnd    <$> f "implicit-around" implArndP     ])  --------------------------------------------------------------------------------@@ -367,10 +386,21 @@ -- $defsrc  defsrcP :: Parser DefSrc-defsrcP = many $ lexeme keycodeP-+defsrcP =+  DefSrc+    <$> optional (keywordP "name" word)+    <*> many (lexeme keycodeP)  -------------------------------------------------------------------------------- -- $deflayer+ deflayerP :: Parser DefLayer-deflayerP = DefLayer <$> lexeme word <*> many (lexeme buttonP)+deflayerP = DefLayer <$> lexeme word <*> many (lexeme layerSettingP)++layerSettingP :: Parser DefLayerSetting+layerSettingP =+  lexeme . choice . map try $+    [ LSrcName <$> keywordP "source" word+    , LImplArnd <$> keywordP "implicit-around" implArndP+    , LButton <$> buttonP+    ]
src/KMonad/Args/TH.hs view
@@ -16,15 +16,18 @@  import Language.Haskell.TH (Exp, Q) import Language.Haskell.TH.Syntax (runIO)+import UnliftIO.Directory (findExecutable) import UnliftIO.Process (readProcessWithExitCode) ---- | Get the git hash of the current revision at compile time+-- | Get the git hash of the current commit at compile time. gitHash :: Q Exp gitHash = do   str <- runIO do-    (exitCode, hash, _) <- readProcessWithExitCode "git" ["rev-parse", "HEAD"] ""-    pure case exitCode of-      ExitSuccess -> takeWhile (/= '\n') hash-      _           -> ""+    findExecutable "git" >>= \case+      Nothing  -> pure ""                         -- Git not present+      Just git -> do+        (exitCode, hash, _) <- readProcessWithExitCode git ["rev-parse", "HEAD"] ""+        pure case exitCode of+          ExitSuccess -> takeWhile (/= '\n') hash+          _           -> ""                       -- Not in a git repo   [| fromString str |]
src/KMonad/Args/Types.hs view
@@ -16,13 +16,15 @@      -- * $but   , DefButton(..)+  , ImplArnd(..)      -- * $tls   , DefSetting(..)   , DefSettings   , DefAlias+  , DefLayerSetting(..)   , DefLayer(..)-  , DefSrc+  , DefSrc(..)   , KExpr(..)      -- * $defio@@ -32,6 +34,8 @@     -- * $lenses   , AsKExpr(..)   , AsDefSetting(..)+  , HasDefSrc(..)+  , AsDefLayerSetting(..) ) where  @@ -71,7 +75,11 @@   | KAroundNext DefButton                  -- ^ Surround a future button   | KAroundNextSingle DefButton            -- ^ Surround a future button   | KMultiTap [(Int, DefButton)] DefButton -- ^ Do things depending on tap-count+  | KStepped [DefButton]                   -- ^ Do different things, one-by-one   | KAround DefButton DefButton            -- ^ Wrap 1 button around another+  | KAroundOnly DefButton DefButton        -- ^ Wrap 1 button only around another+  | KAroundWhenAlone DefButton DefButton   -- ^ Wrap 1 button around another when it's "alone"+  | KAroundImplicit DefButton DefButton    -- ^ Wrap 1 button around another   | KAroundNextTimeout Int DefButton DefButton   | KTapMacro [DefButton] (Maybe Int)     -- ^ Sequence of buttons to tap, possible delay between each press@@ -87,13 +95,22 @@   | KBeforeAfterNext DefButton DefButton   -- ^ Surround a future button in a before and after tap   | KTrans                                 -- ^ Transparent button that does nothing   | KBlock                                 -- ^ Button that catches event-  deriving Show+  deriving (Show, Eq, Typeable, Data) +instance Plated DefButton +-- | Possible values for implicit around+data ImplArnd+  = IADisabled+  | IAAround+  | IAAroundOnly+  | IAAroundWhenAlone+  deriving (Show, Eq)+ -------------------------------------------------------------------------------- -- $cfg ----- The Cfg token that can be extracted from a config-text without ever enterring+-- The Cfg token that can be extracted from a config-text without ever entering -- IO. This will then directly be translated to a DaemonCfg -- @@ -116,18 +133,31 @@ -- A collection of all the different top-level statements possible in a config -- file. --- | A list of keycodes describing the ordering of all the other layers-type DefSrc = [Keycode]+-- | A list of keycodes describing the ordering used by all other layers+-- | which is associated with a name.+data DefSrc = DefSrc+  { _srcName  :: Maybe Text -- ^ A unique name used to refer to this layer.+  , _keycodes :: [Keycode]  -- ^ Layer settings containing also the buttons.+  }+  deriving (Show, Eq)+makeClassy ''DefSrc  -- | A mapping from names to button tokens type DefAlias = [(Text, DefButton)] +data DefLayerSetting+  = LSrcName Text+  | LImplArnd ImplArnd+  | LButton DefButton+  deriving (Show, Eq)+makeClassyPrisms ''DefLayerSetting+ -- | A layer of buttons data DefLayer = DefLayer-  { _layerName :: Text        -- ^ A unique name used to refer to this layer-  , _buttons   :: [DefButton] -- ^ A list of button tokens+  { _layerName :: Text+  , _layerSettings :: [DefLayerSetting]   }-  deriving Show+  deriving (Show, Eq)   --------------------------------------------------------------------------------@@ -140,25 +170,25 @@   = KDeviceSource FilePath   | KLowLevelHookSource   | KIOKitSource (Maybe Text)-  deriving Show+  deriving (Show)  -- | All different output-tokens KMonad can take data OToken   = KUinputSink Text (Maybe Text)   | KSendEventSink (Maybe (Int, Int))   | KKextSink-  deriving Show+  deriving (Show)  -- | All possible single settings data DefSetting   = SIToken      IToken   | SOToken      OToken   | SCmpSeq      DefButton-  | SInitStr     Text   | SFallThrough Bool   | SAllowCmd    Bool   | SCmpSeqDelay Int-  deriving Show+  | SImplArnd    ImplArnd+  deriving (Show) makeClassyPrisms ''DefSetting  -- | 'Eq' instance for a 'DefSetting'. Because every one of these options may be@@ -168,9 +198,9 @@   SIToken{}      == SIToken{}      = True   SOToken{}      == SOToken{}      = True   SCmpSeq{}      == SCmpSeq{}      = True-  SInitStr{}     == SInitStr{}     = True   SFallThrough{} == SFallThrough{} = True   SAllowCmd{}    == SAllowCmd{}    = True+  SImplArnd{}    == SImplArnd{}    = True   _              == _              = False  -- | A list of different 'DefSetting' values@@ -185,7 +215,7 @@   | KDefSrc   DefSrc   | KDefLayer DefLayer   | KDefAlias DefAlias-  deriving Show+  deriving (Show, Eq) makeClassyPrisms ''KExpr  
src/KMonad/Keyboard/ComposeSeq.hs view
@@ -20,6 +20,7 @@ where  import KMonad.Prelude+import RIO.Text.Partial (replace)  -------------------------------------------------------------------------------- @@ -30,16 +31,21 @@ -- 3. A descriptive-name -- ssComposed :: [(Text, Char, Text)]-ssComposed =+ssComposed = composeSeqs & (each . _1) %~ sanitize+ where+  sanitize :: Text -> Text+  sanitize = replace "(" "\\("+           . replace ")" "\\)"+           . replace "_" "\\_"++  composeSeqs :: [(Text, Char, Text)]+  composeSeqs =     [ ("' '"      , '´'     , "acute")     , ( "^ -"     , '¯'     , "macron" )     , ( "spc ("   , '˘'     , "breve" )     , ( "\" \""   , '¨'     , "diaeresis" )     , ("spc <"    , 'ˇ'     , "caron")-    , ("` spc"    , '`'     , "grave")     , (", spc"    , '¸'     , "cedilla")-    , ("spc spc"  , ' '     , "nobreakspace")-    , ("spc ."    , ' '     , "U2008")     , ("o c"      , '©'     , "copyright")     , ("o r"      , '®'     , "registered")     , (". >"      , '›'     , "U203a")@@ -728,11 +734,13 @@     , ("_ '"      , '⍘'     , "U2358")     , ("0 ~"      , '⍬'     , "U236c")     , ("| ~"      , '⍭'     , "U236d")-    , ("c /"      , '¢'     , "cent" )     , ("< _"      , '≤'     , "U2264")     , ("> _"      , '≥'     , "U2265")      -- Sequences that should exist but do not work+    --, ("` spc"    , '`'     , "grave") -- recursive and incorrect. It's <dead_grave> <space> and <dead_grave> is not mapped in en_US     --, ("^ spc", '^', "asciicircum") -- This overlaps with the normal 'shifted-6' macro for     -- , ("' j", 'j́', "jacute")+    --, ("spc spc"  , ' '     , "nobreakspace") -- cannot be parsed since it is matched by `lexeme` of previous token+    --, ("spc ."    , ' '     , "U2008") -- see above     ]
src/KMonad/Keyboard/IO/Linux/Types.hs view
@@ -103,16 +103,17 @@ --           see: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h --         for sync: always 0 --- | Translate a 'LinuxKeyEvent' to a kmonad 'KeyEvent'+-- | Translate a 'LinuxKeyEvent' to a KMonad 'KeyEvent' fromLinuxKeyEvent :: LinuxKeyEvent -> Maybe KeyEvent fromLinuxKeyEvent (LinuxKeyEvent (_, _, typ, c, val))+  | c > 255 = Nothing   | typ == 1 && val == 0 = Just . mkRelease $ kc   | typ == 1 && val == 1 = Just . mkPress   $ kc   | otherwise = Nothing   where     kc = toEnum . fromIntegral $ c -- This is theoretically partial, but practically not --- | Translate kmonad 'KeyEvent' along with a 'SystemTime' to 'LinuxKeyEvent's+-- | Translate KMonad 'KeyEvent' along with a 'SystemTime' to 'LinuxKeyEvent's -- for writing. toLinuxKeyEvent :: KeyEvent -> SystemTime -> LinuxKeyEvent toLinuxKeyEvent e (MkSystemTime s ns)
src/KMonad/Keyboard/IO/Mac/Types.hs view
@@ -214,7 +214,7 @@   , ((0x7,0x61), KeyKp9)   , ((0x7,0x62), KeyKp0)   , ((0x7,0x63), KeyKpDot)-  -- , ((0x7,0x64), KeyNonUSBackslash)+  , ((0x7,0x64), Key102nd)   -- , ((0x7,0x65), KeyApplication)   , ((0x7,0x66), KeyPower)   , ((0x7,0x67), KeyKpEqual)
src/KMonad/Keyboard/Keycode.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveAnyClass, CPP #-}+{-# LANGUAGE DeriveAnyClass #-} {-| Module      : KMonad.Keyboard.Keycode Description : Description of all possible keycodes.@@ -37,7 +37,7 @@ -- headers: -- https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h. ----- Anywhere there are missing regions in the linux headers, we've defined+-- Anywhere there are missing regions in the Linux headers, we've defined -- @MissingXX@ values for the ADT. -- -- Calling 'RIO.Partial.toEnum' on a Linux keycode value should produce the@@ -301,14 +301,13 @@   | Missing253   | Missing254   | Missing255-#ifdef darwin_HOST_OS+  -- Darwin   | KeyFn   | KeyLaunchpad   | KeyMissionCtrl   | KeySpotlight   | KeyDictation-#endif-  deriving (Eq, Show, Bounded, Enum, Ord, Generic, Hashable)+  deriving (Eq, Show, Bounded, Enum, Ord, Generic, Hashable, Typeable, Data)   instance Display Keycode where@@ -353,65 +352,69 @@ -- | A collection of useful aliases to refer to keycode names aliases :: Q.MultiMap Keycode Text aliases = Q.mkMultiMap-  [ (KeyEnter,          ["ret", "return", "ent"])-  , (KeyMinus,          ["min", "-"])-  , (KeyEqual,          ["eql", "="])-  , (KeySleep,          ["zzz"])-  , (KeySpace,          ["spc"])-  , (KeyPageUp,         ["pgup"])-  , (KeyPageDown,       ["pgdn"])-  , (KeyInsert,         ["ins"])-  , (KeyDelete,         ["del"])-  , (KeyVolumeUp,       ["volu"])-  , (KeyVolumeDown,     ["voldwn", "vold"])-  , (KeyBrightnessUp,   ["brup", "bru"])-  , (KeyBrightnessDown, ["brdown", "brdwn", "brdn"])-  , (KeyLeftAlt,        ["lalt", "alt"])-  , (KeyRightAlt,       ["ralt"])-  , (KeyCompose,        ["comp", "cmps", "cmp"])-  , (KeyLeftShift,      ["lshift", "lshft", "lsft", "shft", "sft"])-  , (KeyRightShift,     ["rshift", "rshft", "rsft"])-  , (KeyLeftCtrl,       ["lctrl", "lctl", "ctl"])-  , (KeyRightCtrl,      ["rctrl", "rctl"])-  , (KeyLeftMeta,       ["lmeta", "lmet", "met"])-  , (KeyRightMeta,      ["rmeta", "rmet"])-  , (KeyBackspace,      ["bks", "bspc"])-  , (KeyCapsLock,       ["caps"])-  , (Key102nd,          ["102d", "lsgt", "nubs"])-  , (KeyForward,        ["fwd"])-  , (KeyScrollLock,     ["scrlck", "slck"])-  , (KeyScrollUp,       ["scrup", "sup"])-  , (KeyScrollDown,     ["scrdn", "sdwn", "sdn"])-  , (KeyPrint,          ["prnt"])-  , (KeyWakeUp,         ["wkup"])-  , (KeyLeft,           ["lft"])-  , (KeyRight,          ["rght"])-  , (KeyLeftBrace,      ["lbrc", "["])-  , (KeyRightBrace,     ["rbrc", "]"])-  , (KeySemicolon,      ["scln", ";"])-  , (KeyApostrophe,     ["apos", "'", "apo"])-  , (KeyGrave,          ["grv", "`"])-  , (KeyBackslash,      ["bksl", "\\"]) -- NOTE: "\\" here is a 1char string, the first \ is consumed by Haskell as an escape character-  , (KeyComma,          ["comm", ","])-  , (KeyDot,            ["."])-  , (KeySlash,          ["/"])-  , (KeyNumLock,        ["nlck"])-  , (KeyKpSlash,        ["kp/"])-  , (KeyKpEnter,        ["kprt"])-  , (KeyKpPlus,         ["kp+"])-  , (KeyKpAsterisk,     ["kp*"])-  , (KeyKpMinus,        ["kp-"])-  , (KeyKpDot,          ["kp."])-  , (KeySysRq,          ["ssrq", "sys"])-  , (KeyKbdIllumDown,   ["bldn"])-  , (KeyKbdIllumUp,     ["blup"])-  , (KeyNextSong,       ["next"])-  , (KeyPlayPause,      ["pp"])-  , (KeyPreviousSong,   ["prev"])-#ifdef darwin_HOST_OS-  , (KeyLaunchpad,      ["lp"])-  , (KeyMissionCtrl,    ["mctl"])-  , (KeySpotlight,      ["spot"])-  , (KeyDictation,      ["dict"])-#endif+  [ (KeyEnter,            ["ret", "return", "ent"])+  , (KeyMinus,            ["min", "-"])+  , (KeyEqual,            ["eql", "="])+  , (KeySleep,            ["zzz"])+  , (KeySpace,            ["spc"])+  , (KeyPageUp,           ["pgup"])+  , (KeyPageDown,         ["pgdn"])+  , (KeyInsert,           ["ins"])+  , (KeyDelete,           ["del"])+  , (KeyVolumeUp,         ["volu"])+  , (KeyVolumeDown,       ["voldwn", "vold"])+  , (KeyBrightnessUp,     ["brup", "bru"])+  , (KeyBrightnessDown,   ["brdown", "brdwn", "brdn"])+  , (KeyLeftAlt,          ["lalt", "alt"])+  , (KeyRightAlt,         ["ralt"])+  , (KeyCompose,          ["comp", "cmps", "cmp"])+  , (KeyLeftShift,        ["lshift", "lshft", "lsft", "shft", "sft"])+  , (KeyRightShift,       ["rshift", "rshft", "rsft"])+  , (KeyLeftCtrl,         ["lctrl", "lctl", "ctl"])+  , (KeyRightCtrl,        ["rctrl", "rctl"])+  , (KeyLeftMeta,         ["lmeta", "lmet", "met"])+  , (KeyRightMeta,        ["rmeta", "rmet"])+  , (KeyBackspace,        ["bks", "bspc"])+  , (KeyCapsLock,         ["caps"])+  , (Key102nd,            ["102d", "lsgt", "nubs"])+  , (KeyForward,          ["fwd"])+  , (KeyScrollLock,       ["scrlck", "slck"])+  , (KeyScrollUp,         ["scrup", "sup"])+  , (KeyScrollDown,       ["scrdn", "sdwn", "sdn"])+  , (KeyPrint,            ["prnt"])+  , (KeyWakeUp,           ["wkup"])+  , (KeyLeft,             ["lft"])+  , (KeyRight,            ["rght"])+  , (KeyLeftBrace,        ["lbrc", "["])+  , (KeyRightBrace,       ["rbrc", "]"])+  , (KeySemicolon,        ["scln", ";"])+  , (KeyApostrophe,       ["apos", "'", "apo"])+  , (KeyGrave,            ["grv", "`"])+  , (KeyBackslash,        ["bksl", "\\"]) -- NOTE: "\\" here is a 1char string, the first \ is consumed by Haskell as an escape character+  , (KeyComma,            ["comm", ","])+  , (KeyDot,              ["."])+  , (KeySlash,            ["/"])+  , (KeyNumLock,          ["nlck"])+  , (KeyKpSlash,          ["kp/"])+  , (KeyKpEnter,          ["kprt"])+  , (KeyKpPlus,           ["kp+"])+  , (KeyKpAsterisk,       ["kp*"])+  , (KeyKpMinus,          ["kp-"])+  , (KeyKpDot,            ["kp."])+  , (KeySysRq,            ["ssrq", "sys"])+  , (KeyKbdIllumDown,     ["bldn"])+  , (KeyKbdIllumUp,       ["blup"])+  , (KeyNextSong,         ["next"])+  , (KeyPlayPause,        ["pp"])+  , (KeyPreviousSong,     ["prev"])+  -- Darwin+  , (KeyLaunchpad,        ["lp"])+  , (KeyMissionCtrl,      ["mctl"])+  , (KeySpotlight,        ["spot"])+  , (KeyDictation,        ["dict"])+  -- Japanese+  , (KeyZenkakuHankaku,   ["zeh"])+  , (KeyMuhenkan,         ["muh"])+  , (KeyHenkan,           ["hen"])+  , (KeyKatakanaHiragana, ["kah"])   ]
src/KMonad/Model/Action.hs view
@@ -66,7 +66,7 @@ -------------------------------------------------------------------------------- -- $keyfun --- | Boolean isomorph signalling wether an event should be caught or not+-- | Boolean isomorph signalling whether an event should be caught or not. data Catch = Catch | NoCatch deriving (Show, Eq)  instance Semigroup Catch where
src/KMonad/Model/Button.hs view
@@ -20,6 +20,7 @@   , HasButton(..)   , onPress   , onRelease+  , onTap   , mkButton   , around   , tapOn@@ -39,6 +40,8 @@    -- * Button combinators   -- $combinators+  , aroundOnly+  , aroundWhenAlone   , aroundNext   , aroundNextTimeout   , aroundNextSingle@@ -54,6 +57,7 @@   , tapNextPress   , tapMacro   , tapMacroRelease+  , steppedButton   , stickyKey   ) where@@ -64,6 +68,7 @@ import KMonad.Keyboard import KMonad.Util +import qualified RIO.HashSet as S  -------------------------------------------------------------------------------- -- $but@@ -72,11 +77,15 @@ -- 'Button' is essentially a collection of 2 different actions, 1 to perform on -- 'Press' and another on 'Release'. --- | A 'Button' consists of two 'MonadK' actions, one to take when a press is+-- | A 'Button' consists of three 'MonadK' actions, one to take when a press is -- registered from the OS, and another when a release is registered.+-- With a third when both should happen in sequence. This will only be used+-- by other button such as `tap-macro`.+-- Use 'mkButton' instead of this constructor. data Button = Button   { _pressAction   :: !Action -- ^ Action to take when pressed   , _releaseAction :: !Action -- ^ Action to take when released+  , _tapAction     :: !Action -- ^ Action to take when tapped as a sub button   } makeClassy ''Button @@ -87,8 +96,15 @@ -- therefore can only rely on functionality from 'MonadK'. I.e. the actions must -- be pure 'MonadK'. mkButton :: AnyK () -> AnyK () -> Button-mkButton a b = Button (Action a) (Action b)+mkButton a b = mkButton' a b $ a *> b +-- | Create a 'Button' out of a press, release action and tap action+--+-- The non standard tap action is useful when inside other buttons+-- like `tap-macro`+mkButton' :: AnyK () -> AnyK () -> AnyK () -> Button+mkButton' a b c = Button (Action a) (Action b) (Action c)+ -- | Create a new button with only a 'Press' action onPress :: AnyK () -> Button onPress p = mkButton p $ pure ()@@ -96,6 +112,13 @@ onRelease :: AnyK () -> Button onRelease = mkButton (pure ()) +onTap :: AnyK () -> Button+onTap = mkButton' (pure ()) (pure ())++-- | Like 'onPress' but with an alternative button to use for tapping+onPress' :: Button -> AnyK () -> Button+onPress' (Button{_tapAction = Action t}) p = mkButton' p (pure ()) t+ -------------------------------------------------------------------------------- -- $running --@@ -103,9 +126,7 @@  -- | Perform both the press and release of a button immediately tap :: MonadK m => Button -> m ()-tap b = do-  runAction $ b^.pressAction-  runAction $ b^.releaseAction+tap b = runAction $ b^.tapAction  -- | Perform the press action of a Button and register its release callback. --@@ -149,9 +170,10 @@  -- | Create a button that toggles a layer on and off layerToggle :: LayerTag -> Button-layerToggle t = mkButton+layerToggle t = mkButton'   (layerOp $ PushLayer t)   (layerOp $ PopLayer  t)+  (pure ())  -- | Create a button that switches the base-layer on a press layerSwitch :: LayerTag -> Button@@ -186,10 +208,70 @@      Button -- ^ The outer 'Button'   -> Button -- ^ The inner 'Button'   -> Button -- ^ The resulting nested 'Button'-around outer inner = Button-  (Action (runAction (outer^.pressAction)   *> runAction (inner^.pressAction)))-  (Action (runAction (inner^.releaseAction) *> runAction (outer^.releaseAction)))+around outer inner = mkButton+  (runAction (outer^.pressAction)   *> runAction (inner^.pressAction))+  (runAction (inner^.releaseAction) *> runAction (outer^.releaseAction)) +-- | A variant of `around`, which releases its outer button when another key+-- is pressed.+aroundOnly ::+     Button -- ^ The outer 'Button'+  -> Button -- ^ The inner 'Button'+  -> Button -- ^ The resulting nested 'Button'+aroundOnly outer inner = onPress' (around outer inner) $ do+  runAction $ outer^.pressAction+  runAction $ inner^.pressAction+  go =<< matchMy Release+ where+  go :: KeyPred -> AnyK ()+  go isMyRelease = hookF InputHook $ \e ->+    if+      | isMyRelease e -> do+        runAction $ inner^.releaseAction+        runAction $ outer^.releaseAction+        pure Catch+      -- Another key is pressed, so release the modifier immediately.+      | isPress e -> do+        runAction $ outer^.releaseAction+        await isMyRelease $ \_ -> do+          runAction (inner^.releaseAction)+          pure Catch+        pure NoCatch+      | otherwise ->+        go isMyRelease $> NoCatch++-- | A variant of `around-only` that represses its outer button when all other+-- keys after it have been released.+aroundWhenAlone ::+     Button -- ^ The outer 'Button'+  -> Button -- ^ The inner 'Button'+  -> Button -- ^ The resulting nested 'Button'+aroundWhenAlone outer inner = onPress' (around outer inner) $ do+  runAction $ outer^.pressAction+  runAction $ inner^.pressAction+  go S.empty =<< matchMy Release+  pure ()+ where+  go :: HashSet Keycode -> KeyPred -> AnyK ()+  go pressed isMyRelease = hookF InputHook $ \e ->+    if+      | isMyRelease e -> do+        runAction $ inner^.releaseAction+        when (null pressed) . runAction $ outer^.releaseAction+        pure Catch+      | isPress e -> do+        let pressed' = S.insert (e^.keycode) pressed+        when (null pressed) . runAction $ outer^.releaseAction+        go pressed' isMyRelease+        pure NoCatch+      | otherwise -> do -- some release+        let pressed' = S.delete (e^.keycode) pressed+        let shouldPressOuter = S.member (e^.keycode) pressed && null pressed'+        inject e+        when shouldPressOuter . after 3 . runAction $ outer^.pressAction+        await isRelease $ \_ -> go pressed' isMyRelease $> NoCatch+        pure Catch+ -- | A 'Button' that, once pressed, will surround the next button with another. -- -- Think of this as, essentially, a tappable mod. For example, an 'aroundNext@@ -244,14 +326,14 @@      Switch -- ^ Which 'Switch' should trigger the tap   -> Button -- ^ The 'Button' to tap   -> Button -- ^ The tapping 'Button'-tapOn Press   b = mkButton (tap b)   (pure ())-tapOn Release b = mkButton (pure ()) (tap b)+tapOn Press   b = onPress $ tap b+tapOn Release b = onRelease $ tap b  -- | Create a 'Button' that performs a tap of one button if it is released -- within an interval. If the interval is exceeded, press the other button (and -- release it when a release is detected). tapHold :: Milliseconds -> Button -> Button -> Button-tapHold ms t h = onPress $ withinHeld ms (matchMy Release)+tapHold ms t h = onPress' t $ withinHeld ms (matchMy Release)   (press h)                     -- If we catch timeout before release   (const $ tap t $> Catch) -- If we catch release before timeout @@ -259,7 +341,7 @@ -- own release, or else switches to holding some other button if the next event -- is a different keypress. tapNext :: Button -> Button -> Button-tapNext t h = onPress $ hookF InputHook $ \e -> do+tapNext t h = onPress' t $ hookF InputHook $ \e -> do   p <- matchMy Release   if p e     then tap t   $> Catch@@ -296,7 +378,7 @@ -- It does all of this while holding processing of other buttons, so time will -- get rolled back like a TapHold button. tapNextRelease :: Button -> Button -> Button-tapNextRelease t h = onPress $ do+tapNextRelease t h = onPress' t $ do   hold True   go []   where@@ -373,7 +455,7 @@ -- 2. It is the press of some other button, we hold -- 3. It is the release of some other button, ignore. tapNextPress :: Button -> Button -> Button-tapNextPress t h = onPress go+tapNextPress t h = onPress' t go   where     go :: MonadK m => m ()     go = hookF InputHook $ \e -> do@@ -401,8 +483,12 @@ -- into its list. The moment a delay is exceeded or immediately upon reaching -- the last button, that button is pressed. multiTap :: Button -> [(Milliseconds, Button)] -> Button-multiTap l bs = onPress $ go bs+multiTap l bs = onPress' tap' $ go bs   where+    tap' = case bs of+      []           -> l+      ((_, b) : _) -> b+     go :: [(Milliseconds, Button)] -> AnyK ()     go []            = press l     go ((ms, b):bs') = do@@ -437,20 +523,22 @@ -- | Create a 'Button' that performs a series of taps on press. Note that the -- last button is only released when the tapMacro itself is released. tapMacro :: [Button] -> Button-tapMacro bs = onPress $ go bs+tapMacro bs = mkButton' (go True bs) (pure ()) (go False bs)   where-    go []      = pure ()-    go [b]     = press b-    go (b:rst) = tap b >> go rst+    go _ []      = pure ()+    go False [b]     = press b+    go True [b] = tap b+    go forceTap (b:rst) = tap b >> go forceTap rst  -- | Create a 'Button' that performs a series of taps on press, -- except for the last Button, which is tapped on release. tapMacroRelease :: [Button] -> Button-tapMacroRelease bs = onPress $ go bs+tapMacroRelease bs = mkButton' (go True bs) (pure ()) (go False bs)   where-    go []      = pure ()-    go [b]     = awaitMy Release $ tap b >> pure Catch-    go (b:rst) = tap b >> go rst+    go _ []      = pure ()+    go False [b]     = awaitMy Release $ tap b >> pure Catch+    go True [b] = tap b+    go forceTap (b:rst) = tap b >> go forceTap rst  -- | Switch to a layer for a period of time, then automatically switch back layerDelay :: Milliseconds -> LayerTag -> Button@@ -495,3 +583,16 @@                *> inject (t^.event)                *> after 3 (runAction $ b^.releaseAction)                $> Catch)++-- | Create a button that functions as a different button everything it is pushed+--+-- I.e: first it acts as the first button, then as the second, then as the+-- third, and when finished rotates back to being the first button.+steppedButton :: [Button] -> Button+steppedButton bs = onPress $ go bs+  where+    go [] = undefined+    go [b] = press b+    go (b:bs') = do+      press b+      awaitMy Press $ go bs' $> Catch
src/KMonad/Model/Dispatch.hs view
@@ -26,7 +26,7 @@  In the sequencing of components, the 'Dispatch' occurs first, which means that it reads directly from the KeySource. Any component after the 'Dispatch' need-not worry about wether an event is being rerun or not, it simply treats all+not worry about whether an event is being rerun or not, it simply treats all events as equal.  -}
src/KMonad/Util.hs view
@@ -43,7 +43,7 @@  -- | Newtype wrapper around 'Int' to add type safety to our time values newtype Milliseconds = Milliseconds { unMS :: Int }-  deriving (Eq, Ord, Num, Real, Enum, Integral, Show, Read, Generic, Display)+  deriving (Eq, Ord, Num, Real, Enum, Integral, Show, Read, Generic, Display, Typeable, Data)  -- | Calculate how much time has elapsed between 2 time points tDiff :: ()
src/KMonad/Util/LayerStack.hs view
@@ -102,7 +102,7 @@   => t1 (l, t2 (k, a)) -- ^ The /alist/ of /alists/ describing the mapping   -> LayerStack l k a mkLayerStack nestMaps = let-  -- Create a HashMap l (Layer k a) from the listlikes+  -- Create a HashMap l (Layer k a) from the list-likes   hms = M.fromList . map (over _2 mkLayer) $ toList nestMaps --   -- Create a HashMap (l, k) a from `hms`   its = M.fromList $ hms ^@.. ifolded <.> (to unLayer . ifolded)
+ test/KMonad/ButtonDocSpec.hs view
@@ -0,0 +1,70 @@+module KMonad.ButtonDocSpec (spec) where++import KMonad.Args.Parser+import KMonad.Args.Types+import KMonad.Prelude++import Data.Char (isSpace)+import Data.Data+import qualified RIO.Text as T++import Test.Hspec++spec :: Spec+spec = do+  docsExistForEveryButtons+  tutorialMentionsEveryButton++docsExistForEveryButtons :: Spec+docsExistForEveryButtons =+  (describe "button-docs" . traverse_ checkForDocsIn)+    [ quickReferencePath+    , tutorialPath+    ]+ where+  checkForDocsIn file = do+    cnt <- runIO $ readFileUtf8 file+    describe file . sequence_ $ traverse checkForDoc keywordButtons (lines cnt)+  checkForDoc btn cnt =+    it ("Doc mentions " <> unpack (fst btn)) $+      unless (buttonDocExist btn cnt) (expectationFailure "button is not mentioned in documentation")+  buttonDocExist = any . checkerFromButton . fst+  -- ignore deprecated names+  checkerFromButton "momentary-layer" = const True+  checkerFromButton "permanent-layer" = const True+  checkerFromButton btn =+    T.isPrefixOf ("- `(" <> btn <> " ") . T.dropWhile isSpace+      <||> T.isInfixOf ("`" <> btn <> "`")+      <||> T.isInfixOf ("`" <> btn <> "'")+  (<||>) = liftA2 (||)+  infixr 2 <||>++tutorialMentionsEveryButton :: Spec+tutorialMentionsEveryButton =+  describe "button-usage" $+    sequence_ . traverse checkButtonUsed buttonConstrs =<< runIO getTutorial+ where+  checkButtonUsed btn cnt =+    it ("Buttontype `" <> showConstr btn <> "` appears outside of comments")+      . unless (containsButton btn cnt)+      $ expectationFailure ("Buttontype `" <> showConstr btn <> "` is never used")+  containsButton btn = any . anyButton $ (== btn) . toConstr+  anyButton f (KDefLayer DefLayer{_layerSettings = lyrBtns}) = anyOf (each . _LButton) (anySubButton f) lyrBtns+  anyButton f (KDefAlias als) = any (anySubButton f . snd) als+  anyButton _ _ = False+  anySubButton f x = f x || anyOf plate f x+  buttonConstrs = dataTypeConstrs $ dataTypeOf (undefined :: DefButton)++getTutorial :: IO [KExpr]+getTutorial =+  either cannotParseTutorial pure . parseTokens+    =<< readFileUtf8 tutorialPath+ where+  cannotParseTutorial err =+    fail ("Could not parse `" <> tutorialPath <> "`:\n" <> show err)+      $> []++quickReferencePath :: FilePath+quickReferencePath = "doc/quick-reference.md"+tutorialPath :: FilePath+tutorialPath = "keymap/tutorial.kbd"
+ test/KMonad/ComposeSeqSpec.hs view
@@ -0,0 +1,30 @@+module KMonad.ComposeSeqSpec (spec) where++import KMonad.Args.Parser+import KMonad.Args.Types+import KMonad.Keyboard.ComposeSeq+import KMonad.Keyboard.Keycode+import KMonad.Parsing+import KMonad.Prelude++import Test.Hspec++import qualified RIO.Text as T++spec :: Spec+spec = describe "compose-sequences" $ traverse_ checkComposeSeq ssComposed+ where+  checkComposeSeq (expected, c, name) = describe ("Compose sequence for " <> unpack name) $ do+    let c' = T.singleton c+    let actualSeq = runParser buttonP "" c'+    let expectedSeq = runParser (KComposeSeq <$> some buttonP) "" expected+    let actualE2E = parseTokens $ "(deflayer <test> " <> c' <> " )"+    let expectedE2E = first ParseError expectedSeq <&> \x -> [KDefLayer (DefLayer "<test>" [LButton x])]+    it "Is compose sequence" $ actualSeq `shouldSatisfy` parsesAsValidComposeSeq+    it "Matches expected" $ actualSeq `shouldBe` expectedSeq+    it "Could parse in E2E" $ actualE2E `shouldBe` expectedE2E+  parsesAsValidComposeSeq (Right (KComposeSeq seq')) = all isSimple seq'+  parsesAsValidComposeSeq _ = False+  isSimple (KEmit _) = True+  isSimple (KAroundImplicit (KEmit KeyLeftShift) (KEmit _)) = True+  isSimple _ = False
test/KMonad/GestureSpec.hs view
@@ -3,8 +3,6 @@ import KMonad.Prelude import KMonad.Gesture -import Data.Either (fromRight)- import Test.Hspec hiding (around)  r :: Either a b -> b