From f4a9fceacd46697a625283eaa08302794f11f662 Mon Sep 17 00:00:00 2001
From: Jens Nolte <git@queezle.net>
Date: Fri, 2 Apr 2021 00:35:39 +0200
Subject: [PATCH] Add 'squeekboard' block

---
 src/QBar/Blocks.hs             |  2 +
 src/QBar/Blocks/Squeekboard.hs | 77 ++++++++++++++++++++++++++++++++++
 src/QBar/Cli.hs                | 10 ++++-
 src/QBar/Theme.hs              |  2 +-
 4 files changed, 88 insertions(+), 3 deletions(-)
 create mode 100644 src/QBar/Blocks/Squeekboard.hs

diff --git a/src/QBar/Blocks.hs b/src/QBar/Blocks.hs
index 60f9a70..1dccb3a 100644
--- a/src/QBar/Blocks.hs
+++ b/src/QBar/Blocks.hs
@@ -9,6 +9,7 @@ module QBar.Blocks
     QBar.Blocks.Qubes.qubesVMCountBlock,
     QBar.Blocks.Script.scriptBlock,
     QBar.Blocks.Script.pollScriptBlock,
+    QBar.Blocks.Squeekboard.squeekboardBlock,
   )
 where
 
@@ -19,3 +20,4 @@ import qualified QBar.Blocks.DiskUsage
 import qualified QBar.Blocks.NetworkManager
 import qualified QBar.Blocks.Qubes
 import qualified QBar.Blocks.Script
+import qualified QBar.Blocks.Squeekboard
diff --git a/src/QBar/Blocks/Squeekboard.hs b/src/QBar/Blocks/Squeekboard.hs
new file mode 100644
index 0000000..d32a589
--- /dev/null
+++ b/src/QBar/Blocks/Squeekboard.hs
@@ -0,0 +1,77 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+module QBar.Blocks.Squeekboard where
+
+import Control.Monad.Except (MonadError)
+import Data.Either (isRight)
+import qualified DBus
+import qualified DBus.Client as DBus
+import DBus.Internal.Message (signalBody)
+import Pipes.Core
+import QBar.BlockHelper
+import QBar.BlockOutput
+import QBar.Blocks.NetworkManager (getDBusProperty, runExceptT_)
+import QBar.Core
+
+squeekboardBlock :: Bool -> Block
+squeekboardBlock autoHide = runSignalBlockConfiguration $ SignalBlockConfiguration {
+  aquire,
+  release,
+  signalThread = Nothing,
+  signalBlock = networkManagerBlock',
+  interval = Nothing
+}
+  where
+    aquire :: (() -> IO ()) -> BarIO DBus.Client
+    aquire trigger = liftIO $ do
+      client <- DBus.connectSession
+      let matchRule = DBus.matchAny {
+        DBus.matchPath = Just "/sm/puri/OSK0",
+        DBus.matchInterface = Just "org.freedesktop.DBus.Properties",
+        DBus.matchMember = Just "PropertiesChanged"
+      }
+      void . DBus.addMatch client matchRule $ dbusSignalHandler trigger
+      let matchRule2 = DBus.matchAny {
+        DBus.matchSender = Just "org.freedesktop.DBus",
+        DBus.matchPath = Just "/org/freedesktop/DBus",
+        DBus.matchInterface = Just "org.freedesktop.DBus",
+        DBus.matchMember = Just "NameOwnerChanged"
+      }
+      void . DBus.addMatch client matchRule2 $ \signal -> when (nameOwnerChangedIsPuriOSK0 (signalBody signal)) $ dbusSignalHandler trigger signal
+      return client
+    nameOwnerChangedIsPuriOSK0 :: [DBus.Variant] -> Bool
+    nameOwnerChangedIsPuriOSK0 (path:_) = path == DBus.toVariant ("sm.puri.OSK0" :: String)
+    nameOwnerChangedIsPuriOSK0 _ = False
+    release :: DBus.Client -> BarIO ()
+    release = liftIO . DBus.disconnect
+    networkManagerBlock' :: DBus.Client -> SignalBlock ()
+    networkManagerBlock' client = (liftBarIO . networkManagerBlock'' client) >=> respond >=> networkManagerBlock' client
+    networkManagerBlock'' :: DBus.Client -> Signal () -> BarIO (Maybe BlockOutput)
+    networkManagerBlock'' client (EventSignal Click{button=1}) = do
+      mCurrent <- runExceptT_ (getVisible client)
+      case mCurrent of
+        (Just current) -> void $ setVisible client (not current)
+        Nothing -> return ()
+      networkManagerBlock''' client
+    networkManagerBlock'' client _ = networkManagerBlock''' client
+    networkManagerBlock''' :: DBus.Client -> BarIO (Maybe BlockOutput)
+    networkManagerBlock''' client = blockOutput <$> runExceptT_ (getVisible client)
+    blockOutput :: Maybe Bool -> Maybe BlockOutput
+    blockOutput (Just isEnabled) = Just (mkBlockOutput (mkText isEnabled normalImportant "⌨\xFE0E osk"))
+    blockOutput Nothing = if autoHide then Nothing else Just (mkBlockOutput $ mkText False errorImportant "⌨\xFE0E n/a")
+    dbusSignalHandler :: (() -> IO ()) -> DBus.Signal -> IO ()
+    dbusSignalHandler trigger _signal = trigger ()
+
+getVisible :: (MonadError () m, MonadIO m) => DBus.Client -> m Bool
+getVisible client = do
+  getDBusProperty
+    client
+    "sm.puri.OSK0"
+    "/sm/puri/OSK0"
+    "sm.puri.OSK0"
+    "Visible"
+
+setVisible :: (MonadIO m) => DBus.Client -> Bool -> m Bool
+setVisible client value = do
+  let methodCall = ((DBus.methodCall "/sm/puri/OSK0" "sm.puri.OSK0" "SetVisible") {DBus.methodCallDestination = Just "sm.puri.OSK0", DBus.methodCallBody = [DBus.toVariant value]})
+  isRight <$> liftIO (DBus.call client methodCall)
diff --git a/src/QBar/Cli.hs b/src/QBar/Cli.hs
index c1febf9..618c2b5 100644
--- a/src/QBar/Cli.hs
+++ b/src/QBar/Cli.hs
@@ -98,10 +98,11 @@ blockParser =
     hidden <>
     command "date" (info (pure $ addBlock dateBlock) (progDesc "Load the date and time block.")) <>
     command "cpu" (info (pure $ addBlock $ cpuUsageBlock 1) (progDesc "Load the cpu usage block.")) <>
-    command "battery" (info (pure $ addBlock $ batteryBlock) (progDesc "Load the battery block.")) <>
+    command "battery" (info (pure $ addBlock batteryBlock) (progDesc "Load the battery block.")) <>
     command "disk" (info diskUsageBlockParser (progDesc "Load the disk usage block.")) <>
     command "networkmanager" (info (pure $ addBlock networkManagerBlock) (progDesc "Load the network-manager block.")) <>
     command "script" (info scriptBlockParser (progDesc "Display the output of an external script as a block.")) <>
+    command "squeekboard" (info squeekboardParser (progDesc "Toggles the visibility of the 'squeekboard' on-screen-keyboard when clicked (squeekboard must be running).")) <>
     command "diskQubesPool" (info (pure $ addBlock diskUsageQubesBlock) (progDesc "Load a block that shows free space in Qubes' default pool.")) <>
     command "qubesProperty" (info qubesPropertyBlockParser (progDesc "Display the current value of a Qubes property.")) <>
     command "qubesCount" (info (pure $ addBlock qubesVMCountBlock) (progDesc "Display the number of running Qubes (VMs)."))
@@ -122,12 +123,17 @@ scriptBlockParser = helper <*> do
     long "interval" <>
     short 'i' <>
     metavar "SECONDS" <>
-    (help $ "Interval to use for --poll mode (default: " <> humanReadableInterval defaultInterval <> ")")
+    help ("Interval to use for --poll mode (default: " <> humanReadableInterval defaultInterval <> ")")
     ))
   clickEvents <- switch $ long "events" <> short 'e' <> help "Send click events to stdin of the script"
   script <- strArgument (metavar "SCRIPT" <> help "The script that will be executed with a shell.")
   return $ (if poll then addBlock . pollScriptBlock pollInterval else addBlock . scriptBlock clickEvents) script
 
+squeekboardParser :: Parser (BarIO ())
+squeekboardParser = do
+  autoHide <- switch $ long "auto-hide" <> short 'q' <> help "Hide the block (instead of showing an error) when squeekboard is not running."
+  return $ addBlock (squeekboardBlock autoHide)
+
 qubesPropertyBlockParser :: Parser (BarIO ())
 qubesPropertyBlockParser  = do
   name <- strArgument (metavar "NAME" <> help "The NAME of the property.")
diff --git a/src/QBar/Theme.hs b/src/QBar/Theme.hs
index ba9dbc2..e864808 100644
--- a/src/QBar/Theme.hs
+++ b/src/QBar/Theme.hs
@@ -138,7 +138,7 @@ rainbowTheme = AnimatedTheme rainbowThemePipe
     rainbowThemePipe :: AnimatedTheme
     rainbowThemePipe = do
       time <- liftIO $ fromRational . toRational <$> getPOSIXTime
-      yield =<< rainbowThemePipe' time <$> await
+      yield . rainbowThemePipe' time =<< await
       rainbowThemePipe
     rainbowThemePipe' :: Double -> StaticTheme
     rainbowThemePipe' time blocks = reverse $ evalState (mapM rainbowBlock $ reverse blocks) 0
-- 
GitLab