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 +1/−1
- c_src/mac/dext.cpp +12/−14
- changelog.md +33/−0
- doc/quick-reference.md +463/−0
- keymap/tutorial.kbd +1119/−0
- kmonad.cabal +189/−131
- src/KMonad/App/Main.hs +2/−1
- src/KMonad/App/Types.hs +1/−1
- src/KMonad/Args.hs +1/−1
- src/KMonad/Args/Cmd.hs +13/−12
- src/KMonad/Args/Joiner.hs +80/−12
- src/KMonad/Args/Parser.hs +43/−13
- src/KMonad/Args/TH.hs +9/−6
- src/KMonad/Args/Types.hs +44/−14
- src/KMonad/Keyboard/ComposeSeq.hs +13/−5
- src/KMonad/Keyboard/IO/Linux/Types.hs +3/−2
- src/KMonad/Keyboard/IO/Mac/Types.hs +1/−1
- src/KMonad/Keyboard/Keycode.hs +69/−66
- src/KMonad/Model/Action.hs +1/−1
- src/KMonad/Model/Button.hs +125/−24
- src/KMonad/Model/Dispatch.hs +1/−1
- src/KMonad/Util.hs +1/−1
- src/KMonad/Util/LayerStack.hs +1/−1
- test/KMonad/ButtonDocSpec.hs +70/−0
- test/KMonad/ComposeSeqSpec.hs +30/−0
- test/KMonad/GestureSpec.hs +0/−2
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