packages feed

helic 0.4.0.0 → 0.5.0.0

raw patch · 13 files changed

+116/−38 lines, 13 filesdep +aesonPVP ok

version bump matches the API change (PVP)

Dependencies added: aeson

API changes (from Hackage documentation)

+ Helic.Data.X11Config: instance Data.Aeson.Types.FromJSON.FromJSON Helic.Data.X11Config.DisplayId
+ Helic.Data.X11Config: instance Data.Aeson.Types.FromJSON.FromJSON Helic.Data.X11Config.X11Config
+ Helic.Data.X11Config: instance Data.Aeson.Types.ToJSON.ToJSON Helic.Data.X11Config.DisplayId
+ Helic.Data.X11Config: instance Data.Aeson.Types.ToJSON.ToJSON Helic.Data.X11Config.X11Config
+ Helic.Data.X11Config: instance Data.Default.Class.Default Helic.Data.X11Config.X11Config
+ Helic.Data.X11Config: instance Data.String.IsString Helic.Data.X11Config.DisplayId
+ Helic.Data.X11Config: instance GHC.Classes.Eq Helic.Data.X11Config.DisplayId
+ Helic.Data.X11Config: instance GHC.Classes.Eq Helic.Data.X11Config.X11Config
+ Helic.Data.X11Config: instance GHC.Generics.Generic Helic.Data.X11Config.DisplayId
+ Helic.Data.X11Config: instance GHC.Generics.Generic Helic.Data.X11Config.X11Config
+ Helic.Data.X11Config: instance GHC.Show.Show Helic.Data.X11Config.DisplayId
+ Helic.Data.X11Config: instance GHC.Show.Show Helic.Data.X11Config.X11Config
+ Helic.Interpreter.Gtk: noDisplayAvailable :: Members [Stop Text, Embed IO] r => DisplayManager -> Sem r Bool
+ Helic.Interpreter.Gtk: tryOpenDisplay :: Members [Stop Text, Log, Embed IO] r => DisplayId -> DisplayManager -> Sem r ()
+ Helic.Json: json :: Name -> Q [Dec]
+ Helic.Json: unaryJson :: Name -> Q [Dec]
- Helic: interpretGtk :: Members [Resource, Log, Embed IO] r => InterpreterFor (Scoped Display (Gtk Display) !! Text) r
+ Helic: interpretGtk :: Members [Resource, Log, Embed IO] r => X11Config -> InterpreterFor (Scoped Display (Gtk Display) !! Text) r
- Helic.Interpreter.Gtk: bracketGtk :: Members [Resource, Log, Embed IO] r => (Display -> Sem (Stop Text : r) a) -> Sem (Stop Text : r) a
+ Helic.Interpreter.Gtk: bracketGtk :: Members [Resource, Log, Embed IO] r => DisplayId -> (Display -> Sem (Stop Text : r) a) -> Sem (Stop Text : r) a
- Helic.Interpreter.Gtk: interpretGtk :: Members [Resource, Log, Embed IO] r => InterpreterFor (Scoped Display (Gtk Display) !! Text) r
+ Helic.Interpreter.Gtk: interpretGtk :: Members [Resource, Log, Embed IO] r => X11Config -> InterpreterFor (Scoped Display (Gtk Display) !! Text) r

Files

changelog.md view
@@ -1,5 +1,10 @@ # Unreleased +# 0.5.0.0++* Allow empty config files.+* Forcibly connect to a GTK display if none is currently open.+ # 0.4.0.0  * Rewrite the GTK main loop effects for better resilience when no display is available (yet).
helic.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           helic-version:        0.4.0.0+version:        0.5.0.0 synopsis:       Clipboard Manager description:    See <https://hackage.haskell.org/package/helic/docs/Helic.html> category:       Clipboard@@ -43,6 +43,7 @@       Helic.Data.NetConfig       Helic.Data.Selection       Helic.Data.TmuxConfig+      Helic.Data.X11Config       Helic.Data.XClipboardEvent       Helic.Data.YankConfig       Helic.Effect.Agent@@ -67,6 +68,7 @@       Helic.Interpreter.History       Helic.Interpreter.InstanceName       Helic.Interpreter.XClipboard+      Helic.Json       Helic.List       Helic.Listen       Helic.Net.Api@@ -137,7 +139,8 @@       ViewPatterns   ghc-options: -Wall -Wredundant-constraints -Wunused-packages -fplugin=Polysemy.Plugin   build-depends:-      base ==4.*+      aeson >=1.5+    , base ==4.*     , chronos >=1.1.1     , exon >=0.3     , gi-gdk >=3
lib/Helic/App.hs view
@@ -59,7 +59,7 @@ listenApp ::   Config ->   Sem AppStack ()-listenApp (Config name tmux net maxHistory _) =+listenApp (Config name tmux net x11 maxHistory _) =   runReader (fromMaybe def tmux) $   runReader (fromMaybe def net) $   interpretEventsChan @XClipboardEvent $@@ -67,7 +67,7 @@   interpretAtomic mempty $   interpretInstanceName name $   interpretManager $-  interpretGtk $+  interpretGtk (fromMaybe def x11) $   interpretGtkMain (MilliSeconds 500) (Seconds 10) $   interpretGtkClipboard $   gtkMainLoop subscribeEvents $@@ -84,7 +84,7 @@   Config ->   YankConfig ->   Sem AppStack ()-yankApp (Config name _ net _ _) yankConfig =+yankApp (Config name _ net _ _ _) yankConfig =   interpretManager $   interpretInstanceName name $   runReader (fromMaybe def net) $@@ -104,7 +104,7 @@   Config ->   ListConfig ->   Sem AppStack ()-listApp (Config _ _ net _ _) listConfig =+listApp (Config _ _ net _ _ _) listConfig =   runReader listConfig $   runClient net $   list@@ -113,6 +113,6 @@   Config ->   LoadConfig ->   Sem AppStack ()-loadApp (Config _ _ net _ _) (LoadConfig event) =+loadApp (Config _ _ net _ _ _) (LoadConfig event) =   runClient net $   (void . fromEither =<< Client.load event)
lib/Helic/Config/File.hs view
@@ -17,7 +17,7 @@   Sem r Config parseFileConfig (toFilePath -> path) = do   Log.debug [exon|Reading config file #{toText path}|]-  fromEither =<< first formatError <$> embed (decodeFileEither path)+  fromEither =<< bimap formatError (fromMaybe def) <$> embed (decodeFileEither path)   where     formatError exc =       toText [exon|invalid config file: #{prettyPrintParseException exc}|]
lib/Helic/Data/Config.hs view
@@ -7,12 +7,14 @@  import Helic.Data.NetConfig (NetConfig) import Helic.Data.TmuxConfig (TmuxConfig)+import Helic.Data.X11Config (X11Config)  data Config =   Config {     name :: Maybe Text,     tmux :: Maybe TmuxConfig,     net :: Maybe NetConfig,+    x11 :: Maybe X11Config,     maxHistory :: Maybe Int,     verbose :: Maybe Bool   }
+ lib/Helic/Data/X11Config.hs view
@@ -0,0 +1,22 @@+{-# options_haddock prune #-}++-- |X11Config Data Type, Internal+module Helic.Data.X11Config where++import Helic.Json (json, unaryJson)++newtype DisplayId =+  DisplayId { unDisplayId :: Text }+  deriving stock (Eq, Show, Generic)+  deriving newtype (IsString)++json ''DisplayId++data X11Config =+  X11Config {+    display :: Maybe DisplayId+  }+  deriving stock (Eq, Show, Generic)+  deriving anyclass (Default)++unaryJson ''X11Config
lib/Helic/Gtk.hs view
@@ -6,10 +6,8 @@ import Exon (exon) import qualified GI.GLib as Glib import qualified GI.Gdk as GiGdk-import qualified GI.Gdk as Gdk import GI.Gdk (Display) import qualified GI.Gtk as GiGtk-import qualified GI.Gtk as GI import Polysemy.Final (withWeavingToFinal) import qualified Polysemy.Log as Log @@ -33,7 +31,7 @@     recovering :: IO x -> IO x     recovering =       flip Base.onException (putMVar result Nothing)-  _ <- tryStop $ recovering $ Gdk.threadsAddIdle Glib.PRIORITY_DEFAULT do+  _ <- tryStop $ recovering $ GiGdk.threadsAddIdle Glib.PRIORITY_DEFAULT do     putMVar result . Just =<< recovering ma     pure False   stopNote [exon|Gtk ui thread computation '#{desc}' failed|] =<< embed (takeMVar result)@@ -44,24 +42,24 @@   MonadIO m =>   Display ->   Selection ->-  m GI.Clipboard+  m GiGtk.Clipboard unsafeGtkClipboard display name = do-  selection <- Gdk.atomIntern (Selection.toXString name) False-  GI.clipboardGetForDisplay display selection+  selection <- GiGdk.atomIntern (Selection.toXString name) False+  GiGtk.clipboardGetForDisplay display selection  -- |Return a GTK clipboard, converting all exceptions to 'Stop'. gtkClipboard ::   Members [Stop Text, Embed IO] r =>   Display ->   Selection ->-  Sem r GI.Clipboard+  Sem r GiGtk.Clipboard gtkClipboard display name =   tryStop (unsafeGtkClipboard display name)  -- |Request the text contents of a GTK clipboard, catching all exceptions, and passing the result to a handler. -- If the clipboard is empty or an exception was thrown, the value passed to the handler is 'Left', otherwise 'Right'. clipboardRequest ::-  GI.Clipboard ->+  GiGtk.Clipboard ->   (Either Text Text -> IO ()) ->   IO () clipboardRequest clipboard handle =@@ -69,7 +67,7 @@     handle (Left (show e))   where     run =-      GI.clipboardRequestText clipboard (const (handle . maybeToRight "no clipboard text"))+      GiGtk.clipboardRequestText clipboard (const (handle . maybeToRight "no clipboard text"))  -- |Registers a callback for the "owner change" event of a GTK clipboard, which is triggered whenever a client updates -- the text.@@ -77,39 +75,39 @@ -- exception was thrown. subscribeWith ::   Member (Final IO) r =>-  GI.Clipboard ->+  GiGtk.Clipboard ->   (Either Text Text -> Sem r ()) ->   Sem r () subscribeWith clipboard handle =   withWeavingToFinal \ s wv _ -> do     let lower ma = void (wv (ma <$ s))-    s <$ GI.onClipboardOwnerChange clipboard \ _ ->+    s <$ GiGtk.onClipboardOwnerChange clipboard \ _ ->       clipboardRequest clipboard (lower . handle)  -- |Safely request the text contents of a clipboard by scheduling an action on the UI thread and converting exceptions -- into 'Stop'. readClipboard ::   Members [Log, Stop Text, Embed IO] r =>-  GI.Clipboard ->+  GiGtk.Clipboard ->   Sem r (Maybe Text) readClipboard =-  gtkUi "readClipboard" . GI.clipboardWaitForText+  gtkUi "readClipboard" . GiGtk.clipboardWaitForText  -- |Update the text contents of a clipboard. -- Does not catch exceptions. unsafeSetClipboard ::   MonadIO m =>-  GI.Clipboard ->+  GiGtk.Clipboard ->   Text ->   m () unsafeSetClipboard clipboard text =-  GI.clipboardSetText clipboard text (-1)+  GiGtk.clipboardSetText clipboard text (-1)  -- |Safely update the text contents of a clipboard by scheduling an action on the UI thread and converting exceptions -- into 'Stop'. writeClipboard ::   Members [Stop Text, Embed IO] r =>-  GI.Clipboard ->+  GiGtk.Clipboard ->   Text ->   Sem r () writeClipboard clipboard =@@ -140,10 +138,11 @@ subscribeToClipboard f selection = do   cb <- getClipboard selection   subscribeWith cb \case-    Right t ->+    Right t -> do+      Log.debug [exon|GTK subscriber for #{show selection}: received #{t}|]       f selection t     Left e ->-      Log.warn [exon|GTK: #{e}|]+      Log.warn [exon|GTK subscriber for #{show selection}: #{e}|]  -- |Fetch the text contents of the GTK clipboard corresponding to the specified X11 selection, converting exceptions -- into 'Stop'.
lib/Helic/Interpreter/Gtk.hs view
@@ -2,28 +2,51 @@ -- Internal. module Helic.Interpreter.Gtk where +import Exon (exon) import qualified GI.Gdk as GiGdk-import GI.Gdk (Display) import qualified GI.Gtk as GiGtk import Polysemy.Conc (interpretScopedResumable) import qualified Polysemy.Log as Log +import Helic.Data.X11Config (DisplayId (DisplayId), X11Config (X11Config)) import qualified Helic.Effect.Gtk as Gtk import Helic.Effect.Gtk (Gtk) import Helic.Gtk (getDisplay) import Helic.Stop (tryStop) +tryOpenDisplay ::+  Members [Stop Text, Log, Embed IO] r =>+  DisplayId ->+  GiGdk.DisplayManager ->+  Sem r ()+tryOpenDisplay (DisplayId fallbackDisplay) dm = do+  Log.warn [exon|No default display available. Trying to connect to #{fallbackDisplay}|]+  tryStop (GiGdk.displayManagerOpenDisplay dm fallbackDisplay) >>= \case+    Just _ ->+      Log.info [exon|Connected to display #{fallbackDisplay}|]+    Nothing ->+      stop [exon|Could not connect to display #{fallbackDisplay}|]++noDisplayAvailable ::+  Members [Stop Text, Embed IO] r =>+  GiGdk.DisplayManager ->+  Sem r Bool+noDisplayAvailable dm =+  tryStop (isNothing <$> GiGdk.displayManagerGetDefaultDisplay dm)+ -- |Initialize GTK, run the scoped action, then tear down the GTK environment. bracketGtk ::   Members [Resource, Log, Embed IO] r =>-  (Display -> Sem (Stop Text : r) a) ->+  DisplayId ->+  (GiGdk.Display -> Sem (Stop Text : r) a) ->   Sem (Stop Text : r) a-bracketGtk =+bracketGtk fallbackDisplay =   bracket acquire release   where     acquire = do       unlessM (fst <$> tryStop (GiGtk.initCheck Nothing)) do-        stop "GTK intialization failed"+        dm <- tryStop GiGdk.displayManagerGet+        ifM (noDisplayAvailable dm) (tryOpenDisplay fallbackDisplay dm) (stop "GTK intialization failed")       getDisplay     release display = do       Log.debug "Quitting the GTK main loop"@@ -36,9 +59,10 @@ -- This uses 'Scoped' to bracket the initialization and termination of the GTK environment. interpretGtk ::   Members [Resource, Log, Embed IO] r =>-  InterpreterFor (Scoped Display (Gtk Display) !! Text) r-interpretGtk =-  interpretScopedResumable bracketGtk \ display -> \case+  X11Config ->+  InterpreterFor (Scoped GiGdk.Display (Gtk GiGdk.Display) !! Text) r+interpretGtk (X11Config fallbackDisplay) =+  interpretScopedResumable (bracketGtk (fromMaybe ":0" fallbackDisplay)) \ display -> \case     Gtk.Main ->       GiGtk.main     Gtk.Resource ->
+ lib/Helic/Json.hs view
@@ -0,0 +1,21 @@+-- |Aeson TH deriving, Internal+module Helic.Json where++import qualified Data.Aeson as Aeson+import Data.Aeson.TH (deriveJSON)+import qualified Language.Haskell.TH as TH++-- |Derive Aeson codecs for single-field types with custom settings.+unaryJson :: TH.Name -> TH.Q [TH.Dec]+unaryJson =+  deriveJSON Aeson.defaultOptions {+    Aeson.fieldLabelModifier = dropWhile ('_' ==)+  }++-- |Derive Aeson codecs with custom settings.+json :: TH.Name -> TH.Q [TH.Dec]+json =+  deriveJSON Aeson.defaultOptions {+    Aeson.fieldLabelModifier = dropWhile ('_' ==),+    Aeson.unwrapUnaryRecords = True+  }
lib/Helic/Listen.hs view
@@ -24,7 +24,7 @@ listen =   Conc.subscribe do     Sync.putBlock Listening-    (forever (History.receive =<< Conc.consume))+    forever (History.receive =<< Conc.consume)  -- |Run an action with 'listen' in a thread, waiting for the event subscriber to be up and running before executing the -- action.
readme.md view
@@ -170,6 +170,7 @@ |`tmux.enable`|`true`|Whether to send events to *tmux*.| |`tmux.package`|`pkgs.tmux`|Only for *NixOS*: The `nixpkgs` package used for the *tmux* executable.| |`tmux.exe`|`tmux`|Only for YAML file: The path to the *tmux* executable|+|`x11.display`|`:0`|Only for YAML file: The path to the *tmux* executable|  # Neovim 
test/Helic/Dev.hs view
@@ -12,7 +12,7 @@  conf :: Config conf =-  Config (Just "dev") (Just def) (Just (NetConfig (Just 11111) Nothing Nothing)) Nothing (Just True)+  Config (Just "dev") (Just def) (Just (NetConfig (Just 11111) Nothing Nothing)) Nothing Nothing (Just True)  main :: IO () main =
test/Helic/Test/ConfigFileTest.hs view
@@ -1,5 +1,3 @@-{-# options_ghc -Wno-unused-imports #-}- module Helic.Test.ConfigFileTest where  import Path (Abs, File, Rel, absfile, relfile)@@ -11,15 +9,18 @@ import Helic.Data.Config (Config (Config)) import Helic.Data.NetConfig (NetConfig (NetConfig)) import Helic.Data.TmuxConfig (TmuxConfig (TmuxConfig))+import Helic.Data.X11Config (X11Config (X11Config))  target :: Config target =-  Config (Just "name") (Just tmux) (Just net) (Just 1000) (Just False)+  Config (Just "name") (Just tmux) (Just net) (Just x) (Just 1000) (Just False)   where     tmux =       TmuxConfig (Just True) (Just [absfile|/bin/tmux|])     net =       NetConfig (Just 10001) (Just 5) (Just ["remote:1000"])+    x =+      X11Config (Just ":1")  test_readConfigFile :: UnitTest test_readConfigFile = do