packages feed

greenclip 3.4.0 → 4.0.0

raw patch · 4 files changed

+128/−51 lines, 4 filesdep +base16-bytestringdep +cryptohash-md5dep +tomland

Dependencies added: base16-bytestring, cryptohash-md5, tomland

Files

README.md view
@@ -4,7 +4,7 @@  ## Description -Recyle your clipboard selections with greenclip and don't waste your time anymore+Recycle your clipboard selections with greenclip and don't waste your time anymore to reselect things over and over.  **Purpose:**@@ -16,24 +16,42 @@  **Features:**   + Integrated with [rofi](https://github.com/DaveDavenport/rofi)-  + Permanently set some selections to added at the end (set `staticHistoryPath = your/file/with/static/entries` in the config file)-  + Merge X Primary selection with clipboard selection (set `usePrimarySelectionAsInput = True` in the config file)+  + Permanently set some selections to added at the end (set `static_history = []` in the config file)+  + Merge X Primary selection with clipboard selection (set `use_primary_selection_as_input = true` in the config file)   + Blacklist some applications (see `I want to blacklist some applications !` in the FAQ section)-  + Copy small images+  + Copy small images (you can disable it in the config)  ## Installation -1. It's a static binary so drop it anywhere in your $PATH env+- It's a static binary so drop it anywhere in your $PATH env -```wget https://github.com/erebe/greenclip/releases/download/3.3/greenclip```+```wget https://github.com/erebe/greenclip/releases/download/v4.2/greenclip``` -Alternatively if you are using Archlinux you can install the package from AUR +- Alternatively if you are using Archlinux you can install the package from AUR+ ``yay rofi-greenclip``  PS: If you want, you can add a permanent list of selections to be added to your current history. Go see the config file +Configuration file can be found at: +```+❯ cat ~/.config/greenclip.toml +[greenclip]+  history_file = "/home/erebe/.cache/greenclip.history"+  max_history_length = 50+  max_selection_size_bytes = 0+  trim_space_from_selection = true+  use_primary_selection_as_input = false+  blacklisted_applications = []+  enable_image_support = true+  image_cache_directory = "/tmp/greenclip"+  static_history = [+ '''¯\_(ツ)_/¯''',+]+```+ ## Usage  Greenclip is intended to be used with [rofi](https://github.com/DaveDavenport/rofi)@@ -41,7 +59,7 @@ 1. Spawn the daemon ``` greenclip daemon ``` 2. When ever you need to get your selections history ``` rofi -modi "clipboard:greenclip print" -show clipboard -run-command '{cmd}' ``` 3. The entry that you have selected will be in your clipboard now-4. Configuration file can be found in ```.config/greenclip.cfg```+4. Configuration file can be found in ```.config/greenclip.toml```  ## Migrating from 2.x version to 3.x one @@ -109,9 +127,9 @@  A. You can also use greenclip with [dmenu](http://tools.suckless.org/dmenu) or [fzf](https://github.com/junegunn/fzf). Example usage: -   `greenclip print | sed '/^$/d' | dmenu -i -l 10 -p clipboard | xargs -r -d'\n' -I '{}' greenclip print '{}'`+   `greenclip print | grep . | dmenu -i -l 10 -p clipboard | xargs -r -d'\n' -I '{}' greenclip print '{}'` -   `greenclip print | sed '/^$/d' | fzf -e | xargs -r -d'\n' -I '{}' greenclip print '{}'`+   `greenclip print | grep . | fzf -e | xargs -r -d'\n' -I '{}' greenclip print '{}'`  ---------- 
greenclip.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           greenclip-version:        3.4.0+version:        4.0.0 synopsis:       Simple clipboard manager to be integrated with rofi description:    Simple clipboard manager to be integrated with rofi - Please see README.md category:       Linux Desktop@@ -23,7 +23,7 @@   main-is: Main.hs   hs-source-dirs:       src-  ghc-options: -Wall -O3+  ghc-options: -Wall -O3 -Wno-name-shadowing   build-depends:       base >= 4 && < 5     , protolude@@ -39,9 +39,12 @@     , unix     , directory     , wordexp+    , tomland+    , base16-bytestring+    , cryptohash-md5     , X11 >= 1.6   other-modules:       Clipboard   default-language: Haskell2010   default-extensions: Strict-  pkgconfig-depends: x11, xcb, xau, xdmcp+  pkgconfig-depends: x11, xcb, xau, xdmcp, xscrnsaver
src/Clipboard.hs view
@@ -16,6 +16,7 @@  import           Data.Binary              (Binary) import qualified Data.ByteString          as B+import qualified Data.Text as T import           Lens.Micro  import           System.Directory         (setCurrentDirectory)@@ -33,6 +34,13 @@                    | BITMAP ByteString                    deriving (Show, Eq, Generic, Binary) +selectionLength :: Selection -> Int+selectionLength (Selection _ (UTF8 a)) = T.length a+selectionLength (Selection _ (PNG a)) = B.length a+selectionLength (Selection _ (JPEG a)) = B.length a+selectionLength (Selection _ (BITMAP a)) = B.length a++ data Selection = Selection {     appName   :: Text   , selection :: SelectionType@@ -47,7 +55,6 @@   , mimesPriorities  :: [Atom]   , defaultMime      :: Atom } deriving (Show)-  test :: IO () test =
src/Main.hs view
@@ -1,3 +1,4 @@+ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE MultiParamTypeClasses #-}@@ -13,37 +14,59 @@ import           Protolude.Conv        (toS)  import           Control.Monad.Catch   (MonadCatch, catchAll)+import qualified Crypto.Hash.MD5       as H import           Data.Binary           (decodeFile, encode) import qualified Data.ByteString       as B+import qualified Data.ByteString.Base16 as BB+import qualified Data.ByteString.Char8 as BC import           Data.List             (dropWhileEnd) import qualified Data.Text             as T+import qualified Data.Text.Encoding    as TE import           Data.Vector           (Vector) import qualified Data.Vector           as V import           Lens.Micro-import           Lens.Micro.Mtl+import           Lens.Micro.Mtl    hiding ((.=)) import qualified System.Directory      as Dir import           System.Posix.Files    (setFileMode) import           System.Environment    (lookupEnv) import           System.IO             (hClose, hGetContents) import           System.Timeout        (timeout) import           System.Wordexp.Simple (wordexp)+--import           System.Posix.Temp     (mkdtemp) +import Toml (TomlCodec, (.=))+import qualified Toml+ import qualified Clipboard             as Clip  -data Command = DAEMON | PRINT | COPY Text | CLEAR | HELP deriving (Show, Read)+data Command = DAEMON | PRINT | COPY Text | CLEAR | PRUNE FilePath | HELP deriving (Show, Read)  data Config = Config   { maxHistoryLength           :: Int+  , maxItemSizeBytes           :: Int   , historyPath                :: Text-  , staticHistoryPath          :: Text   , imageCachePath             :: Text   , usePrimarySelectionAsInput :: Bool   , blacklistedApps            :: [Text]   , trimSpaceFromSelection     :: Bool   , enableImageSupport         :: Bool+  , staticHistory              :: [Text]   } deriving (Show, Read) +configCodec :: TomlCodec Config+configCodec = Config+    <$> Toml.int "max_history_length"  .= maxHistoryLength+    <*> Toml.int "max_selection_size_bytes" .= maxItemSizeBytes+    <*> Toml.text "history_file" .= historyPath+    <*> Toml.text "image_cache_directory" .= imageCachePath+    <*> Toml.bool "use_primary_selection_as_input" .= usePrimarySelectionAsInput+    <*> Toml.arrayOf Toml._Text  "blacklisted_applications" .= blacklistedApps+    <*> Toml.bool "trim_space_from_selection" .= trimSpaceFromSelection+    <*> Toml.bool "enable_image_support" .= enableImageSupport+    <*> Toml.arrayOf Toml._Text  "static_history" .= staticHistory++ type ClipHistory = Vector Clip.Selection  readFile :: FilePath -> IO ByteString@@ -60,13 +83,11 @@  getStaticHistory :: (MonadIO m, MonadReader Config m) => m ClipHistory getStaticHistory = do-  storePath <- view $ to (toS . staticHistoryPath)-  liftIO $ readH storePath `catchAll` const mempty-  where-    readH filePath = readFile filePath <&> V.fromList . fmap toSelection . T.lines . toS-    toSelection txt = Clip.Selection "greenclip" (Clip.UTF8 txt)+  history <- view $ to (staticHistory)+  return . V.fromList $ Clip.Selection "greenclip" . Clip.UTF8 <$> history  + storeHistory :: (MonadIO m, MonadReader Config m) => ClipHistory -> m () storeHistory history = do   storePath <- view $ to (toS . historyPath)@@ -75,6 +96,17 @@     writeH storePath = B.writeFile storePath . toS . encode . V.toList  +pruneHistory :: (MonadIO m, MonadReader Config m) => FilePath -> m ()+pruneHistory path = do+  targets <- liftIO $ BC.lines <$> readFile path+  history <- getHistory+  storeHistory $ V.filter (go targets) history+  where+    go ts (Clip.Selection _ (Clip.UTF8 x)) = hashText x `notElem` ts+    go _  _                                = False+    hashText = BB.encode . H.hash . TE.encodeUtf8++ appendToHistory :: (MonadIO m, MonadReader Config m) => Clip.Selection -> ClipHistory -> m (ClipHistory, ClipHistory) appendToHistory sel history' = do   trimSelection <- view $ to trimSpaceFromSelection@@ -151,8 +183,16 @@     innerloop :: (MonadIO m, MonadReader Config m) => [(IO (Maybe Clip.Selection), Maybe Clip.Selection)] -> ClipHistory -> m ClipHistory     innerloop getSelections history = do       -- Get selection from enabled clipboards-      (getSelections', sel) <- liftIO $ getSelection getSelections+      (getSelections', rawSelection) <- liftIO $ getSelection getSelections +      -- Do not store selection items above threshold size+      maxItemSize <- view (to maxItemSizeBytes)+      let sel = case rawSelection of+            Nothing -> Nothing+            Just selection -> if maxItemSize > 0 && Clip.selectionLength selection >= maxItemSize+                              then Nothing+                              else Just selection+                    -- Do not use selection coming from blacklisted app       liftIO $ when (isJust sel) (print (Clip.appName <$> sel))       blacklist <- view (to blacklistedApps)@@ -224,40 +264,47 @@ getConfig :: IO Config getConfig = do   home <- Dir.getHomeDirectory-  let cfgPath = home <> "/.config/greenclip.cfg"--  cfgStr <- readFile cfgPath `catchAll` const mempty--  let unprettyCfg' = cfgStr & T.strip . T.replace "\n" "" . toS-  let unprettyCfg = if "trimSpaceFromSelection" `T.isInfixOf` unprettyCfg'-                      then unprettyCfg'-                      else T.replace "}" ", trimSpaceFromSelection = True }" unprettyCfg'-  let cfgMaybe = readMaybe $ toS unprettyCfg-  let cfg = fromMaybe defaultConfig cfgMaybe--  -- Write back the config file if the current one was invalid-  _ <- if isNothing cfgMaybe || unprettyCfg /= unprettyCfg'-        then let prettyCfg = show cfg & T.replace "," ",\n" . T.replace "{" "{\n " . T.replace "}" "\n}"-             in writeFile cfgPath prettyCfg-        else return ()--  historyPath' <- expandHomeDir $ cfg ^. to historyPath-  staticHistoryPath' <- expandHomeDir $ cfg ^. to staticHistoryPath-  imageCachePath' <- expandHomeDir $ cfg ^. to imageCachePath--  return $ cfg { historyPath = historyPath'-               , staticHistoryPath = staticHistoryPath'-               , imageCachePath = imageCachePath'-               }+  let configPath = home <> "/.config/greenclip.toml"+  +  configExist <- Dir.doesFileExist configPath+  when (not configExist) $ do +    config <- Toml.encode (Toml.table configCodec "greenclip") <$> defaultConfig+    writeFile configPath config+    return ()+  +  tomlRes <- Toml.decodeFileEither (Toml.table configCodec "greenclip") configPath+  when (isLeft tomlRes) $ do+    die . toS $  "Error parsing the config file at " <> (show configPath) <> "\n" <> Toml.prettyTomlDecodeErrors (fromLeft mempty tomlRes)+  +  let cfg = fromRight (Config 50 0 "" "" False [] True True []) tomlRes +  +  -- Replace $HOME|~|*... in config path+  cfg <- do+    imgCachePath <- wordexp . toS $ imageCachePath cfg+    historyP <- wordexp . toS $ historyPath cfg +    return $ cfg { imageCachePath = (toS $ headDef "" imgCachePath), historyPath = (toS $ headDef "" historyP)}+    +  -- if it ends with / we don't create a temp directory+  -- user is responsible for it+  -- cfg <- if (lastDef ' ' (toS $ imageCachePath cfg) /= '/')+  --    then do+  --      dirPath <- mkdtemp $ (toS $ imageCachePath cfg)+  --      return $ cfg { imageCachePath = toS dirPath }+  --    else return cfg+     +  return cfg    where-    defaultConfig = Config 50 "~/.cache/greenclip.history" "~/.cache/greenclip.staticHistory" "/tmp/greenclip/" False [] True True-    expandHomeDir str = (toS . fromMaybe (toS str) . listToMaybe <$> wordexp (toS str)) `catchAll` (\_ -> return $ toS str)+    defaultConfig = do +      homeDir <- toS . fromMaybe mempty . listToMaybe <$> wordexp "~/"+      return $ Config 50 0 (homeDir <> ".cache/greenclip.history") "/tmp/greenclip" False [] True True +        ["Greenclip has been updated to v4.1, update your new config file at ~/.config/greenclip.toml"]   parseArgs :: [Text] -> Command parseArgs ("daemon":_)   = DAEMON parseArgs ["clear"]      = CLEAR+parseArgs ["prune", p]   = PRUNE $ T.unpack p parseArgs ["print"]      = PRINT parseArgs ["print", sel] = COPY sel parseArgs _              = HELP@@ -269,13 +316,15 @@     DAEMON   -> runReaderT runDaemon cfg     PRINT    -> runReaderT printHistoryForRofi cfg     CLEAR    -> runReaderT (storeHistory mempty) cfg+    PRUNE p  -> runReaderT (pruneHistory p) cfg     -- Should rename COPY into ADVERTISE but as greenclip is already used I don't want to break configs     -- of other people     COPY sel -> runReaderT (advertiseSelection sel) cfg-    HELP     -> putText $ "greenclip v3.3 -- Recyle your clipboard selections\n\n" <>+    HELP     -> putText $ "greenclip v4.2 -- Recyle your clipboard selections\n\n" <>                           "Available commands\n" <>                           "daemon: Spawn the daemon that will listen to selections\n" <>                           "print:  Display all selections history\n" <>+                          "prune FILE: Remove selections in list of md5 hashes in FILE\n" <>                           "clear:  Clear history\n" <>                           "help:   Display this message\n"