greenclip 2.0.0 → 2.0.1
raw patch · 4 files changed
+140/−15 lines, 4 filesdep +X11dep +unixdep +utf8-stringdep −Clipboard
Dependencies added: X11, unix, utf8-string
Dependencies removed: Clipboard
Files
- README.md +12/−11
- greenclip.cabal +7/−2
- src/Clipboard.hs +119/−0
- src/Main.hs +2/−2
README.md view
@@ -7,21 +7,22 @@ Recyle your clipboard selections with greenclip and don't waste your time anymore to reselect things other and other. -**Purpose :**+**Purpose:** Keeps track of your history of selections to quickly switch between them -**Demo :** -<video controls>- <source src="https://github.com/erebe/greenclip/releases/download/1.1/demo.webm" type="video/webm">- <a href="https://www.youtube.com/watch?v=Utk-9Gy8H3w">Video Link</a>-</video>+**Demo:** <a href="https://www.youtube.com/watch?v=Utk-9Gy8H3w">Video Link</a> -**Installation :**+**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) -1. It's a static binary so drop it anywhere in your $PATH env +**Installation:** -```wget https://github.com/erebe/greenclip/releases/download/1.3/greenclip```+1. It's a static binary so drop it anywhere in your $PATH env +```wget https://github.com/erebe/greenclip/releases/download/2.0/greenclip```+ Alternatively if you are using Archlinux you can install the package from AUR ``pacman -S greenclip``@@ -29,7 +30,7 @@ PS: If you want, you can add a permanent list of selections to be added to your current history. Go see the config file -**Usage :**+**Usage:** Greenclip is intended to be used with [rofi](https://github.com/DaveDavenport/rofi) @@ -38,7 +39,7 @@ 3. The entry that you have selected will be in your clipboard now 4. Configuration file can be found in ```.config/greenclip.cfg``` -**Compilation :**+**Compilation:** 1. Get [stack](https://docs.haskellstack.org/en/stable/README/) for Haskell 2. stack init && stack install
greenclip.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: greenclip-version: 2.0.0+version: 2.0.1 synopsis: Simple clipboard manager to be integrated with rofi description: Please see README.md category: Linux Desktop@@ -27,11 +27,16 @@ build-depends: base >= 4.7 && < 5 , classy-prelude- , Clipboard , text , vector , binary , microlens , microlens-mtl , directory+ , X11 >= 1.6+ , utf8-string+ , unix+ , directory+ other-modules:+ Clipboard default-language: Haskell2010
+ src/Clipboard.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE RecordWildCards #-}++module Clipboard+ ( getClipboardString+ , setClipboardString+ , getPrimaryClipboard+ ) where++import Graphics.X11.Xlib+import Graphics.X11.Xlib.Extras+import System.Posix.Process (forkProcess)++import Codec.Binary.UTF8.String (decode, encode)+import Control.Monad+import Data.Maybe+import Foreign (peekByteOff)+import Foreign.C.Types (CChar, CUChar)+import Foreign.Marshal.Array (withArrayLen)+import System.Directory (setCurrentDirectory)+import System.IO (hClose, stderr, stdin, stdout)+import System.IO.Unsafe (unsafePerformIO)+import Data.IORef+import Control.Concurrent (threadDelay)++getClipboardString :: IO (Maybe String)+getClipboardString = do+ (display, window, clipboards) <- readIORef initialSetup+ inp <- internAtom display "clipboard_get" False+ target <- internAtom display "UTF8_STRING" True+ xConvertSelection display (head clipboards) target inp window currentTime+ Just <$> clipboardInputWait display window inp++getPrimaryClipboard :: IO (Maybe String)+getPrimaryClipboard = do+ (display, window, clipboards) <- readIORef initialSetup+ inp <- internAtom display "clipboard_get" False+ target <- internAtom display "UTF8_STRING" True+ xConvertSelection display (clipboards !! 1) target inp window currentTime+ Just <$> clipboardInputWait display window inp++clipboardInputWait :: Display -> Window -> Atom -> IO String+clipboardInputWait display' window' inp' = allocaXEvent (go display' window' inp')+ where+ go display window inp evPtr = do+ waitForEvent display+ nextEvent display evPtr+ ev <- getEvent evPtr+ if ev_event_type ev == selectionNotify+ then charsToString . fromMaybe mempty <$> getWindowProperty8 display inp window+ else go display window inp evPtr+ waitForEvent display = do+ nbEvs <- pending display+ when (nbEvs == 0) $ threadDelay 100000 >> waitForEvent display++charsToString :: [CChar] -> String+charsToString = decode . map fromIntegral++setClipboardString :: String -> IO ()+setClipboardString str = void $ forkProcess $ do+ mapM_ hClose [stdin, stdout, stderr]+ setCurrentDirectory "/"+ (display, window, clipboards) <- readIORef initialSetup+ mapM_ (\atom -> xSetSelectionOwner display atom window currentTime) clipboards+ advertiseSelection display clipboards (stringToChars str)+ cleanup display window++advertiseSelection :: Display -> [Atom] -> [CUChar] -> IO ()+advertiseSelection display clipboards' str = allocaXEvent (go clipboards')+ where+ go [] _ = return ()+ go clipboards evPtr = do+ nextEvent display evPtr+ ev <- getEvent evPtr+ case ev of+ SelectionRequest {..} -> do+ target' <- getAtomName display ev_target+ res <- handleOutput display ev_requestor ev_property target' str+ sendSelectionNotify display ev_requestor ev_selection ev_target res ev_time+ go clipboards evPtr++ _ | ev_event_type ev == selectionClear -> do+ target <- peekByteOff evPtr 40 :: IO Atom+ go (filter (/= target) clipboards) evPtr++ _ -> go clipboards evPtr++handleOutput :: Display -> Window -> Atom -> Maybe String -> [CUChar] -> IO Atom+handleOutput display req prop (Just "UTF8_STRING") str = do+ prop' <- getAtomName display prop+ if isNothing prop' then handleOutput display req prop Nothing str else do+ target <- internAtom display "UTF8_STRING" True+ void $ withArrayLen str $ \len str' ->+ xChangeProperty display req prop target 8 propModeReplace str'+ (fromIntegral len)+ return prop+handleOutput _ _ _ _ _ = return none++sendSelectionNotify :: Display -> Window -> Atom -> Atom -> Atom -> Time -> IO ()+sendSelectionNotify display req sel target prop time = allocaXEvent $ \ev -> do+ setEventType ev selectionNotify+ setSelectionNotify ev req sel target prop time+ sendEvent display req False 0 ev++stringToChars :: String -> [CUChar]+stringToChars = map fromIntegral . encode++initialSetup :: IORef (Display, Window, [Atom])+initialSetup = unsafePerformIO $ do+ display <- openDisplay ""+ window <- createSimpleWindow display (defaultRootWindow display)+ 0 0 1 1 0 0 0+ clipboards <- internAtom display "CLIPBOARD" False+ newIORef (display, window, [clipboards, pRIMARY])+{-# NOINLINE initialSetup #-}++cleanup :: Display -> Window -> IO ()+cleanup display window = do+ destroyWindow display window+ closeDisplay display
src/Main.hs view
@@ -14,7 +14,7 @@ import qualified Data.Vector as V import Lens.Micro import Lens.Micro.Mtl-import qualified System.Clipboard as Clip+import qualified Clipboard as Clip import qualified System.Directory as Dir import System.Environment (lookupEnv) import System.IO (IOMode (..), openFile)@@ -153,7 +153,7 @@ -- Should rename COPY into ADVERTISE but as greenclip is already used I don't want to break configs -- of other people COPY sel -> advertiseSelection sel- HELP -> putStrLn $ "greenclip v1.3 -- Recyle your clipboard selections\n\n" <>+ HELP -> putStrLn $ "greenclip v2.0 -- 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" <>