Compare commits

..

1 Commits

Author SHA1 Message Date
Nathan Dwarshuis ffa4e593bc WIP use nix (alsa not linked) 2022-07-23 01:17:58 -04:00
57 changed files with 4445 additions and 5270 deletions

357
.stylish-haskell.yaml Normal file
View File

@ -0,0 +1,357 @@
# stylish-haskell configuration file
# ==================================
# The stylish-haskell tool is mainly configured by specifying steps. These steps
# are a list, so they have an order, and one specific step may appear more than
# once (if needed). Each file is processed by these steps in the given order.
steps:
# Convert some ASCII sequences to their Unicode equivalents. This is disabled
# by default.
# - unicode_syntax:
# # In order to make this work, we also need to insert the UnicodeSyntax
# # language pragma. If this flag is set to true, we insert it when it's
# # not already present. You may want to disable it if you configure
# # language extensions using some other method than pragmas. Default:
# # true.
# add_language_pragma: true
# Format module header
#
# Currently, this option is not configurable and will format all exports and
# module declarations to minimize diffs
#
# - module_header:
# # How many spaces use for indentation in the module header.
# indent: 4
#
# # Should export lists be sorted? Sorting is only performed within the
# # export section, as delineated by Haddock comments.
# sort: true
#
# # See `separate_lists` for the `imports` step.
# separate_lists: true
# Format record definitions. This is disabled by default.
#
# You can control the layout of record fields. The only rules that can't be configured
# are these:
#
# - "|" is always aligned with "="
# - "," in fields is always aligned with "{"
# - "}" is likewise always aligned with "{"
#
# - records:
# # How to format equals sign between type constructor and data constructor.
# # Possible values:
# # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor.
# # - "indent N" -- insert a new line and N spaces from the beginning of the next line.
# equals: "indent 2"
#
# # How to format first field of each record constructor.
# # Possible values:
# # - "same_line" -- "{" and first field goes on the same line as the data constructor.
# # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor
# first_field: "indent 2"
#
# # How many spaces to insert between the column with "," and the beginning of the comment in the next line.
# field_comment: 2
#
# # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines.
# deriving: 2
#
# # How many spaces to insert before "via" clause counted from indentation of deriving clause
# # Possible values:
# # - "same_line" -- "via" part goes on the same line as "deriving" keyword.
# # - "indent N" -- insert a new line and N spaces from the beginning of "deriving" keyword.
# via: "indent 2"
#
# # Sort typeclass names in the "deriving" list alphabetically.
# sort_deriving: true
#
# # Wheter or not to break enums onto several lines
# #
# # Default: false
# break_enums: false
#
# # Whether or not to break single constructor data types before `=` sign
# #
# # Default: true
# break_single_constructors: true
#
# # Whether or not to curry constraints on function.
# #
# # E.g: @allValues :: Enum a => Bounded a => Proxy a -> [a]@
# #
# # Instead of @allValues :: (Enum a, Bounded a) => Proxy a -> [a]@
# #
# # Default: false
# curried_context: false
# Align the right hand side of some elements. This is quite conservative
# and only applies to statements where each element occupies a single
# line.
# Possible values:
# - always - Always align statements.
# - adjacent - Align statements that are on adjacent lines in groups.
# - never - Never align statements.
# All default to always.
- simple_align:
cases: always
top_level_patterns: always
records: always
multi_way_if: always
# Import cleanup
- imports:
# There are different ways we can align names and lists.
#
# - global: Align the import names and import list throughout the entire
# file.
#
# - file: Like global, but don't add padding when there are no qualified
# imports in the file.
#
# - group: Only align the imports per group (a group is formed by adjacent
# import lines).
#
# - none: Do not perform any alignment.
#
# Default: global.
align: global
# The following options affect only import list alignment.
#
# List align has following options:
#
# - after_alias: Import list is aligned with end of import including
# 'as' and 'hiding' keywords.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - with_alias: Import list is aligned with start of alias or hiding.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - with_module_name: Import list is aligned `list_padding` spaces after
# the module name.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# init, last, length)
#
# This is mainly intended for use with `pad_module_names: false`.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# init, last, length, scanl, scanr, take, drop,
# sort, nub)
#
# - new_line: Import list starts always on new line.
#
# > import qualified Data.List as List
# > (concat, foldl, foldr, head, init, last, length)
#
# - repeat: Repeat the module name to align the import list.
#
# > import qualified Data.List as List (concat, foldl, foldr, head)
# > import qualified Data.List as List (init, last, length)
#
# Default: after_alias
list_align: after_alias
# Right-pad the module names to align imports in a group:
#
# - true: a little more readable
#
# > import qualified Data.List as List (concat, foldl, foldr,
# > init, last, length)
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
# > init, last, length)
#
# - false: diff-safe
#
# > import qualified Data.List as List (concat, foldl, foldr, init,
# > last, length)
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
# > init, last, length)
#
# Default: true
pad_module_names: true
# Long list align style takes effect when import is too long. This is
# determined by 'columns' setting.
#
# - inline: This option will put as much specs on same line as possible.
#
# - new_line: Import list will start on new line.
#
# - new_line_multiline: Import list will start on new line when it's
# short enough to fit to single line. Otherwise it'll be multiline.
#
# - multiline: One line per import list entry.
# Type with constructor list acts like single import.
#
# > import qualified Data.Map as M
# > ( empty
# > , singleton
# > , ...
# > , delete
# > )
#
# Default: inline
long_list_align: multiline
# Align empty list (importing instances)
#
# Empty list align has following options
#
# - inherit: inherit list_align setting
#
# - right_after: () is right after the module name:
#
# > import Vector.Instances ()
#
# Default: inherit
empty_list_align: inherit
# List padding determines indentation of import list on lines after import.
# This option affects 'long_list_align'.
#
# - <integer>: constant value
#
# - module_name: align under start of module name.
# Useful for 'file' and 'group' align settings.
#
# Default: 4
list_padding: 4
# Separate lists option affects formatting of import list for type
# or class. The only difference is single space between type and list
# of constructors, selectors and class functions.
#
# - true: There is single space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable (fold, foldl, foldMap))
#
# - false: There is no space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable(fold, foldl, foldMap))
#
# Default: true
separate_lists: true
# Space surround option affects formatting of import lists on a single
# line. The only difference is single space after the initial
# parenthesis and a single space before the terminal parenthesis.
#
# - true: There is single space associated with the enclosing
# parenthesis.
#
# > import Data.Foo ( foo )
#
# - false: There is no space associated with the enclosing parenthesis
#
# > import Data.Foo (foo)
#
# Default: false
space_surround: false
# Enabling this argument will use the new GHC lib parse to format imports.
#
# This currently assumes a few things, it will assume that you want post
# qualified imports. It is also not as feature complete as the old
# imports formatting.
#
# It does not remove redundant lines or merge lines. As such, the full
# feature scope is still pending.
#
# It _is_ however, a fine alternative if you are using features that are
# not parseable by haskell src extensions and you're comfortable with the
# presets.
#
# Default: false
ghc_lib_parser: false
# Language pragmas
- language_pragmas:
# We can generate different styles of language pragma lists.
#
# - vertical: Vertical-spaced language pragmas, one per line.
#
# - compact: A more compact style.
#
# - compact_line: Similar to compact, but wrap each line with
# `{-#LANGUAGE #-}'.
#
# Default: vertical.
style: vertical
# Align affects alignment of closing pragma brackets.
#
# - true: Brackets are aligned in same column.
#
# - false: Brackets are not aligned together. There is only one space
# between actual import and closing bracket.
#
# Default: true
align: true
# stylish-haskell can detect redundancy of some language pragmas. If this
# is set to true, it will remove those redundant pragmas. Default: true.
remove_redundant: true
# Language prefix to be used for pragma declaration, this allows you to
# use other options non case-sensitive like "language" or "Language".
# If a non correct String is provided, it will default to: LANGUAGE.
language_prefix: LANGUAGE
# Replace tabs by spaces. This is disabled by default.
# - tabs:
# # Number of spaces to use for each tab. Default: 8, as specified by the
# # Haskell report.
# spaces: 8
# Remove trailing whitespace
- trailing_whitespace: {}
# Squash multiple spaces between the left and right hand sides of some
# elements into single spaces. Basically, this undoes the effect of
# simple_align but is a bit less conservative.
# - squash: {}
# A common setting is the number of columns (parts of) code will be wrapped
# to. Different steps take this into account.
#
# Set this to null to disable all line wrapping.
#
# Default: 80.
columns: 80
# By default, line endings are converted according to the OS. You can override
# preferred format here.
#
# - native: Native newline format. CRLF on Windows, LF on other OSes.
#
# - lf: Convert to LF ("\n").
#
# - crlf: Convert to CRLF ("\r\n").
#
# Default: native.
newline: native
# Sometimes, language extensions are specified in a cabal file or from the
# command line instead of using language pragmas in the file. stylish-haskell
# needs to be aware of these, so it can parse the file correctly.
#
# No language extensions are enabled by default.
# language_extensions:
# - TemplateHaskell
# - QuasiQuotes
# Attempt to find the cabal file in ancestors of the current directory, and
# parse options (currently only language extensions) from that.
#
# Default: true
cabal: true

View File

@ -18,33 +18,3 @@ Built just for me...although you may fork if you like it ;)
* selecting Wifi networks (networkmanager_dmenu) * selecting Wifi networks (networkmanager_dmenu)
* clipboard management (greenclip) * clipboard management (greenclip)
* mounting disks * mounting disks
# Installation
The "easy" way will only work on Arch out of the box.
After cloning this repo, move to the root of this repo and install the build
dependency packages:
```
pacman -S --needed - < make_pkgs
```
Build/install xmonad/xmobar binaries:
```
stack install
```
Install official runtime dependencies:
```
pacman -S --needed $(./scripts/pacman_deps)
```
Install unofficial runtime dependencies with your favorite AUR helper (which is
obviously yay):
```
yay -S $(./scripts/aur_deps)
```

View File

@ -1,74 +0,0 @@
-- | Start a VirtualBox instance with a sentinel wrapper process.
--
-- The only reason why this is needed is because I want to manage virtualboxes
-- in their own dynamic workspaces, which are currently set up to correspond to
-- one process. The problem with Virtualbox is that the VBoxManage command
-- spawns a new VM and then exits, which means the process that was originally
-- attached to the dynamic workspace only exists for a few seconds when the VM
-- is starting.
--
-- Solution: Run VBoxManage in a wrapper binary that launches the VM and sleeps
-- until its PID exits. By monitoring this wrapper, the dynamic workspace only
-- has one process to track and will maintain the workspace throughout the
-- lifetime of the VM.
module Main (main) where
import qualified Data.ByteString.Lazy.UTF8 as BU
import RIO
import RIO.Process
import qualified RIO.Text as T
import System.Process (Pid)
import Text.XML.Light
import UnliftIO.Environment
import XMonad.Internal.Concurrent.VirtualBox
import XMonad.Internal.IO
main :: IO ()
main = do
args <- getArgs
runSimpleApp $
runAndWait args
runAndWait :: [String] -> RIO SimpleApp ()
runAndWait [n] = do
c <- liftIO $ vmInstanceConfig (T.pack n)
either (logError . displayBytesUtf8 . encodeUtf8) runConfig c
where
runConfig c = maybe err runID =<< vmMachineID c
runID i = do
vmLaunch i
p <- vmPID i
liftIO $ mapM_ waitUntilExit p
err = logError "Could not get machine ID"
runAndWait _ = logInfo "Usage: vbox-start VBOXNAME"
vmLaunch :: T.Text -> RIO SimpleApp ()
vmLaunch i = do
rc <- proc "VBoxManage" ["startvm", T.unpack i] runProcess
case rc of
ExitSuccess -> return ()
_ ->
logError $
"Failed to start VM: "
<> displayBytesUtf8 (encodeUtf8 i)
vmPID :: T.Text -> RIO SimpleApp (Maybe Pid)
vmPID vid = do
(rc, out) <- proc "pgrep" ["-f", "VirtualBoxVM.*" ++ T.unpack vid] readProcessStdout
return $ case rc of
ExitSuccess -> readMaybe $ BU.toString out
_ -> Nothing
vmMachineID :: FilePath -> RIO SimpleApp (Maybe T.Text)
vmMachineID iPath = do
res <- tryAny $ readFileUtf8 iPath
case res of
Right contents -> return $ findMachineID contents
Left e -> logError (displayShow e) >> return Nothing
where
findMachineID c =
T.stripSuffix "}"
=<< T.stripPrefix "{"
=<< (fmap T.pack . findAttr (blank_name {qName = "uuid"}))
=<< (\e -> findChild (qual e "Machine") e)
=<< parseXMLDoc c

View File

@ -1,3 +1,6 @@
module Main (main) where
--------------------------------------------------------------------------------
-- | Xmobar binary -- | Xmobar binary
-- --
-- Features: -- Features:
@ -7,80 +10,52 @@
-- * Some custom plugins (imported below) -- * Some custom plugins (imported below)
-- * Theme integration with xmonad (shared module imported below) -- * Theme integration with xmonad (shared module imported below)
-- * A custom Locks plugin from my own forked repo -- * A custom Locks plugin from my own forked repo
module Main (main) where
import Data.Internal.DBus import Data.Internal.DBus
import Data.Internal.XIO import Data.Internal.Dependency
import GHC.Enum (enumFrom) import Data.List
import Options.Applicative import Data.Maybe
import RIO hiding (hFlush)
import RIO.FilePath import System.Exit
import RIO.List import System.IO
import qualified RIO.NonEmpty as NE import System.IO.Error
import qualified RIO.Text as T
import XMonad.Core hiding (config) import Xmobar.Plugins.Bluetooth
import XMonad.Internal.Command.Power import Xmobar.Plugins.ClevoKeyboard
import XMonad.Internal.DBus.Brightness.ClevoKeyboard import Xmobar.Plugins.Device
import XMonad.Internal.DBus.Brightness.IntelBacklight import Xmobar.Plugins.IntelBacklight
import XMonad.Internal.DBus.Control import Xmobar.Plugins.Screensaver
import XMonad.Internal.DBus.Screensaver (ssSignalDep) import Xmobar.Plugins.VPN
import qualified XMonad.Internal.Theme as XT
import Xmobar hiding import System.Posix.Signals
( iconOffset import XMonad.Core hiding (config)
, run import XMonad.Hooks.DynamicLog hiding (xmobar)
) import XMonad.Internal.Command.Desktop
import Xmobar.Plugins.ActiveConnection import XMonad.Internal.Command.Power
import Xmobar.Plugins.Bluetooth import XMonad.Internal.DBus.Brightness.ClevoKeyboard
import Xmobar.Plugins.ClevoKeyboard import XMonad.Internal.DBus.Brightness.IntelBacklight
import Xmobar.Plugins.Common import XMonad.Internal.DBus.Control
import Xmobar.Plugins.IntelBacklight import XMonad.Internal.DBus.Screensaver (ssSignalDep)
import Xmobar.Plugins.Screensaver import XMonad.Internal.Process hiding (CmdSpec)
import qualified XMonad.Internal.Theme as T
import Xmobar hiding
( iconOffset
)
import Xmobar.Plugins.Common
main :: IO () main :: IO ()
main = parse >>= xio main = do
db <- connectDBus
c <- withCache $ evalConfig db
disconnectDBus db
-- this is needed to prevent waitForProcess error when forking in plugins (eg
-- alsacmd)
_ <- installHandler sigCHLD Default Nothing
-- this is needed to see any printed messages
hFlush stdout
xmobar c
parse :: IO XOpts evalConfig :: DBusState -> FIO Config
parse = execParser opts
where
parseOpts = parseDeps <|> parseTest <|> pure XRun
opts =
info (parseOpts <**> helper) $
fullDesc <> header "xmobar: the best taskbar ever"
data XOpts = XDeps | XTest | XRun
parseDeps :: Parser XOpts
parseDeps =
flag'
XDeps
(long "deps" <> short 'd' <> help "print dependencies")
parseTest :: Parser XOpts
parseTest =
flag'
XTest
(long "test" <> short 't' <> help "test dependencies without running")
xio :: XOpts -> IO ()
xio o = case o of
XDeps -> hRunXIO False stderr printDeps
XTest -> hRunXIO False stderr $ withDBus_ Nothing Nothing evalConfig
XRun -> runXIO "xmobar.log" run
run :: XIO ()
run = do
-- IDK why this is needed, I thought this was default
liftIO $ hSetBuffering stdout LineBuffering
-- this isn't totally necessary except for the fact that killing xmobar
-- will make it print something about catching SIGTERM, and without
-- linebuffering it usually only prints the first few characters (even then
-- it only prints 10-20% of the time)
liftIO $ hSetBuffering stderr LineBuffering
-- TODO do these dbus things really need to remain connected?
c <- withDBus Nothing Nothing evalConfig
liftIO $ xmobar c
evalConfig :: DBusState -> XIO Config
evalConfig db = do evalConfig db = do
cs <- getAllCommands <$> rightPlugins db cs <- getAllCommands <$> rightPlugins db
bf <- getTextFont bf <- getTextFont
@ -88,20 +63,11 @@ evalConfig db = do
d <- io $ cfgDir <$> getDirectories d <- io $ cfgDir <$> getDirectories
return $ config bf ifs ios cs d return $ config bf ifs ios cs d
printDeps :: XIO ()
printDeps = withDBus_ Nothing Nothing $ \db ->
mapM_ logInfo $
fmap showFulfillment $
sort $
nub $
concatMap dumpFeature $
allFeatures db
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- toplevel configuration -- | toplevel configuration
-- | The text font family -- | The text font family
textFont :: Always XT.FontBuilder textFont :: Always T.FontBuilder
textFont = fontAlways "XMobar Text Font" "DejaVu Sans Mono" defFontPkgs textFont = fontAlways "XMobar Text Font" "DejaVu Sans Mono" defFontPkgs
-- | Offset of the text in the bar -- | Offset of the text in the bar
@ -109,94 +75,84 @@ textFontOffset :: Int
textFontOffset = 16 textFontOffset = 16
-- | Attributes for the bar font (size, weight, etc) -- | Attributes for the bar font (size, weight, etc)
textFontData :: XT.FontData textFontData :: T.FontData
textFontData = XT.defFontData {XT.weight = Just XT.Bold, XT.size = Just 11} textFontData = T.defFontData { T.weight = Just T.Bold, T.size = Just 11 }
-- | The icon font family -- | The icon font family
iconFont :: Sometimes XT.FontBuilder iconFont :: Sometimes T.FontBuilder
iconFont = iconFont = fontSometimes "XMobar Icon Font" "Symbols Nerd Font"
fontSometimes [Package Official "ttf-nerd-fonts-symbols"]
"XMobar Icon Font"
"Symbols Nerd Font"
[Package Official "ttf-nerd-fonts-symbols"]
-- | Offsets for the icons in the bar (relative to the text offset) -- | Offsets for the icons in the bar (relative to the text offset)
iconOffset :: BarFont -> Int iconOffset :: BarFont -> Int
iconOffset IconSmall = 0 iconOffset IconSmall = 0
iconOffset IconMedium = 1 iconOffset IconMedium = 1
iconOffset IconLarge = 1 iconOffset IconLarge = 1
iconOffset IconXLarge = 2 iconOffset IconXLarge = 2
-- | Sizes (in pixels) for the icon fonts -- | Sizes (in pixels) for the icon fonts
iconSize :: BarFont -> Int iconSize :: BarFont -> Int
iconSize IconSmall = 13 iconSize IconSmall = 13
iconSize IconMedium = 15 iconSize IconMedium = 15
iconSize IconLarge = 18 iconSize IconLarge = 18
iconSize IconXLarge = 20 iconSize IconXLarge = 20
-- | Attributes for icon fonts -- | Attributes for icon fonts
iconFontData :: Int -> XT.FontData iconFontData :: Int -> T.FontData
iconFontData s = XT.defFontData {XT.pixelsize = Just s, XT.size = Nothing} iconFontData s = T.defFontData { T.pixelsize = Just s, T.size = Nothing }
-- | Global configuration -- | Global configuration
-- Note that the 'font' and 'textOffset' are assumed to pertain to one (and -- Note that the 'font' and 'textOffset' are assumed to pertain to one (and
-- only one) text font, and all other fonts are icon fonts. If this assumption -- only one) text font, and all other fonts are icon fonts. If this assumption
-- changes the code will need to change significantly -- changes the code will need to change significantly
config :: T.Text -> [T.Text] -> [Int] -> BarRegions -> FilePath -> Config config :: String -> [String] -> [Int] -> BarRegions -> FilePath -> Config
config bf ifs ios br confDir = config bf ifs ios br confDir = defaultConfig
defaultConfig { font = bf
{ font = T.unpack bf , additionalFonts = ifs
, additionalFonts = fmap T.unpack ifs , textOffset = textFontOffset
, textOffset = textFontOffset , textOffsets = ios
, textOffsets = ios , bgColor = T.bgColor
, bgColor = T.unpack XT.bgColor , fgColor = T.fgColor
, fgColor = T.unpack XT.fgColor , position = BottomSize C 100 24
, position = BottomSize C 100 24 , border = NoBorder
, border = NoBorder , borderColor = T.bordersColor
, borderColor = T.unpack XT.bordersColor
, sepChar = T.unpack pSep , sepChar = pSep
, alignSep = [lSep, rSep] , alignSep = [lSep, rSep]
, template = T.unpack $ fmtRegions br , template = fmtRegions br
, lowerOnStart = False
, hideOnStart = False , lowerOnStart = False
, allDesktops = True , hideOnStart = False
, overrideRedirect = True , allDesktops = True
, pickBroadest = False , overrideRedirect = True
, persistent = True , pickBroadest = False
, -- store the icons with the xmonad/xmobar stack project , persistent = True
iconRoot = confDir </> "assets" </> "icons" -- store the icons with the xmonad/xmobar stack project
, commands = csRunnable <$> concatRegions br , iconRoot = confDir ++ "/icons"
}
, commands = csRunnable <$> concatRegions br
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- plugin features -- | plugin features
-- --
-- some commands depend on the presence of interfaces that can only be -- some commands depend on the presence of interfaces that can only be
-- determined at runtime; define these checks here -- determined at runtime; define these checks here
getAllCommands :: [Maybe CmdSpec] -> BarRegions getAllCommands :: [Maybe CmdSpec] -> BarRegions
getAllCommands right = getAllCommands right = BarRegions
BarRegions { brLeft = [ CmdSpec
{ brLeft = { csAlias = "UnsafeStdinReader"
[ CmdSpec , csRunnable = Run UnsafeStdinReader
{ csAlias = "UnsafeStdinReader" }
, csRunnable = Run UnsafeStdinReader ]
} , brCenter = []
] , brRight = catMaybes right
, brCenter = [] }
, brRight = catMaybes right
}
rightPlugins :: DBusState -> XIO [Maybe CmdSpec] rightPlugins :: DBusState -> FIO [Maybe CmdSpec]
rightPlugins db = rightPlugins DBusState { dbSesClient = ses, dbSysClient = sys }
mapM evalFeature $ = mapM evalFeature
allFeatures db
++ [always' "date indicator" dateCmd]
where
always' n = Right . Always n . Always_ . FallbackAlone
allFeatures :: DBusState -> [Feature CmdSpec]
allFeatures DBusState {dbSesClient = ses, dbSysClient = sys} =
[ Left getWireless [ Left getWireless
, Left $ getEthernet sys , Left $ getEthernet sys
, Left $ getVPN sys , Left $ getVPN sys
@ -207,67 +163,56 @@ allFeatures DBusState {dbSesClient = ses, dbSysClient = sys} =
, Left $ getCk ses , Left $ getCk ses
, Left $ getSs ses , Left $ getSs ses
, Right getLock , Right getLock
, always' "date indicator" dateCmd
] ]
where
always' n = Right . Always n . Always_ . FallbackAlone
type BarFeature = Sometimes CmdSpec type BarFeature = Sometimes CmdSpec
-- TODO what if I don't have a wireless card? -- TODO what if I don't have a wireless card?
getWireless :: BarFeature getWireless :: BarFeature
getWireless = getWireless = Sometimes "wireless status indicator" xpfWireless
Sometimes [Subfeature (IORoot wirelessCmd $ Only readWireless) "sysfs path"]
"wireless status indicator"
xpfWireless
[Subfeature (IORoot wirelessCmd $ Only readWireless) "sysfs path"]
getEthernet :: Maybe NamedSysConnection -> BarFeature getEthernet :: Maybe SysClient -> BarFeature
getEthernet cl = iconDBus_ "ethernet status indicator" xpfEthernet root (Only_ devDep) getEthernet cl = iconDBus "ethernet status indicator" (const True) root tree
where where
root useIcon tree' = root useIcon tree' = DBusRoot (const . ethernetCmd useIcon) tree' cl
DBusRoot_ (const $ ethernetCmd useIcon) tree' cl tree = And1 (Only readEthernet) (Only_ devDep)
getBattery :: BarFeature getBattery :: BarFeature
getBattery = iconIO_ "battery level indicator" xpfBattery root tree getBattery = iconIO_ "battery level indicator" xpfBattery root tree
where where
root useIcon = IORoot_ (batteryCmd useIcon) root useIcon = IORoot_ (batteryCmd useIcon)
tree = tree = Only_ $ IOTest_ "Test if battery is present" []
Only_ $ $ fmap (Msg Error) <$> hasBattery
IOTest_ "Test if battery is present" [] $
io $
fmap (Msg LevelError) <$> hasBattery
getVPN :: Maybe NamedSysConnection -> BarFeature getVPN :: Maybe SysClient -> BarFeature
getVPN cl = iconDBus_ "VPN status indicator" xpfVPN root (Only_ devDep) getVPN cl = iconDBus_ "VPN status indicator" xpfVPN root $ toAnd_ vpnDep test
where where
root useIcon tree = DBusRoot_ (const $ vpnCmd useIcon) tree cl root useIcon tree = DBusRoot_ (const $ vpnCmd useIcon) tree cl
test = DBusIO $ IOTest_ "Use nmcli to test if VPN is present"
networkManagerPkgs vpnPresent
getBt :: Maybe NamedSysConnection -> BarFeature getBt :: Maybe SysClient -> BarFeature
getBt = xmobarDBus "bluetooth status indicator" xpfBluetooth btDep btCmd getBt = xmobarDBus "bluetooth status indicator" xpfBluetooth btDep btCmd
getAlsa :: BarFeature getAlsa :: BarFeature
getAlsa = getAlsa = iconIO_ "volume level indicator" (const True) root
iconIO_ "volume level indicator" (const True) root $ $ Only_ $ sysExe [Package Official "alsa-utils"] "alsactl"
Only_ $
sysExe [Package Official "alsa-utils"] "alsactl"
where where
root useIcon = IORoot_ (alsaCmd useIcon) root useIcon = IORoot_ (alsaCmd useIcon)
getBl :: Maybe NamedSesConnection -> BarFeature getBl :: Maybe SesClient -> BarFeature
getBl = getBl = xmobarDBus "Intel backlight indicator" xpfIntelBacklight
xmobarDBus intelBacklightSignalDep blCmd
"Intel backlight indicator"
xpfIntelBacklight
intelBacklightSignalDep
blCmd
getCk :: Maybe NamedSesConnection -> BarFeature getCk :: Maybe SesClient -> BarFeature
getCk = getCk = xmobarDBus "Clevo keyboard indicator" xpfClevoBacklight
xmobarDBus clevoKeyboardSignalDep ckCmd
"Clevo keyboard indicator"
xpfClevoBacklight
clevoKeyboardSignalDep
ckCmd
getSs :: Maybe NamedSesConnection -> BarFeature getSs :: Maybe SesClient -> BarFeature
getSs = xmobarDBus "screensaver indicator" (const True) ssSignalDep ssCmd getSs = xmobarDBus "screensaver indicator" (const True) ssSignalDep ssCmd
getLock :: Always CmdSpec getLock :: Always CmdSpec
@ -276,268 +221,205 @@ getLock = always1 "lock indicator" "icon indicator" root $ lockCmd fontifyAlt
root = IORoot_ (lockCmd fontifyIcon) $ Only_ iconDependency root = IORoot_ (lockCmd fontifyIcon) $ Only_ iconDependency
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- bar feature constructors -- | bar feature constructors
xmobarDBus xmobarDBus :: SafeClient c => String -> XPQuery -> DBusDependency_ c
:: SafeClient c -> (Fontifier -> CmdSpec) -> Maybe c -> BarFeature
=> T.Text
-> XPQuery
-> DBusDependency_ c
-> (Fontifier -> CmdSpec)
-> Maybe (NamedConnection c)
-> BarFeature
xmobarDBus n q dep cmd cl = iconDBus_ n q root (Only_ dep) xmobarDBus n q dep cmd cl = iconDBus_ n q root (Only_ dep)
where where
root useIcon tree = DBusRoot_ (const $ cmd useIcon) tree cl root useIcon tree = DBusRoot_ (const $ cmd useIcon) tree cl
iconIO_ iconIO_ :: String -> XPQuery -> (Fontifier -> IOTree_ -> Root CmdSpec)
:: T.Text -> IOTree_ -> BarFeature
-> XPQuery
-> (Fontifier -> IOTree_ -> Root CmdSpec)
-> IOTree_
-> BarFeature
iconIO_ = iconSometimes' And_ Only_ iconIO_ = iconSometimes' And_ Only_
-- iconDBus iconDBus :: SafeClient c => String -> XPQuery
-- :: T.Text -> (Fontifier -> DBusTree c p -> Root CmdSpec) -> DBusTree c p -> BarFeature
-- -> XPQuery iconDBus = iconSometimes' And1 $ Only_ . DBusIO
-- -> (Fontifier -> DBusTree c p -> Root CmdSpec)
-- -> DBusTree c p
-- -> BarFeature
-- iconDBus = iconSometimes' And1 $ Only_ . DBusIO
iconDBus_ iconDBus_ :: SafeClient c => String -> XPQuery
:: T.Text -> (Fontifier -> DBusTree_ c -> Root CmdSpec) -> DBusTree_ c -> BarFeature
-> XPQuery
-> (Fontifier -> DBusTree_ c -> Root CmdSpec)
-> DBusTree_ c
-> BarFeature
iconDBus_ = iconSometimes' And_ $ Only_ . DBusIO iconDBus_ = iconSometimes' And_ $ Only_ . DBusIO
iconSometimes' iconSometimes' :: (t -> t_ -> t) -> (IODependency_ -> t_) -> String -> XPQuery
:: (t -> t_ -> t) -> (Fontifier -> t -> Root CmdSpec) -> t -> BarFeature
-> (IODependency_ -> t_) iconSometimes' c d n q r t = Sometimes n q
-> T.Text [ Subfeature icon "icon indicator"
-> XPQuery , Subfeature text "text indicator"
-> (Fontifier -> t -> Root CmdSpec) ]
-> t
-> BarFeature
iconSometimes' c d n q r t =
Sometimes
n
q
[ Subfeature icon "icon indicator"
, Subfeature text "text indicator"
]
where where
icon = r fontifyIcon $ c t $ d iconDependency icon = r fontifyIcon $ c t $ d iconDependency
text = r fontifyAlt t text = r fontifyAlt t
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- command specifications -- | command specifications
data BarRegions = BarRegions data BarRegions = BarRegions
{ brLeft :: [CmdSpec] { brLeft :: [CmdSpec]
, brCenter :: [CmdSpec] , brCenter :: [CmdSpec]
, brRight :: [CmdSpec] , brRight :: [CmdSpec]
} } deriving Show
deriving (Show)
data CmdSpec = CmdSpec data CmdSpec = CmdSpec
{ csAlias :: T.Text { csAlias :: String
, csRunnable :: Runnable , csRunnable :: Runnable
} } deriving Show
deriving (Show)
concatRegions :: BarRegions -> [CmdSpec] concatRegions :: BarRegions -> [CmdSpec]
concatRegions (BarRegions l c r) = l ++ c ++ r concatRegions (BarRegions l c r) = l ++ c ++ r
wirelessCmd :: T.Text -> CmdSpec wirelessCmd :: String -> CmdSpec
wirelessCmd iface = wirelessCmd iface = CmdSpec
CmdSpec { csAlias = iface ++ "wi"
{ csAlias = T.append iface "wi" , csRunnable = Run
, csRunnable = Run $ Wireless (T.unpack iface) args 5 $ Wireless iface
} [ "-t", "<qualityipat><essid>"
where , "--"
args = , "--quality-icon-pattern", "<icon=wifi_%%.xpm/>"
fmap ] 5
T.unpack }
[ "-t"
, "<qualityipat><essid>"
, "--"
, "--quality-icon-pattern"
, "<icon=wifi_%%.xpm/>"
]
ethernetCmd :: Fontifier -> CmdSpec ethernetCmd :: Fontifier -> String -> CmdSpec
ethernetCmd = connCmd "\xf0e8" "ETH" ("vlan" :| ["802-3-ethernet"]) ethernetCmd fontify iface = CmdSpec
{ csAlias = iface
vpnCmd :: Fontifier -> CmdSpec , csRunnable = Run
vpnCmd = connCmd "\xf023" "VPN" ("tun" :| []) $ Device (iface, fontify IconMedium "\xf0e8" "ETH", colors)
}
connCmd :: T.Text -> T.Text -> NE.NonEmpty T.Text -> Fontifier -> CmdSpec
connCmd icon abbr contypes fontify =
CmdSpec
{ csAlias = connAlias contypes
, csRunnable =
Run $
ActiveConnection (contypes, fontify IconMedium icon abbr, colors)
}
batteryCmd :: Fontifier -> CmdSpec batteryCmd :: Fontifier -> CmdSpec
batteryCmd fontify = batteryCmd fontify = CmdSpec
CmdSpec { csAlias = "battery"
{ csAlias = "battery" , csRunnable = Run
, csRunnable = Run $ Battery args 50 $ Battery
} [ "--template", "<acstatus><left>"
, "--Low", "10"
, "--High", "80"
, "--low", "red"
, "--normal", T.fgColor
, "--high", T.fgColor
, "--"
, "-P"
, "-o" , fontify' "\xf0e7" "BAT"
, "-O" , fontify' "\xf1e6" "AC"
, "-i" , fontify' "\xf1e6" "AC"
] 50
}
where where
fontify' = fontify IconSmall fontify' = fontify IconSmall
args =
fmap vpnCmd :: Fontifier -> CmdSpec
T.unpack vpnCmd fontify = CmdSpec
[ "--template" { csAlias = vpnAlias
, "<acstatus><left>" , csRunnable = Run $ VPN (fontify IconMedium "\xf023" "VPN", colors)
, "--Low" }
, "10"
, "--High"
, "80"
, "--low"
, "red"
, "--normal"
, XT.fgColor
, "--high"
, XT.fgColor
, "--"
, "-P"
, "-o"
, fontify' "\xf0e7" "BAT"
, "-O"
, fontify' "\xf1e6" "AC"
, "-i"
, fontify' "\xf1e6" "AC"
]
btCmd :: Fontifier -> CmdSpec btCmd :: Fontifier -> CmdSpec
btCmd fontify = btCmd fontify = CmdSpec
CmdSpec { csAlias = btAlias
{ csAlias = btAlias , csRunnable = Run
, csRunnable = $ Bluetooth (fontify' "\xf5b0" "+", fontify' "\xf5ae" "-") colors
Run $ }
Bluetooth (fontify' "\x0f00b1" "+", fontify' "\x0f00af" "-") colors
}
where where
fontify' i = fontify IconLarge i . T.append "BT" fontify' i = fontify IconLarge i . ("BT" ++)
alsaCmd :: Fontifier -> CmdSpec alsaCmd :: Fontifier -> CmdSpec
alsaCmd fontify = alsaCmd fontify = CmdSpec
CmdSpec { csAlias = "alsa:default:Master"
{ csAlias = "alsa:default:Master" , csRunnable = Run
, csRunnable = $ Alsa "default" "Master"
Run $ [ "-t", "<status><volume>%"
Alsa "default" "Master" $ , "--"
fmap , "-O", fontify' "\xf028" "+"
T.unpack , "-o", fontify' "\xf026" "-" ++ " "
[ "-t" , "-c", T.fgColor
, "<status><volume>%" , "-C", T.fgColor
, "--" ]
, "-O" }
, fontify' "\xf028" "+"
, "-o"
, T.append (fontify' "\xf026" "-") " "
, "-c"
, XT.fgColor
, "-C"
, XT.fgColor
]
}
where where
fontify' i = fontify IconSmall i . T.append "VOL" fontify' i = fontify IconSmall i . ("VOL" ++)
blCmd :: Fontifier -> CmdSpec blCmd :: Fontifier -> CmdSpec
blCmd fontify = blCmd fontify = CmdSpec
CmdSpec { csAlias = blAlias
{ csAlias = blAlias , csRunnable = Run $ IntelBacklight $ fontify IconSmall "\xf185" "BL: "
, csRunnable = Run $ IntelBacklight $ fontify IconSmall "\xf185" "BL: " }
}
ckCmd :: Fontifier -> CmdSpec ckCmd :: Fontifier -> CmdSpec
ckCmd fontify = ckCmd fontify = CmdSpec
CmdSpec { csAlias = ckAlias
{ csAlias = ckAlias , csRunnable = Run $ ClevoKeyboard $ fontify IconSmall "\xf40b" "KB: "
, csRunnable = Run $ ClevoKeyboard $ fontify IconSmall "\xf11c" "KB: " }
}
ssCmd :: Fontifier -> CmdSpec ssCmd :: Fontifier -> CmdSpec
ssCmd fontify = ssCmd fontify = CmdSpec
CmdSpec { csAlias = ssAlias
{ csAlias = ssAlias , csRunnable = Run $ Screensaver (fontify IconSmall "\xf254" "SS", colors)
, csRunnable = Run $ Screensaver (fontify IconSmall "\xf254" "SS", colors) }
}
lockCmd :: Fontifier -> CmdSpec lockCmd :: Fontifier -> CmdSpec
lockCmd fontify = lockCmd fontify = CmdSpec
CmdSpec { csAlias = "locks"
{ csAlias = "locks" , csRunnable = Run
, csRunnable = $ Locks
Run $ [ "-N", numIcon
Locks $ , "-n", disabledColor numIcon
fmap , "-C", capIcon
T.unpack , "-c", disabledColor capIcon
[ "-N" , "-s", ""
, numIcon , "-S", ""
, "-n" , "-d", " "
, disabledColor numIcon ]
, "-C" }
, capIcon
, "-c"
, disabledColor capIcon
, "-s"
, ""
, "-S"
, ""
, "-d"
, " "
]
}
where where
numIcon = fontify' "\x0f03a6" "N" numIcon = fontify' "\xf8a5" "N"
capIcon = fontify' "\x0f0bf1" "C" capIcon = fontify' "\xf657" "C"
fontify' = fontify IconXLarge fontify' = fontify IconXLarge
disabledColor = xmobarFGColor XT.backdropFgColor disabledColor = xmobarFGColor T.backdropFgColor
dateCmd :: CmdSpec dateCmd :: CmdSpec
dateCmd = dateCmd = CmdSpec
CmdSpec { csAlias = "date"
{ csAlias = "date" , csRunnable = Run $ Date "%Y-%m-%d %H:%M:%S " "date" 10
, csRunnable = Run $ Date "%Y-%m-%d %H:%M:%S " "date" 10 }
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- text font -- | low-level testing functions
vpnPresent :: IO (Maybe Msg)
vpnPresent =
go <$> tryIOError (readCreateProcessWithExitCode' (proc' "nmcli" args) "")
where
args = ["-c", "no", "-t", "-f", "TYPE", "c", "show"]
go (Right (ExitSuccess, out, _)) = if "vpn" `elem` lines out then Nothing
else Just $ Msg Error "vpn not found"
go (Right (ExitFailure c, _, err)) = Just $ Msg Error
$ "vpn search exited with code "
++ show c ++ ": " ++ err
go (Left e) = Just $ Msg Error $ show e
--------------------------------------------------------------------------------
-- | text font
-- --
-- ASSUME there is only one text font for this entire configuration. This -- ASSUME there is only one text font for this entire configuration. This
-- will correspond to the first font/offset parameters in the config record. -- will correspond to the first font/offset parameters in the config record.
getTextFont :: XIO T.Text getTextFont :: FIO String
getTextFont = do getTextFont = do
fb <- evalAlways textFont fb <- evalAlways textFont
return $ fb textFontData return $ fb textFontData
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- icon fonts -- | icon fonts
getIconFonts :: XIO ([T.Text], [Int]) getIconFonts :: FIO ([String], [Int])
getIconFonts = do getIconFonts = do
fb <- evalSometimes iconFont fb <- evalSometimes iconFont
return $ maybe ([], []) apply fb return $ maybe ([], []) apply fb
where where
apply fb = apply fb = unzip $ (\i -> (iconString fb i, iconOffset i + textFontOffset))
unzip $ <$> iconFonts
(\i -> (iconString fb i, iconOffset i + textFontOffset))
<$> iconFonts
data BarFont data BarFont = IconSmall
= IconSmall
| IconMedium | IconMedium
| IconLarge | IconLarge
| IconXLarge | IconXLarge
@ -546,17 +428,16 @@ data BarFont
iconFonts :: [BarFont] iconFonts :: [BarFont]
iconFonts = enumFrom minBound iconFonts = enumFrom minBound
iconString :: XT.FontBuilder -> BarFont -> T.Text iconString :: T.FontBuilder -> BarFont -> String
iconString fb i = fb $ iconFontData $ iconSize i iconString fb i = fb $ iconFontData $ iconSize i
iconDependency :: IODependency_ iconDependency :: IODependency_
iconDependency = IOSometimes_ iconFont iconDependency = IOSometimes_ iconFont
fontifyText :: BarFont -> T.Text -> T.Text fontifyText :: BarFont -> String -> String
fontifyText fnt txt = fontifyText fnt txt = concat ["<fn=", show $ 1 + fromEnum fnt, ">", txt, "</fn>"]
T.concat ["<fn=", T.pack $ show $ 1 + fromEnum fnt, ">", txt, "</fn>"]
type Fontifier = BarFont -> T.Text -> T.Text -> T.Text type Fontifier = BarFont -> String -> String -> String
fontifyAlt :: Fontifier fontifyAlt :: Fontifier
fontifyAlt _ _ alt = alt fontifyAlt _ _ alt = alt
@ -565,13 +446,13 @@ fontifyIcon :: Fontifier
fontifyIcon f i _ = fontifyText f i fontifyIcon f i _ = fontifyText f i
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- various formatting things -- | various formatting things
colors :: Colors colors :: Colors
colors = Colors {colorsOn = XT.fgColor, colorsOff = XT.backdropFgColor} colors = Colors { colorsOn = T.fgColor, colorsOff = T.backdropFgColor }
sep :: T.Text sep :: String
sep = xmobarFGColor XT.backdropFgColor " : " sep = xmobarFGColor T.backdropFgColor " : "
lSep :: Char lSep :: Char
lSep = '}' lSep = '}'
@ -579,15 +460,14 @@ lSep = '}'
rSep :: Char rSep :: Char
rSep = '{' rSep = '{'
pSep :: T.Text pSep :: String
pSep = "%" pSep = "%"
fmtSpecs :: [CmdSpec] -> T.Text fmtSpecs :: [CmdSpec] -> String
fmtSpecs = T.intercalate sep . fmap go fmtSpecs = intercalate sep . fmap go
where where
go CmdSpec {csAlias = a} = T.concat [pSep, a, pSep] go CmdSpec { csAlias = a } = wrap pSep pSep a
fmtRegions :: BarRegions -> T.Text fmtRegions :: BarRegions -> String
fmtRegions BarRegions {brLeft = l, brCenter = c, brRight = r} = fmtRegions BarRegions { brLeft = l, brCenter = c, brRight = r } =
T.concat fmtSpecs l ++ [lSep] ++ fmtSpecs c ++ [rSep] ++ fmtSpecs r
[fmtSpecs l, T.singleton lSep, fmtSpecs c, T.singleton rSep, fmtSpecs r]

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
indentation: 2
function-arrows: leading
comma-style: leading
import-export-style: leading
indent-wheres: true
record-brace-space: true
newlines-between-decls: 1
haddock-style: single-line
haddock-style-module:
let-style: inline
in-style: right-align
respectful: false
fixities: []
unicode: never

64
install_deps Executable file
View File

@ -0,0 +1,64 @@
#!/bin/bash
## Build xmonad and install packages to make it run at full capacity
prebuild () {
# TODO this can be integrated into stack with nix
# for x11
make_pkgs=(libx11 libxrandr libxss)
# for alsa
make_pkgs=(alsa-lib)
# for iwlib
make_pkgs=(wireless_tools)
# for x11-xft
make_pkgs+=(libxft)
# for xmobar
make_pkgs+=(libxpm)
sudo pacman --noconfirm -S "${make_pkgs[@]}"
}
build () {
stack install
}
query='.[].success |
objects |
.root.tree |
..|.left?.data, ..|.right?.data, .data? |
select(. != null) |
.fulfillment |
select(. != null) |
add | select(. != null)'
jq_type () {
echo "$1" | jq --raw-output "select(.type==\"$2\") | .name" | sort | uniq
}
postbuild () {
# these are extra packages that pertain to processes outside xmonad but are
# still required/desired to make it work correctly
xmonad_pkgs=(xinit autorandr picom)
raw=$(xmonad --deps | jq "$query")
mapfile -t official < <(jq_type "$raw" "Official")
mapfile -t local < <(jq_type "$raw" "AUR")
if ! pacman -Si "${official[@]}" > /dev/null; then
echo "At least one official package doesn't exist."
exit 1
fi
if ! yay -Si "${local[@]}"; then
echo "At least one local package doesn't exist."
exit 1
fi
sudo pacman --noconfirm -S "${xmonad_pkgs[@]}" "${official[@]}"
yay --needed --noconfirm --norebuild --removemake -S "${local[@]}"
}
prebuild
build
postbuild

View File

@ -1,25 +1,15 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Common internal DBus functions -- | Common internal DBus functions
module Data.Internal.DBus module Data.Internal.DBus
( SafeClient (..) ( SafeClient(..)
, SysClient (..) , SysClient(..)
, SesClient (..) , SesClient(..)
, NamedConnection (..)
, NamedSesConnection
, NamedSysConnection
, DBusEnv (..)
, DIO
, HasClient (..)
, releaseBusName
, withDIO
, addMatchCallback , addMatchCallback
, addMatchCallbackSignal
, matchSignalFull
, matchProperty , matchProperty
, matchPropertyFull , matchPropertyFull
, matchPropertyChanged , matchPropertyChanged
, SignalMatch (..) , SignalMatch(..)
, SignalCallback , SignalCallback
, MethodBody , MethodBody
, withSignalMatch , withSignalMatch
@ -35,258 +25,97 @@ module Data.Internal.DBus
, addInterfaceRemovedListener , addInterfaceRemovedListener
, fromSingletonVariant , fromSingletonVariant
, bodyToMaybe , bodyToMaybe
, exportPair ) where
, displayBusName
, displayObjectPath
, displayMemberName
, displayInterfaceName
, displayWrapQuote
, busNameT
, interfaceNameT
, memberNameT
, objectPathT
)
where
import DBus import Control.Exception
import DBus.Client import Control.Monad
import qualified Data.ByteString.Char8 as BC
import RIO import Data.Bifunctor
import RIO.List import qualified Data.Map.Strict as M
import qualified RIO.Map as M import Data.Maybe
import qualified RIO.Text as T
import DBus
import DBus.Client
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Type-safe client -- | Type-safe client
data NamedConnection c = NamedConnection
{ ncClient :: !Client
, ncHumanName :: !(Maybe BusName)
--, ncUniqueName :: !BusName
, ncType :: !c
}
type NamedSesConnection = NamedConnection SesClient
type NamedSysConnection = NamedConnection SysClient
class SafeClient c where class SafeClient c where
getDBusClient toClient :: c -> Client
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> Maybe BusName
-> m (Maybe (NamedConnection c))
disconnectDBusClient getDBusClient :: IO (Maybe c)
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> NamedConnection c
-> m ()
disconnectDBusClient c = do
releaseBusName c
liftIO $ disconnect $ ncClient c
withDBusClient disconnectDBusClient :: c -> IO ()
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) disconnectDBusClient = disconnect . toClient
=> Maybe BusName
-> (NamedConnection c -> m a)
-> m (Maybe a)
withDBusClient n f =
bracket (getDBusClient n) (mapM (liftIO . disconnect . ncClient)) $ mapM f
withDBusClient_ withDBusClient :: (c -> IO a) -> IO (Maybe a)
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) withDBusClient f = do
=> Maybe BusName client <- getDBusClient
-> (NamedConnection c -> m ()) forM client $ \c -> do
-> m () r <- f c
withDBusClient_ n = void . withDBusClient n disconnect (toClient c)
return r
fromDBusClient withDBusClient_ :: (c -> IO ()) -> IO ()
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) withDBusClient_ = void . withDBusClient
=> Maybe BusName
-> (NamedConnection c -> a)
-> m (Maybe a)
fromDBusClient n f = withDBusClient n (return . f)
data SysClient = SysClient fromDBusClient :: (c -> a) -> IO (Maybe a)
fromDBusClient f = withDBusClient (return . f)
newtype SysClient = SysClient Client
instance SafeClient SysClient where instance SafeClient SysClient where
getDBusClient = connectToDBusWithName True SysClient toClient (SysClient cl) = cl
data SesClient = SesClient getDBusClient = fmap SysClient <$> getDBusClient' True
newtype SesClient = SesClient Client
instance SafeClient SesClient where instance SafeClient SesClient where
-- TODO wet toClient (SesClient cl) = cl
getDBusClient = connectToDBusWithName False SesClient
connectToDBusWithName getDBusClient = fmap SesClient <$> getDBusClient' False
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> Bool
-> c
-> Maybe BusName
-> m (Maybe (NamedConnection c))
connectToDBusWithName sys t n = do
clRes <- getDBusClient' sys
case clRes of
Nothing -> do
logError "could not get client"
return Nothing
Just cl -> do
--helloRes <- liftIO $ callHello cl
--case helloRes of
-- Nothing -> do
-- logError "count not get unique name"
-- return Nothing
-- Just unique -> do
n' <- maybe (return Nothing) (`requestBusName` cl) n
return $
Just $
NamedConnection
{ ncClient = cl
, ncHumanName = n'
-- , ncUniqueName = unique
, ncType = t
}
releaseBusName getDBusClient' :: Bool -> IO (Maybe Client)
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> NamedConnection c
-> m ()
releaseBusName NamedConnection {ncClient, ncHumanName} = do
-- TODO this might error?
case ncHumanName of
Just n -> do
liftIO $ void $ releaseName ncClient n
logInfo $ "released bus name: " <> displayBusName n
Nothing -> return ()
requestBusName
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> BusName
-> Client
-> m (Maybe BusName)
requestBusName n cl = do
res <- try $ liftIO $ requestName cl n []
case res of
Left e -> do
logError $ displayBytesUtf8 $ BC.pack $ clientErrorMessage e
return Nothing
Right r -> do
let msg
| r == NamePrimaryOwner = "registering name"
| r == NameAlreadyOwner = "this process already owns name"
| r == NameInQueue
|| r == NameExists =
"another process owns name"
-- this should never happen
| otherwise = "unknown error when requesting name"
logInfo $ msg <> ": " <> displayBusName n
case r of
NamePrimaryOwner -> return $ Just n
_ -> return Nothing
getDBusClient'
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> Bool
-> m (Maybe Client)
getDBusClient' sys = do getDBusClient' sys = do
res <- try $ liftIO $ if sys then connectSystem else connectSession res <- try $ if sys then connectSystem else connectSession
case res of case res of
Left e -> do Left e -> putStrLn (clientErrorMessage e) >> return Nothing
logInfo $ displayBytesUtf8 $ BC.pack $ clientErrorMessage e
return Nothing
Right c -> return $ Just c Right c -> return $ Just c
--callHello :: Client -> IO (Maybe BusName)
--callHello cl = do
-- reply <- call_ cl $ methodCallBus dbusName dbusPath dbusInterface "Hello"
-- case methodReturnBody reply of
-- [name] | Just nameStr <- fromVariant name -> do
-- busName <- parseBusName nameStr
-- return $ Just busName
-- _ -> return Nothing
--
data DBusEnv env c = DBusEnv {dClient :: !(NamedConnection c), dEnv :: !env}
type DIO env c = RIO (DBusEnv env c)
instance HasClient (DBusEnv SimpleApp) where
clientL = lens dClient (\x y -> x {dClient = y})
instance HasLogFunc (DBusEnv SimpleApp c) where
logFuncL = lens dEnv (\x y -> x {dEnv = y}) . logFuncL
withDIO
:: (MonadUnliftIO m, MonadReader env m)
=> NamedConnection c
-> DIO env c a
-> m a
withDIO cl x = do
env <- ask
runRIO (DBusEnv cl env) x
class HasClient env where
clientL :: SafeClient c => Lens' (env c) (NamedConnection c)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Methods -- | Methods
type MethodBody = Either T.Text [Variant] type MethodBody = Either String [Variant]
callMethod' callMethod' :: SafeClient c => c -> MethodCall -> IO MethodBody
:: (SafeClient c, MonadUnliftIO m, MonadReader (env c) m, HasClient env) callMethod' cl = fmap (bimap methodErrorMessage methodReturnBody)
=> MethodCall . call (toClient cl)
-> m MethodBody
callMethod' mc = do
cl <- ncClient <$> view clientL
liftIO $ bimap (T.pack . methodErrorMessage) methodReturnBody <$> call cl mc
callMethod callMethod :: SafeClient c => c -> BusName -> ObjectPath -> InterfaceName
:: (SafeClient c, MonadUnliftIO m, MonadReader (env c) m, HasClient env) -> MemberName -> IO MethodBody
=> BusName callMethod client bus path iface = callMethod' client . methodCallBus bus path iface
-> ObjectPath
-> InterfaceName
-> MemberName
-> m MethodBody
callMethod bus path iface = callMethod' . methodCallBus bus path iface
methodCallBus :: BusName -> ObjectPath -> InterfaceName -> MemberName -> MethodCall methodCallBus :: BusName -> ObjectPath -> InterfaceName -> MemberName -> MethodCall
methodCallBus b p i m = methodCallBus b p i m = (methodCall p i m)
(methodCall p i m) { methodCallDestination = Just b }
{ methodCallDestination = Just b
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Bus names -- | Bus names
dbusInterface :: InterfaceName dbusInterface :: InterfaceName
dbusInterface = interfaceName_ "org.freedesktop.DBus" dbusInterface = interfaceName_ "org.freedesktop.DBus"
callGetNameOwner callGetNameOwner :: SafeClient c => c -> BusName -> IO (Maybe BusName)
:: ( SafeClient c callGetNameOwner cl name = bodyToMaybe <$> callMethod' cl mc
, MonadUnliftIO m
, MonadReader (env c) m
, HasClient env
, HasLogFunc (env c)
)
=> BusName
-> m (Maybe BusName)
callGetNameOwner name = do
res <- callMethod' mc
case res of
Left err -> do
logError $ Utf8Builder $ encodeUtf8Builder err
return Nothing
Right body -> return $ fromSingletonVariant body
where where
mc = mc = (methodCallBus dbusName dbusPath dbusInterface mem)
(methodCallBus dbusName dbusPath dbusInterface mem) { methodCallBody = [toVariant name] }
{ methodCallBody = [toVariant name]
}
mem = memberName_ "GetNameOwner" mem = memberName_ "GetNameOwner"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Variant parsing -- | Variant parsing
-- TODO log failures here?
fromSingletonVariant :: IsVariant a => [Variant] -> Maybe a fromSingletonVariant :: IsVariant a => [Variant] -> Maybe a
fromSingletonVariant = fromVariant <=< listToMaybe fromSingletonVariant = fromVariant <=< listToMaybe
@ -294,81 +123,30 @@ bodyToMaybe :: IsVariant a => MethodBody -> Maybe a
bodyToMaybe = either (const Nothing) fromSingletonVariant bodyToMaybe = either (const Nothing) fromSingletonVariant
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Signals -- | Signals
type SignalCallback m = [Variant] -> m () type SignalCallback = [Variant] -> IO ()
addMatchCallbackSignal addMatchCallback :: SafeClient c => MatchRule -> SignalCallback -> c
:: ( MonadReader (env c) m -> IO SignalHandler
, MonadUnliftIO m addMatchCallback rule cb cl = addMatch (toClient cl) rule $ cb . signalBody
, SafeClient c
, HasClient env
)
=> MatchRule
-> (Signal -> m ())
-> m SignalHandler
addMatchCallbackSignal rule cb = do
cl <- ncClient <$> view clientL
withRunInIO $ \run -> addMatch cl rule $ run . cb
addMatchCallback matchSignal :: Maybe BusName -> Maybe ObjectPath -> Maybe InterfaceName
:: ( MonadReader (env c) m -> Maybe MemberName -> MatchRule
, MonadUnliftIO m matchSignal b p i m = matchAny
, SafeClient c { matchPath = p
, HasClient env , matchSender = b
) , matchInterface = i
=> MatchRule , matchMember = m
-> SignalCallback m }
-> m SignalHandler
addMatchCallback rule cb = addMatchCallbackSignal rule (cb . signalBody)
matchSignal matchSignalFull :: SafeClient c => c -> BusName -> Maybe ObjectPath
:: Maybe BusName -> Maybe InterfaceName -> Maybe MemberName -> IO (Maybe MatchRule)
-> Maybe ObjectPath matchSignalFull client b p i m =
-> Maybe InterfaceName fmap (\o -> matchSignal (Just o) p i m) <$> callGetNameOwner client b
-> Maybe MemberName
-> MatchRule
matchSignal b p i m =
matchAny
{ matchPath = p
, matchSender = b
, matchInterface = i
, matchMember = m
}
matchSignalFull
:: ( MonadReader (env c) m
, HasLogFunc (env c)
, MonadUnliftIO m
, SafeClient c
, HasClient env
)
=> BusName
-> Maybe ObjectPath
-> Maybe InterfaceName
-> Maybe MemberName
-> m (Maybe MatchRule)
matchSignalFull b p i m = do
res <- callGetNameOwner b
case res of
Just o -> return $ Just $ matchSignal (Just o) p i m
Nothing -> do
logError msg
return Nothing
where
bus_ = displayWrapQuote $ displayBusName b
iface_ = displayWrapQuote . displayInterfaceName <$> i
path_ = displayWrapQuote . displayObjectPath <$> p
mem_ = displayWrapQuote . displayMemberName <$> m
match =
intersperse ", " $
mapMaybe (\(k, v) -> fmap ((k <> "=") <>) v) $
zip ["interface", "path", "member"] [iface_, path_, mem_]
stem = "could not get match rule for bus " <> bus_
msg = if null match then stem else stem <> " where " <> mconcat match
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Properties -- | Properties
propertyInterface :: InterfaceName propertyInterface :: InterfaceName
propertyInterface = interfaceName_ "org.freedesktop.DBus.Properties" propertyInterface = interfaceName_ "org.freedesktop.DBus.Properties"
@ -376,74 +154,45 @@ propertyInterface = interfaceName_ "org.freedesktop.DBus.Properties"
propertySignal :: MemberName propertySignal :: MemberName
propertySignal = memberName_ "PropertiesChanged" propertySignal = memberName_ "PropertiesChanged"
callPropertyGet callPropertyGet :: SafeClient c => BusName -> ObjectPath -> InterfaceName
:: ( HasClient env -> MemberName -> c -> IO [Variant]
, MonadReader (env c) m callPropertyGet bus path iface property cl = fmap (either (const []) (:[]))
, HasLogFunc (env c) $ getProperty (toClient cl) $ methodCallBus bus path iface property
, MonadUnliftIO m
, SafeClient c
)
=> BusName
-> ObjectPath
-> InterfaceName
-> MemberName
-> m [Variant]
callPropertyGet bus path iface property = do
cl <- ncClient <$> view clientL
res <- liftIO $ getProperty cl $ methodCallBus bus path iface property
case res of
Left err -> do
logError $ displayBytesUtf8 $ BC.pack $ methodErrorMessage err
return []
Right v -> return [v]
matchProperty :: Maybe BusName -> Maybe ObjectPath -> MatchRule matchProperty :: Maybe BusName -> Maybe ObjectPath -> MatchRule
matchProperty b p = matchProperty b p =
matchSignal b p (Just propertyInterface) (Just propertySignal) matchSignal b p (Just propertyInterface) (Just propertySignal)
matchPropertyFull matchPropertyFull :: SafeClient c => c -> BusName -> Maybe ObjectPath
:: ( MonadReader (env c) m -> IO (Maybe MatchRule)
, HasLogFunc (env c) matchPropertyFull cl b p =
, MonadUnliftIO m matchSignalFull cl b p (Just propertyInterface) (Just propertySignal)
, SafeClient c
, HasClient env
)
=> BusName
-> Maybe ObjectPath
-> m (Maybe MatchRule)
matchPropertyFull b p =
matchSignalFull b p (Just propertyInterface) (Just propertySignal)
data SignalMatch a = Match a | NoMatch | Failure deriving (Eq, Show) data SignalMatch a = Match a | NoMatch | Failure deriving (Eq, Show)
withSignalMatch :: MonadUnliftIO m => (Maybe a -> m ()) -> SignalMatch a -> m () withSignalMatch :: (Maybe a -> IO ()) -> SignalMatch a -> IO ()
withSignalMatch f (Match x) = f (Just x) withSignalMatch f (Match x) = f (Just x)
withSignalMatch f Failure = f Nothing withSignalMatch f Failure = f Nothing
withSignalMatch _ NoMatch = return () withSignalMatch _ NoMatch = return ()
matchPropertyChanged matchPropertyChanged :: IsVariant a => InterfaceName -> String -> [Variant]
:: IsVariant a
=> InterfaceName
-> MemberName
-> [Variant]
-> SignalMatch a -> SignalMatch a
matchPropertyChanged iface property [sigIface, sigValues, _] = matchPropertyChanged iface property [i, body, _] =
let i = fromVariant sigIface :: Maybe T.Text let i' = (fromVariant i :: Maybe String)
v = fromVariant sigValues :: Maybe (M.Map T.Text Variant) b = toMap body in
in case (i, v) of case (i', b) of
(Just i', Just v') -> (Just i'', Just b') -> if i'' == formatInterfaceName iface then
if i' == interfaceNameT iface maybe NoMatch Match $ fromVariant =<< M.lookup property b'
then else NoMatch
maybe NoMatch Match $ _ -> Failure
fromVariant =<< M.lookup (memberNameT property) v' where
else NoMatch toMap v = fromVariant v :: Maybe (M.Map String Variant)
_ -> Failure
matchPropertyChanged _ _ _ = Failure matchPropertyChanged _ _ _ = Failure
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Object Manager -- | Object Manager
type ObjectTree = M.Map ObjectPath (M.Map InterfaceName (M.Map T.Text Variant)) type ObjectTree = M.Map ObjectPath (M.Map String (M.Map String Variant))
omInterface :: InterfaceName omInterface :: InterfaceName
omInterface = interfaceName_ "org.freedesktop.DBus.ObjectManager" omInterface = interfaceName_ "org.freedesktop.DBus.ObjectManager"
@ -457,133 +206,24 @@ omInterfacesAdded = memberName_ "InterfacesAdded"
omInterfacesRemoved :: MemberName omInterfacesRemoved :: MemberName
omInterfacesRemoved = memberName_ "InterfacesRemoved" omInterfacesRemoved = memberName_ "InterfacesRemoved"
callGetManagedObjects callGetManagedObjects :: SafeClient c => c -> BusName -> ObjectPath
:: ( MonadReader (env c) m -> IO ObjectTree
, HasLogFunc (env c) callGetManagedObjects cl bus path =
, MonadUnliftIO m either (const M.empty) (fromMaybe M.empty . fromSingletonVariant)
, SafeClient c <$> callMethod cl bus path omInterface getManagedObjects
, HasClient env
)
=> BusName
-> ObjectPath
-> m ObjectTree
callGetManagedObjects bus path = do
res <- callMethod bus path omInterface getManagedObjects
case res of
Left err -> do
logError $ Utf8Builder $ encodeUtf8Builder err
return M.empty
Right v ->
return $
fmap (M.mapKeys interfaceName_) $
fromMaybe M.empty $
fromSingletonVariant v
addInterfaceChangedListener addInterfaceChangedListener :: SafeClient c => BusName -> MemberName
:: ( MonadReader (env c) m -> ObjectPath -> SignalCallback -> c -> IO (Maybe SignalHandler)
, HasLogFunc (env c) addInterfaceChangedListener bus prop path sc cl = do
, MonadUnliftIO m rule <- matchSignalFull cl bus (Just path) (Just omInterface) (Just prop)
, SafeClient c forM rule $ \r -> addMatchCallback r sc cl
, HasClient env
)
=> BusName
-> MemberName
-> ObjectPath
-> SignalCallback m
-> m (Maybe SignalHandler)
addInterfaceChangedListener bus prop path sc = do
res <- matchSignalFull bus (Just path) (Just omInterface) (Just prop)
case res of
Nothing -> do
logError $
"could not add listener for property"
<> prop_
<> " at path "
<> path_
<> " on bus "
<> bus_
return Nothing
Just rule -> Just <$> addMatchCallback rule sc
where
bus_ = "'" <> displayBusName bus <> "'"
path_ = "'" <> displayObjectPath path <> "'"
prop_ = "'" <> displayMemberName prop <> "'"
addInterfaceAddedListener addInterfaceAddedListener :: SafeClient c => BusName -> ObjectPath
:: ( MonadReader (env c) m -> SignalCallback -> c -> IO (Maybe SignalHandler)
, HasLogFunc (env c)
, MonadUnliftIO m
, SafeClient c
, HasClient env
)
=> BusName
-> ObjectPath
-> SignalCallback m
-> m (Maybe SignalHandler)
addInterfaceAddedListener bus = addInterfaceAddedListener bus =
addInterfaceChangedListener bus omInterfacesAdded addInterfaceChangedListener bus omInterfacesAdded
addInterfaceRemovedListener addInterfaceRemovedListener :: SafeClient c => BusName -> ObjectPath
:: ( MonadReader (env c) m -> SignalCallback -> c -> IO (Maybe SignalHandler)
, HasLogFunc (env c)
, MonadUnliftIO m
, SafeClient c
, HasClient env
)
=> BusName
-> ObjectPath
-> SignalCallback m
-> m (Maybe SignalHandler)
addInterfaceRemovedListener bus = addInterfaceRemovedListener bus =
addInterfaceChangedListener bus omInterfacesRemoved addInterfaceChangedListener bus omInterfacesRemoved
--------------------------------------------------------------------------------
-- Interface export/unexport
exportPair
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> ObjectPath
-> (Client -> m Interface)
-> NamedConnection c
-> (m (), m ())
exportPair path toIface cl = (up, down)
where
cl_ = ncClient cl
up = do
logInfo $ "adding interface: " <> path_
i <- toIface cl_
liftIO $ export cl_ path i
down = do
logInfo $ "removing interface: " <> path_
liftIO $ unexport cl_ path
path_ = displayObjectPath path
--------------------------------------------------------------------------------
-- logging helpers
busNameT :: BusName -> T.Text
busNameT = T.pack . formatBusName
objectPathT :: ObjectPath -> T.Text
objectPathT = T.pack . formatObjectPath
interfaceNameT :: InterfaceName -> T.Text
interfaceNameT = T.pack . formatInterfaceName
memberNameT :: MemberName -> T.Text
memberNameT = T.pack . formatMemberName
displayBusName :: BusName -> Utf8Builder
displayBusName = displayBytesUtf8 . BC.pack . formatBusName
displayObjectPath :: ObjectPath -> Utf8Builder
displayObjectPath = displayBytesUtf8 . BC.pack . formatObjectPath
displayMemberName :: MemberName -> Utf8Builder
displayMemberName = displayBytesUtf8 . BC.pack . formatMemberName
displayInterfaceName :: InterfaceName -> Utf8Builder
displayInterfaceName = displayBytesUtf8 . BC.pack . formatInterfaceName
displayWrapQuote :: Utf8Builder -> Utf8Builder
displayWrapQuote x = "'" <> x <> "'"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Dmenu (Rofi) Commands -- | Dmenu (Rofi) Commands
module XMonad.Internal.Command.DMenu module XMonad.Internal.Command.DMenu
( runCmdMenu ( runCmdMenu
@ -13,58 +13,60 @@ module XMonad.Internal.Command.DMenu
, runBTMenu , runBTMenu
, runShowKeys , runShowKeys
, runAutorandrMenu , runAutorandrMenu
) ) where
where
import DBus import Control.Monad.Reader
import qualified Data.ByteString.Char8 as BC
import Data.Internal.DBus import Data.Internal.DBus
import Data.Internal.XIO import Data.Internal.Dependency
import Graphics.X11.Types
import RIO import DBus
import qualified RIO.ByteString as B
import RIO.Directory import Graphics.X11.Types
( XdgDirectory (..)
, getXdgDirectory import System.Directory
) ( XdgDirectory (..)
import qualified RIO.Text as T , getXdgDirectory
-- import System.IO )
import XMonad.Core hiding (spawn) import System.IO
import XMonad.Internal.Command.Desktop
import XMonad.Internal.DBus.Common import XMonad.Core hiding (spawn)
import XMonad.Internal.Notify import XMonad.Internal.Command.Desktop
import XMonad.Internal.Shell import XMonad.Internal.DBus.Common
import XMonad.Util.NamedActions import XMonad.Internal.Notify
import XMonad.Internal.Process
import XMonad.Internal.Shell
import XMonad.Util.NamedActions
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DMenu executables -- | DMenu executables
myDmenuCmd :: FilePath myDmenuCmd :: String
myDmenuCmd = "rofi" myDmenuCmd = "rofi"
myDmenuDevices :: FilePath myDmenuDevices :: String
myDmenuDevices = "rofi-dev" myDmenuDevices = "rofi-dev"
myDmenuPasswords :: FilePath myDmenuPasswords :: String
myDmenuPasswords = "rofi-bw" myDmenuPasswords = "rofi-bw"
myDmenuBluetooth :: FilePath myDmenuBluetooth :: String
myDmenuBluetooth = "rofi-bt" myDmenuBluetooth = "rofi-bt"
myDmenuVPN :: FilePath myDmenuVPN :: String
myDmenuVPN = "rofi-evpn" myDmenuVPN = "rofi-evpn"
myDmenuMonitors :: FilePath myDmenuMonitors :: String
myDmenuMonitors = "rofi-autorandr" myDmenuMonitors = "rofi-autorandr"
myDmenuNetworks :: FilePath myDmenuNetworks :: String
myDmenuNetworks = "networkmanager_dmenu" myDmenuNetworks = "networkmanager_dmenu"
myClipboardManager :: FilePath myClipboardManager :: String
myClipboardManager = "greenclip" myClipboardManager = "greenclip"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Packages -- | Packages
dmenuPkgs :: [Fulfillment] dmenuPkgs :: [Fulfillment]
dmenuPkgs = [Package Official "rofi"] dmenuPkgs = [Package Official "rofi"]
@ -73,19 +75,19 @@ clipboardPkgs :: [Fulfillment]
clipboardPkgs = [Package AUR "rofi-greenclip"] clipboardPkgs = [Package AUR "rofi-greenclip"]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Other internal functions -- | Other internal functions
spawnDmenuCmd :: MonadUnliftIO m => T.Text -> [T.Text] -> Sometimes (m ()) spawnDmenuCmd :: String -> [String] -> SometimesX
spawnDmenuCmd n = spawnDmenuCmd n =
sometimesExeArgs n "rofi preset" dmenuPkgs True myDmenuCmd sometimesExeArgs n "rofi preset" dmenuPkgs True myDmenuCmd
themeArgs :: T.Text -> [T.Text] themeArgs :: String -> [String]
themeArgs hexColor = themeArgs hexColor =
[ "-theme-str" [ "-theme-str"
, T.concat ["'#element.selected.normal { background-color: ", hexColor, "; }'"] , "'#element.selected.normal { background-color: " ++ hexColor ++ "; }'"
] ]
myDmenuMatchingArgs :: [T.Text] myDmenuMatchingArgs :: [String]
myDmenuMatchingArgs = ["-i"] -- case insensitivity myDmenuMatchingArgs = ["-i"] -- case insensitivity
dmenuTree :: IOTree_ -> IOTree_ dmenuTree :: IOTree_ -> IOTree_
@ -95,153 +97,109 @@ dmenuDep :: IODependency_
dmenuDep = sysExe dmenuPkgs myDmenuCmd dmenuDep = sysExe dmenuPkgs myDmenuCmd
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Exported Commands -- | Exported Commands
-- TODO test that veracrypt and friends are installed -- TODO test that veracrypt and friends are installed
runDevMenu :: MonadUnliftIO m => Sometimes (m ()) runDevMenu :: SometimesX
runDevMenu = sometimesIO_ "device manager" "rofi devices" t x runDevMenu = sometimesIO_ "device manager" "rofi devices" t x
where where
t = dmenuTree $ Only_ (localExe [] myDmenuDevices) t = dmenuTree $ Only_ (localExe [] myDmenuDevices)
x = do x = do
c <- io $ getXdgDirectory XdgConfig "rofi/devices.dhall" c <- io $ getXdgDirectory XdgConfig "rofi/devices.yml"
spawnCmd myDmenuDevices $ spawnCmd myDmenuDevices
["-c", T.pack c] $ ["-c", c]
++ "--" ++ "--" : themeArgs "#999933"
: themeArgs "#999933" ++ myDmenuMatchingArgs
++ myDmenuMatchingArgs
-- TODO test that bluetooth interface exists -- TODO test that bluetooth interface exists
runBTMenu :: MonadUnliftIO m => Sometimes (m ()) runBTMenu :: SometimesX
runBTMenu = runBTMenu = Sometimes "bluetooth selector" xpfBluetooth
Sometimes [Subfeature (IORoot_ cmd tree) "rofi bluetooth"]
"bluetooth selector"
xpfBluetooth
[Subfeature (IORoot_ cmd tree) "rofi bluetooth"]
where where
cmd = spawnCmd myDmenuBluetooth $ "-c" : themeArgs "#0044bb" cmd = spawnCmd myDmenuBluetooth $ "-c":themeArgs "#0044bb"
tree = dmenuTree $ Only_ $ sysExe [] myDmenuBluetooth tree = dmenuTree $ Only_ $ sysExe [] myDmenuBluetooth
runVPNMenu :: MonadUnliftIO m => Sometimes (m ()) runVPNMenu :: SometimesX
runVPNMenu = runVPNMenu = Sometimes "VPN selector" xpfVPN
Sometimes [Subfeature (IORoot_ cmd tree) "rofi VPN"]
"VPN selector"
xpfVPN
[Subfeature (IORoot_ cmd tree) "rofi VPN"]
where where
cmd = cmd = spawnCmd myDmenuVPN
spawnCmd myDmenuVPN $ $ ["-c"] ++ themeArgs "#007766" ++ myDmenuMatchingArgs
["-c"] ++ themeArgs "#007766" ++ myDmenuMatchingArgs tree = dmenuTree $ toAnd_ (localExe [] myDmenuVPN)
tree = $ socketExists "expressVPN" []
dmenuTree $ $ return "/var/lib/expressvpn/expressvpnd.socket"
toAnd_ (localExe [] myDmenuVPN) $
socketExists "expressVPN" [] $
return "/var/lib/expressvpn/expressvpnd.socket"
runCmdMenu :: MonadUnliftIO m => Sometimes (m ()) runCmdMenu :: SometimesX
runCmdMenu = spawnDmenuCmd "command menu" ["-show", "run"] runCmdMenu = spawnDmenuCmd "command menu" ["-show", "run"]
runAppMenu :: MonadUnliftIO m => Sometimes (m ()) runAppMenu :: SometimesX
runAppMenu = spawnDmenuCmd "app launcher" ["-show", "drun"] runAppMenu = spawnDmenuCmd "app launcher" ["-show", "drun"]
runWinMenu :: MonadUnliftIO m => Sometimes (m ()) runWinMenu :: SometimesX
runWinMenu = spawnDmenuCmd "window switcher" ["-show", "window"] runWinMenu = spawnDmenuCmd "window switcher" ["-show", "window"]
runNetMenu :: MonadUnliftIO m => Maybe NamedSysConnection -> Sometimes (m ()) runNetMenu :: Maybe SysClient -> SometimesX
runNetMenu cl = runNetMenu cl =
Sometimes sometimesDBus cl "network control menu" "rofi NetworkManager" tree cmd
"network control menu"
enabled
[Subfeature root "network control menu"]
where where
enabled f = xpfEthernet f || xpfWireless f || xpfVPN f
root = DBusRoot_ cmd tree cl
cmd _ = spawnCmd myDmenuNetworks $ themeArgs "#ff3333" cmd _ = spawnCmd myDmenuNetworks $ themeArgs "#ff3333"
tree = tree = And_ (Only_ $ Bus networkManagerPkgs networkManagerBus)
And_ (Only_ $ Bus networkManagerPkgs networkManagerBus) $ $ toAnd_ (DBusIO dmenuDep) $ DBusIO
toAnd_ (DBusIO dmenuDep) $ $ sysExe [Package AUR "networkmanager-dmenu-git"] myDmenuNetworks
DBusIO $
sysExe [Package AUR "networkmanager-dmenu-git"] myDmenuNetworks
runAutorandrMenu :: MonadUnliftIO m => Sometimes (m ()) runAutorandrMenu :: SometimesX
runAutorandrMenu = sometimesIO_ "autorandr menu" "rofi autorandr" tree cmd runAutorandrMenu = sometimesIO_ "autorandr menu" "rofi autorandr" tree cmd
where where
cmd = spawnCmd myDmenuMonitors $ themeArgs "#ff0066" cmd = spawnCmd myDmenuMonitors $ themeArgs "#ff0066"
tree = dmenuTree $ Only_ $ localExe [] myDmenuMonitors tree = dmenuTree $ Only_ $ localExe [] myDmenuMonitors
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Password manager -- | Password manager
runBwMenu :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runBwMenu :: Maybe SesClient -> SometimesX
runBwMenu cl = sometimesDBus cl "password manager" "rofi bitwarden" tree cmd runBwMenu cl = sometimesDBus cl "password manager" "rofi bitwarden" tree cmd
where where
cmd _ = cmd _ = spawnCmd myDmenuPasswords
spawnCmd myDmenuPasswords $ $ ["-c"] ++ themeArgs "#bb6600" ++ myDmenuMatchingArgs
["-c"] ++ themeArgs "#bb6600" ++ myDmenuMatchingArgs tree = And_ (Only_ $ Bus [] $ busName_ "org.rofi.bitwarden")
tree = $ toAnd_ (DBusIO dmenuDep) (DBusIO $ localExe [] myDmenuPasswords)
And_ (Only_ $ Bus [] $ busName_ "org.rofi.bitwarden") $
toAnd_ (DBusIO dmenuDep) (DBusIO $ localExe [] myDmenuPasswords)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Clipboard -- | Clipboard
runClipMenu :: MonadUnliftIO m => Sometimes (m ()) runClipMenu :: SometimesX
runClipMenu = sometimesIO_ "clipboard manager" "rofi greenclip" tree act runClipMenu = sometimesIO_ "clipboard manager" "rofi greenclip" tree act
where where
act = spawnCmd myDmenuCmd args act = spawnCmd myDmenuCmd args
tree = tree = listToAnds dmenuDep [ sysExe clipboardPkgs myClipboardManager
listToAnds , process [] myClipboardManager
dmenuDep ]
[ sysExe clipboardPkgs myClipboardManager args = [ "-modi", "\"clipboard:greenclip print\""
, process [] $ T.pack myClipboardManager , "-show", "clipboard"
] , "-run-command", "'{cmd}'"
args = ] ++ themeArgs "#00c44e"
[ "-modi"
, "\"clipboard:greenclip print\""
, "-show"
, "clipboard"
, "-run-command"
, "'{cmd}'"
]
++ themeArgs "#00c44e"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Shortcut menu -- | Shortcut menu
runShowKeys runShowKeys :: Always ([((KeyMask, KeySym), NamedAction)] -> X ())
:: (MonadReader env m, MonadUnliftIO m) runShowKeys = Always "keyboard menu" $ Option showKeysDMenu $ Always_
=> Always ([((KeyMask, KeySym), NamedAction)] -> m ()) $ FallbackAlone fallback
runShowKeys =
Always "keyboard menu" $
Option showKeysDMenu $
Always_ $
FallbackAlone fallback
where where
-- TODO this should technically depend on dunst -- TODO this should technically depend on dunst
fallback = fallback = const $ spawnNotify
const $ $ defNoteError { body = Just $ Text "could not display keymap" }
spawnNotify $
defNoteError {body = Just $ Text "could not display keymap"}
showKeysDMenu showKeysDMenu :: SubfeatureRoot ([((KeyMask, KeySym), NamedAction)] -> X ())
:: (MonadReader env m, MonadUnliftIO m) showKeysDMenu = Subfeature
=> SubfeatureRoot ([((KeyMask, KeySym), NamedAction)] -> m ()) { sfName = "keyboard shortcut menu"
showKeysDMenu = , sfData = IORoot_ showKeys $ Only_ dmenuDep
Subfeature }
{ sfName = "keyboard shortcut menu"
, sfData = IORoot_ showKeys $ Only_ dmenuDep
}
showKeys showKeys :: [((KeyMask, KeySym), NamedAction)] -> X ()
:: (MonadReader env m, MonadUnliftIO m) showKeys kbs = io $ do
=> [((KeyMask, KeySym), NamedAction)] (h, _, _, _) <- createProcess' $ (shell' cmd) { std_in = CreatePipe }
-> m () forM_ h $ \h' -> hPutStr h' (unlines $ showKm kbs) >> hClose h'
showKeys kbs = do
h <- spawnPipe cmd
B.hPut h $ BC.unlines $ BC.pack <$> showKm kbs
hClose h
where where
cmd = cmd = fmtCmd myDmenuCmd $ ["-dmenu", "-p", "commands"]
fmtCmd myDmenuCmd $ ++ themeArgs "#7f66ff" ++ myDmenuMatchingArgs
["-dmenu", "-p", "commands"]
++ themeArgs "#7f66ff"
++ myDmenuMatchingArgs

View File

@ -1,9 +1,10 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- General commands -- | General commands
module XMonad.Internal.Command.Desktop module XMonad.Internal.Command.Desktop
( myTerm ( myTerm
, playSound , playSound
-- commands -- commands
, runTerm , runTerm
, runTMux , runTMux
@ -19,8 +20,7 @@ module XMonad.Internal.Command.Desktop
, runVolumeUp , runVolumeUp
, runVolumeMute , runVolumeMute
, runToggleBluetooth , runToggleBluetooth
, runToggleNetworking , runToggleEthernet
, runToggleWifi
, runRestart , runRestart
, runRecompile , runRecompile
, runAreaCapture , runAreaCapture
@ -31,70 +31,76 @@ module XMonad.Internal.Command.Desktop
, runNotificationCloseAll , runNotificationCloseAll
, runNotificationHistory , runNotificationHistory
, runNotificationContext , runNotificationContext
-- daemons -- daemons
, runNetAppDaemon , runNetAppDaemon
-- packages -- packages
, networkManagerPkgs , networkManagerPkgs
) ) where
where
import DBus import Control.Monad (void)
import Data.Internal.DBus import Control.Monad.IO.Class
import Data.Internal.XIO
import RIO import Data.Internal.DBus
import RIO.Directory import Data.Internal.Dependency
import RIO.FilePath
import qualified RIO.Process as P import DBus
import qualified RIO.Text as T
import System.Posix.User import System.Directory
import UnliftIO.Environment import System.Environment
import XMonad.Actions.Volume import System.FilePath
import XMonad.Core hiding (spawn) import System.Posix.User
import XMonad.Internal.DBus.Common
import XMonad.Internal.Notify import XMonad (asks)
import XMonad.Internal.Shell as S import XMonad.Actions.Volume
import XMonad.Operations import XMonad.Core hiding (spawn)
import XMonad.Internal.DBus.Common
import XMonad.Internal.Notify
import XMonad.Internal.Process
import XMonad.Internal.Shell
import XMonad.Operations
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- My Executables -- | My Executables
myTerm :: FilePath myTerm :: String
myTerm = "alacritty" myTerm = "urxvt"
myCalc :: FilePath myCalc :: String
myCalc = "bc" myCalc = "bc"
myBrowser :: FilePath myBrowser :: String
myBrowser = "firefox" myBrowser = "brave-accel"
myEditor :: FilePath myEditor :: String
myEditor = "emacsclient" myEditor = "emacsclient"
myEditorServer :: FilePath myEditorServer :: String
myEditorServer = "emacs" myEditorServer = "emacs"
myMultimediaCtl :: FilePath myMultimediaCtl :: String
myMultimediaCtl = "playerctl" myMultimediaCtl = "playerctl"
myBluetooth :: FilePath myBluetooth :: String
myBluetooth = "bluetoothctl" myBluetooth = "bluetoothctl"
myCapture :: FilePath myCapture :: String
myCapture = "flameshot" myCapture = "flameshot"
myImageBrowser :: FilePath myImageBrowser :: String
myImageBrowser = "feh" myImageBrowser = "feh"
myNotificationCtrl :: FilePath myNotificationCtrl :: String
myNotificationCtrl = "dunstctl" myNotificationCtrl = "dunstctl"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Packages -- | Packages
myTermPkgs :: [Fulfillment] myTermPkgs :: [Fulfillment]
myTermPkgs = myTermPkgs = [ Package Official "rxvt-unicode"
[ Package Official "alacritty" , Package Official "urxvt-perls"
] ]
myEditorPkgs :: [Fulfillment] myEditorPkgs :: [Fulfillment]
myEditorPkgs = [Package Official "emacs-nativecomp"] myEditorPkgs = [Package Official "emacs-nativecomp"]
@ -108,258 +114,186 @@ bluetoothPkgs = [Package Official "bluez-utils"]
networkManagerPkgs :: [Fulfillment] networkManagerPkgs :: [Fulfillment]
networkManagerPkgs = [Package Official "networkmanager"] networkManagerPkgs = [Package Official "networkmanager"]
nmcli :: IODependency_
nmcli = sysExe networkManagerPkgs "nmcli"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Misc constants -- | Misc constants
volumeChangeSound :: FilePath volumeChangeSound :: FilePath
volumeChangeSound = "smb_fireball.wav" volumeChangeSound = "smb_fireball.wav"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Some nice apps -- | Some nice apps
runTerm :: MonadUnliftIO m => Sometimes (m ()) runTerm :: SometimesX
runTerm = sometimesExe "terminal" "alacritty" myTermPkgs True myTerm runTerm = sometimesExe "terminal" "urxvt" myTermPkgs True myTerm
runTMux :: MonadUnliftIO m => Sometimes (m ()) runTMux :: SometimesX
runTMux = sometimesIO_ "terminal multiplexer" "tmux" deps act runTMux = sometimesIO_ "terminal multiplexer" "tmux" deps act
where where
deps = deps = listToAnds (socketExists "tmux" [] socketName)
listToAnds (socketExists "tmux" [] socketName) $ $ fmap (sysExe myTermPkgs) [myTerm, "tmux", "bash"]
fmap (sysExe myTermPkgs) [myTerm, "tmux", "bash"] act = spawn
act = $ "tmux has-session"
S.spawn $ #!&& fmtCmd myTerm ["-e", "bash", "-c", singleQuote c]
fmtCmd "tmux" ["has-session"] #!|| fmtNotifyCmd defNoteError { body = Just $ Text msg }
#!&& fmtCmd myTerm ["-e", "bash", "-c", singleQuote c]
#!|| fmtNotifyCmd defNoteError {body = Just $ Text msg}
c = "exec tmux attach-session -d" c = "exec tmux attach-session -d"
msg = "could not connect to tmux session" msg = "could not connect to tmux session"
socketName = do socketName = do
u <- liftIO getEffectiveUserID u <- getEffectiveUserID
t <- getTemporaryDirectory t <- getTemporaryDirectory
return $ t </> "tmux-" ++ show u </> "default" return $ t </> "tmux-" ++ show u </> "default"
runCalc :: MonadUnliftIO m => Sometimes (m ()) runCalc :: SometimesX
runCalc = sometimesIO_ "calculator" "bc" deps act runCalc = sometimesIO_ "calculator" "bc" deps act
where where
deps = toAnd_ (sysExe myTermPkgs myTerm) (sysExe [Package Official "bc"] myCalc) deps = toAnd_ (sysExe myTermPkgs myTerm) (sysExe [Package Official "bc"] myCalc)
act = spawnCmd myTerm ["-e", T.pack myCalc, "-l"] act = spawnCmd myTerm ["-e", myCalc, "-l"]
runBrowser :: MonadUnliftIO m => Sometimes (m ()) runBrowser :: SometimesX
runBrowser = runBrowser = sometimesExe "web browser" "brave" [Package AUR "brave-bin"]
sometimesExe False myBrowser
"web browser"
"brave"
[Package AUR "brave-bin"]
False
myBrowser
runEditor :: MonadUnliftIO m => Sometimes (m ()) runEditor :: SometimesX
runEditor = sometimesIO_ "text editor" "emacs" tree cmd runEditor = sometimesIO_ "text editor" "emacs" tree cmd
where where
cmd = cmd = spawnCmd myEditor
spawnCmd ["-c", "-e", doubleQuote "(select-frame-set-input-focus (selected-frame))"]
myEditor
["-c", "-e", doubleQuote "(select-frame-set-input-focus (selected-frame))"]
-- NOTE 1: we could test if the emacs socket exists, but it won't come up -- NOTE 1: we could test if the emacs socket exists, but it won't come up
-- before xmonad starts, so just check to see if the process has started -- before xmonad starts, so just check to see if the process has started
tree = toAnd_ (sysExe myEditorPkgs myEditor) $ process [] $ T.pack myEditorServer tree = toAnd_ (sysExe myEditorPkgs myEditor) $ process [] myEditorServer
runFileManager :: MonadUnliftIO m => Sometimes (m ()) runFileManager :: SometimesX
runFileManager = runFileManager = sometimesExe "file browser" "pcmanfm" [Package Official "pcmanfm"]
sometimesExe True "pcmanfm"
"file browser"
"pcmanfm"
[Package Official "pcmanfm"]
True
"pcmanfm"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Multimedia Commands -- | Multimedia Commands
runMultimediaIfInstalled runMultimediaIfInstalled :: String -> String -> SometimesX
:: MonadUnliftIO m runMultimediaIfInstalled n cmd = sometimesExeArgs (n ++ " multimedia control")
=> T.Text "playerctl" [Package Official "playerctl"] True myMultimediaCtl [cmd]
-> T.Text
-> Sometimes (m ())
runMultimediaIfInstalled n cmd =
sometimesExeArgs
(T.append n " multimedia control")
"playerctl"
[Package Official "playerctl"]
True
myMultimediaCtl
[cmd]
runTogglePlay :: MonadUnliftIO m => Sometimes (m ()) runTogglePlay :: SometimesX
runTogglePlay = runMultimediaIfInstalled "play/pause" "play-pause" runTogglePlay = runMultimediaIfInstalled "play/pause" "play-pause"
runPrevTrack :: MonadUnliftIO m => Sometimes (m ()) runPrevTrack :: SometimesX
runPrevTrack = runMultimediaIfInstalled "previous track" "previous" runPrevTrack = runMultimediaIfInstalled "previous track" "previous"
runNextTrack :: MonadUnliftIO m => Sometimes (m ()) runNextTrack :: SometimesX
runNextTrack = runMultimediaIfInstalled "next track" "next" runNextTrack = runMultimediaIfInstalled "next track" "next"
runStopPlay :: MonadUnliftIO m => Sometimes (m ()) runStopPlay :: SometimesX
runStopPlay = runMultimediaIfInstalled "stop playback" "stop" runStopPlay = runMultimediaIfInstalled "stop playback" "stop"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Volume Commands -- | Volume Commands
soundDir :: FilePath soundDir :: FilePath
soundDir = "assets" </> "sound" soundDir = "sound"
playSound :: MonadIO m => FilePath -> m () playSound :: MonadIO m => FilePath -> m ()
playSound file = do playSound file = do
-- manually look up directories to avoid the X monad -- manually look up directories to avoid the X monad
p <- io $ (</> soundDir </> file) . cfgDir <$> getDirectories p <- io $ (</> soundDir </> file) . cfgDir <$> getDirectories
-- paplay seems to have less latency than aplay -- paplay seems to have less latency than aplay
spawnCmd "paplay" [T.pack p] spawnCmd "paplay" [p]
featureSound featureSound :: String -> FilePath -> X () -> X () -> SometimesX
:: MonadUnliftIO m
=> T.Text
-> FilePath
-> m ()
-> m ()
-> Sometimes (m ())
featureSound n file pre post = featureSound n file pre post =
sometimesIO_ (T.unwords ["volume", n, " control"]) "paplay" tree $ sometimesIO_ ("volume " ++ n ++ " control") "paplay" tree
pre >> playSound file >> post $ pre >> playSound file >> post
where where
-- ASSUME pulseaudio pulls in libpulse as a dep; pulseaudio itself is needed tree = Only_ $ sysExe [Package Official "libpulse"] "paplay"
-- to play sound (duh) but libpulse is the package with the paplay binary
tree = Only_ $ sysExe [Package Official "pulseaudio"] "paplay"
runVolumeDown :: MonadUnliftIO m => Sometimes (m ()) runVolumeDown :: SometimesX
runVolumeDown = featureSound "up" volumeChangeSound (return ()) $ void (lowerVolume 2) runVolumeDown = featureSound "up" volumeChangeSound (return ()) $ void (lowerVolume 2)
runVolumeUp :: MonadUnliftIO m => Sometimes (m ()) runVolumeUp :: SometimesX
runVolumeUp = featureSound "down" volumeChangeSound (return ()) $ void (raiseVolume 2) runVolumeUp = featureSound "down" volumeChangeSound (return ()) $ void (raiseVolume 2)
runVolumeMute :: MonadUnliftIO m => Sometimes (m ()) runVolumeMute :: SometimesX
runVolumeMute = featureSound "mute" volumeChangeSound (void toggleMute) $ return () runVolumeMute = featureSound "mute" volumeChangeSound (void toggleMute) $ return ()
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Notification control -- | Notification control
runNotificationCmd runNotificationCmd :: String -> FilePath -> Maybe SesClient -> SometimesX
:: MonadUnliftIO m
=> T.Text
-> T.Text
-> Maybe NamedSesConnection
-> Sometimes (m ())
runNotificationCmd n arg cl = runNotificationCmd n arg cl =
sometimesDBus cl (T.unwords [n, "control"]) "dunstctl" tree cmd sometimesDBus cl (n ++ " control") "dunstctl" tree cmd
where where
cmd _ = spawnCmd myNotificationCtrl [arg] cmd _ = spawnCmd myNotificationCtrl [arg]
tree = tree = toAnd_ (DBusIO $ sysExe notifyPkgs myNotificationCtrl)
toAnd_ (DBusIO $ sysExe notifyPkgs myNotificationCtrl) $ $ Endpoint [] notifyBus notifyPath (interfaceName_ "org.dunstproject.cmd0")
Endpoint [] notifyBus notifyPath (interfaceName_ "org.dunstproject.cmd0") $ $ Method_ $ memberName_ "NotificationAction"
Method_ $
memberName_ "NotificationAction"
runNotificationClose :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runNotificationClose :: Maybe SesClient -> SometimesX
runNotificationClose = runNotificationCmd "close notification" "close" runNotificationClose = runNotificationCmd "close notification" "close"
runNotificationCloseAll :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runNotificationCloseAll :: Maybe SesClient -> SometimesX
runNotificationCloseAll = runNotificationCloseAll =
runNotificationCmd "close all notifications" "close-all" runNotificationCmd "close all notifications" "close-all"
runNotificationHistory :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runNotificationHistory :: Maybe SesClient -> SometimesX
runNotificationHistory = runNotificationHistory =
runNotificationCmd "see notification history" "history-pop" runNotificationCmd "see notification history" "history-pop"
runNotificationContext :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runNotificationContext :: Maybe SesClient -> SometimesX
runNotificationContext = runNotificationContext =
runNotificationCmd "open notification context" "context" runNotificationCmd "open notification context" "context"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- System commands -- | System commands
-- needed to lookup/prompt for passwords/keys for wifi connections and some VPNs -- this is required for some vpn's to work properly with network-manager
runNetAppDaemon :: Maybe NamedSysConnection -> Sometimes (XIO (P.Process () () ())) runNetAppDaemon :: Maybe SysClient -> Sometimes (IO ProcessHandle)
runNetAppDaemon cl = runNetAppDaemon cl = Sometimes "network applet" xpfVPN
Sometimes [Subfeature (DBusRoot_ cmd tree cl) "NM-applet"]
"network applet"
(\x -> xpfVPN x || xpfWireless x)
[Subfeature (DBusRoot_ cmd tree cl) "NM-applet"]
where where
tree = toAnd_ app $ Bus networkManagerPkgs networkManagerBus tree = toAnd_ app $ Bus networkManagerPkgs networkManagerBus
app = DBusIO $ sysExe [Package Official "network-manager-applet"] "nm-applet" app = DBusIO $ sysExe [Package Official "network-manager-applet"] "nm-applet"
cmd _ = P.proc "nm-applet" [] (P.startProcess . P.setCreateGroup True) cmd _ = snd <$> spawnPipe "nm-applet"
runToggleBluetooth :: MonadUnliftIO m => Maybe NamedSysConnection -> Sometimes (m ()) runToggleBluetooth :: Maybe SysClient -> SometimesX
runToggleBluetooth cl = runToggleBluetooth cl = Sometimes "bluetooth toggle" xpfBluetooth
Sometimes [Subfeature (DBusRoot_ cmd tree cl) "bluetoothctl"]
"bluetooth toggle"
xpfBluetooth
[Subfeature (DBusRoot_ cmd tree cl) "bluetoothctl"]
where where
tree = And_ (Only_ $ DBusIO $ sysExe bluetoothPkgs myBluetooth) (Only_ $ Bus [] btBus) tree = And_ (Only_ $ DBusIO $ sysExe bluetoothPkgs myBluetooth) (Only_ $ Bus [] btBus)
cmd _ = cmd _ = spawn
S.spawn $ $ myBluetooth ++ " show | grep -q \"Powered: no\""
fmtCmd myBluetooth ["show"] #!&& "a=on"
#!| "grep -q \"Powered: no\"" #!|| "a=off"
#!&& "a=on" #!>> fmtCmd myBluetooth ["power", "$a", ">", "/dev/null"]
#!|| "a=off" #!&& fmtNotifyCmd defNoteInfo { body = Just $ Text "bluetooth powered $a" }
#!>> fmtCmd myBluetooth ["power", "$a", ">", "/dev/null"]
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "bluetooth powered $a"}
runToggleNetworking :: MonadUnliftIO m => Sometimes (m ()) runToggleEthernet :: SometimesX
runToggleNetworking = runToggleEthernet = sometimes1 "ethernet toggle" "nmcli" $ IORoot (spawn . cmd) $
Sometimes And1 (Only readEthernet) (Only_ $ sysExe networkManagerPkgs "nmcli")
"network toggle"
(\x -> xpfEthernet x || xpfWireless x)
[Subfeature root "nmcli"]
where where
root = IORoot_ cmd $ Only_ nmcli -- TODO make this less noisy
cmd = cmd iface =
S.spawn $ "nmcli -g GENERAL.STATE device show " ++ iface ++ " | grep -q disconnected"
fmtCmd "nmcli" ["networking"] #!&& "a=connect"
#!| "grep -q enabled" #!|| "a=disconnect"
#!&& "a=off" #!>> fmtCmd "nmcli" ["device", "$a", iface]
#!|| "a=on" #!&& fmtNotifyCmd defNoteInfo { body = Just $ Text "ethernet \"$a\"ed" }
#!>> fmtCmd "nmcli" ["networking", "$a"]
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "networking switched $a"}
runToggleWifi :: MonadUnliftIO m => Sometimes (m ())
runToggleWifi = Sometimes "wifi toggle" xpfWireless [Subfeature root "nmcli"]
where
root = IORoot_ cmd $ Only_ nmcli
cmd =
S.spawn $
fmtCmd "nmcli" ["radio", "wifi"]
#!| "grep -q enabled"
#!&& "a=off"
#!|| "a=on"
#!>> fmtCmd "nmcli" ["radio", "wifi", "$a"]
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "wifi switched $a"}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Configuration commands -- | Configuration commands
runRestart :: X () runRestart :: X ()
runRestart = restart "xmonad" True runRestart = restart "xmonad" True
-- TODO use rio in here so I don't have to fill my xinit log with stack poop
-- TODO only recompile the VM binary if we have virtualbox enabled
runRecompile :: X () runRecompile :: X ()
runRecompile = do runRecompile = do
-- assume that the conf directory contains a valid stack project -- assume that the conf directory contains a valid stack project
confDir <- asks (cfgDir . directories) confDir <- asks (cfgDir . directories)
spawn $ spawnAt confDir $ fmtCmd "stack" ["install"]
fmtCmd "cd" [T.pack confDir] #!&& fmtNotifyCmd defNoteInfo { body = Just $ Text "compilation succeeded" }
#!&& fmtCmd "stack" ["install"] #!|| fmtNotifyCmd defNoteError { body = Just $ Text "compilation failed" }
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "compilation succeeded"}
#!|| fmtNotifyCmd defNoteError {body = Just $ Text "compilation failed"}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Screen capture commands -- | Screen capture commands
getCaptureDir :: MonadIO m => m FilePath getCaptureDir :: IO FilePath
getCaptureDir = do getCaptureDir = do
e <- lookupEnv "XDG_DATA_HOME" e <- lookupEnv "XDG_DATA_HOME"
parent <- case e of parent <- case e of
@ -373,38 +307,28 @@ getCaptureDir = do
where where
fallback = (</> ".local/share") <$> getHomeDirectory fallback = (</> ".local/share") <$> getHomeDirectory
runFlameshot runFlameshot :: String -> String -> Maybe SesClient -> SometimesX
:: MonadUnliftIO m runFlameshot n mode cl = sometimesDBus cl n myCapture tree cmd
=> T.Text
-> T.Text
-> Maybe NamedSesConnection
-> Sometimes (m ())
runFlameshot n mode cl = sometimesDBus cl n (T.pack myCapture) tree cmd
where where
cmd _ = spawnCmd myCapture [mode] cmd _ = spawnCmd myCapture [mode]
tree = tree = toAnd_ (DBusIO $ sysExe [Package Official "flameshot"] myCapture)
toAnd_ (DBusIO $ sysExe [Package Official "flameshot"] myCapture) $ $ Bus [] $ busName_ "org.flameshot.Flameshot"
Bus [] $
busName_ "org.flameshot.Flameshot"
-- TODO this will steal focus from the current window (and puts it -- TODO this will steal focus from the current window (and puts it
-- in the root window?) ...need to fix -- in the root window?) ...need to fix
runAreaCapture :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runAreaCapture :: Maybe SesClient -> SometimesX
runAreaCapture = runFlameshot "screen area capture" "gui" runAreaCapture = runFlameshot "screen area capture" "gui"
-- myWindowCap = "screencap -w" --external script -- myWindowCap = "screencap -w" --external script
runDesktopCapture :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runDesktopCapture :: Maybe SesClient -> SometimesX
runDesktopCapture = runFlameshot "fullscreen capture" "full" runDesktopCapture = runFlameshot "fullscreen capture" "full"
runScreenCapture :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ()) runScreenCapture :: Maybe SesClient -> SometimesX
runScreenCapture = runFlameshot "screen capture" "screen" runScreenCapture = runFlameshot "screen capture" "screen"
runCaptureBrowser :: MonadUnliftIO m => Sometimes (m ()) runCaptureBrowser :: SometimesX
runCaptureBrowser = sometimesIO_ runCaptureBrowser = sometimesIO_ "screen capture browser" "feh"
"screen capture browser" (Only_ $ sysExe [Package Official "feh"] myImageBrowser) $ do
"feh" dir <- io getCaptureDir
(Only_ $ sysExe [Package Official "feh"] myImageBrowser) spawnCmd myImageBrowser [dir]
$ do
dir <- getCaptureDir
spawnCmd myImageBrowser [T.pack dir]

View File

@ -1,8 +1,8 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Commands for controlling power -- | Commands for controlling power
module XMonad.Internal.Command.Power module XMonad.Internal.Command.Power
-- commands -- commands
( runHibernate ( runHibernate
, runOptimusPrompt , runOptimusPrompt
, runPowerOff , runPowerOff
@ -12,8 +12,10 @@ module XMonad.Internal.Command.Power
, runSuspend , runSuspend
, runSuspendPrompt , runSuspendPrompt
, runQuitPrompt , runQuitPrompt
-- daemons -- daemons
, runAutolock , runAutolock
-- functions -- functions
, hasBattery , hasBattery
, suspendPrompt , suspendPrompt
@ -21,97 +23,97 @@ module XMonad.Internal.Command.Power
, powerPrompt , powerPrompt
, defFontPkgs , defFontPkgs
, promptFontDep , promptFontDep
) ) where
where
import Data.Internal.XIO import Control.Arrow (first)
import Graphics.X11.Types
import RIO import Data.Internal.Dependency
import RIO.Directory
import RIO.FilePath import Data.Either
import qualified RIO.Map as M import qualified Data.Map as M
import qualified RIO.Process as P
import qualified RIO.Text as T import Graphics.X11.Types
import XMonad.Core hiding (spawn)
import XMonad.Internal.Shell import System.Directory
import qualified XMonad.Internal.Theme as XT import System.Exit
import XMonad.Prompt import System.FilePath.Posix
import XMonad.Prompt.ConfirmPrompt import System.IO.Error
import System.Process (ProcessHandle)
import XMonad.Core
import XMonad.Internal.Process (spawnPipeArgs)
import XMonad.Internal.Shell
import qualified XMonad.Internal.Theme as T
import XMonad.Prompt
import XMonad.Prompt.ConfirmPrompt
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Executables -- | Executables
myScreenlock :: FilePath
myScreenlock :: String
myScreenlock = "screenlock" myScreenlock = "screenlock"
myOptimusManager :: FilePath myOptimusManager :: String
myOptimusManager = "optimus-manager" myOptimusManager = "optimus-manager"
myPrimeOffload :: FilePath myPrimeOffload :: String
myPrimeOffload = "prime-offload" myPrimeOffload = "prime-offload"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Packages -- | Packages
optimusPackages :: [Fulfillment] optimusPackages :: [Fulfillment]
optimusPackages = [Package AUR "optimus-manager"] optimusPackages = [Package AUR "optimus-manager"]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Core commands -- | Core commands
runScreenLock :: SometimesX runScreenLock :: SometimesX
runScreenLock = runScreenLock = sometimesExe "screen locker" "i3lock script"
sometimesExe [Package AUR "i3lock-color"] False myScreenlock
"screen locker"
"i3lock script"
[Package AUR "i3lock-color"]
False
myScreenlock
runPowerOff :: MonadUnliftIO m => m () runPowerOff :: X ()
runPowerOff = spawn "systemctl poweroff" runPowerOff = spawn "systemctl poweroff"
runSuspend :: MonadUnliftIO m => m () runSuspend :: X ()
runSuspend = spawn "systemctl suspend" runSuspend = spawn "systemctl suspend"
runHibernate :: MonadUnliftIO m => m () runHibernate :: X ()
runHibernate = spawn "systemctl hibernate" runHibernate = spawn "systemctl hibernate"
runReboot :: MonadUnliftIO m => m () runReboot :: X ()
runReboot = spawn "systemctl reboot" runReboot = spawn "systemctl reboot"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Autolock -- | Autolock
runAutolock :: Sometimes (XIO (P.Process () () ())) runAutolock :: Sometimes (IO ProcessHandle)
runAutolock = sometimesIO_ "automatic screen lock" "xss-lock" tree cmd runAutolock = sometimesIO_ "automatic screen lock" "xss-lock" tree cmd
where where
tree = tree = And_ (Only_ $ sysExe [Package Official "xss-lock"] "xss-lock")
And_ (Only_ $ sysExe [Package Official "xss-lock"] "xss-lock") $ $ Only_ $ IOSometimes_ runScreenLock
Only_ $ cmd = snd <$> spawnPipeArgs "xss-lock" ["--ignore-sleep", "screenlock"]
IOSometimes_ runScreenLock
cmd = P.proc "xss-lock" args (P.startProcess . P.setCreateGroup True)
args = ["--ignore-sleep", "--", "screenlock", "true"]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Confirmation prompts -- | Confirmation prompts
promptFontDep :: IOTree XT.FontBuilder promptFontDep :: IOTree T.FontBuilder
promptFontDep = fontTreeAlt XT.defFontFamily defFontPkgs promptFontDep = fontTreeAlt T.defFontFamily defFontPkgs
defFontPkgs :: [Fulfillment] defFontPkgs :: [Fulfillment]
defFontPkgs = [Package Official "ttf-dejavu"] defFontPkgs = [Package Official "ttf-dejavu"]
confirmPrompt' :: T.Text -> X () -> XT.FontBuilder -> X () confirmPrompt' :: String -> X () -> T.FontBuilder -> X ()
confirmPrompt' s x fb = confirmPrompt (XT.promptTheme fb) (T.unpack s) x confirmPrompt' s x fb = confirmPrompt (T.promptTheme fb) s x
suspendPrompt :: XT.FontBuilder -> X () suspendPrompt :: T.FontBuilder -> X ()
suspendPrompt = confirmPrompt' "suspend?" $ liftIO runSuspend suspendPrompt = confirmPrompt' "suspend?" runSuspend
quitPrompt :: XT.FontBuilder -> X () quitPrompt :: T.FontBuilder -> X ()
quitPrompt = confirmPrompt' "quit?" $ io exitSuccess quitPrompt = confirmPrompt' "quit?" $ io exitSuccess
sometimesPrompt :: T.Text -> (XT.FontBuilder -> X ()) -> SometimesX sometimesPrompt :: String -> (T.FontBuilder -> X ()) -> SometimesX
sometimesPrompt n = sometimesIO n (T.append n " command") promptFontDep sometimesPrompt n = sometimesIO n (n ++ " command") promptFontDep
-- TODO doesn't this need to also lock the screen? -- TODO doesn't this need to also lock the screen?
runSuspendPrompt :: SometimesX runSuspendPrompt :: SometimesX
@ -121,83 +123,71 @@ runQuitPrompt :: SometimesX
runQuitPrompt = sometimesPrompt "quit prompt" quitPrompt runQuitPrompt = sometimesPrompt "quit prompt" quitPrompt
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Nvidia Optimus -- | Nvidia Optimus
-- TODO for some reason the screen never wakes up after suspend when -- TODO for some reason the screen never wakes up after suspend when
-- the nvidia card is up, so block suspend if nvidia card is running -- the nvidia card is up, so block suspend if nvidia card is running
-- and warn user -- and warn user
isUsingNvidia :: MonadUnliftIO m => m Bool isUsingNvidia :: IO Bool
isUsingNvidia = doesDirectoryExist "/sys/module/nvidia" isUsingNvidia = doesDirectoryExist "/sys/module/nvidia"
hasBattery :: MonadUnliftIO m => m (Maybe T.Text) hasBattery :: IO (Maybe String)
hasBattery = do hasBattery = do
ps <- fromRight [] <$> tryIO (listDirectory syspath) ps <- fromRight [] <$> tryIOError (listDirectory syspath)
ts <- catMaybes <$> mapM readType ps ts <- mapM readType ps
return $ return $ if "Battery\n" `elem` ts then Nothing else Just "battery not found"
if any (T.isPrefixOf "Battery") ts
then Nothing
else Just "battery not found"
where where
readType p = either (const Nothing) Just <$> tryIO (readFileUtf8 $ syspath </> p </> "type") readType p = fromRight [] <$> tryIOError (readFile $ syspath </> p </> "type")
syspath = "/sys/class/power_supply" syspath = "/sys/class/power_supply"
runOptimusPrompt' :: XT.FontBuilder -> X () runOptimusPrompt' :: T.FontBuilder -> X ()
runOptimusPrompt' fb = do runOptimusPrompt' fb = do
nvidiaOn <- io isUsingNvidia nvidiaOn <- io isUsingNvidia
switch $ if nvidiaOn then "integrated" else "nvidia" switch $ if nvidiaOn then "integrated" else "nvidia"
where where
switch mode = confirmPrompt' (prompt mode) (cmd mode) fb switch mode = confirmPrompt' (prompt mode) (cmd mode) fb
prompt mode = T.concat ["gpu switch to ", mode, "?"] prompt mode = "gpu switch to " ++ mode ++ "?"
cmd mode = cmd mode = spawn $
spawn $ myPrimeOffload
T.pack myPrimeOffload #!&& unwords [myOptimusManager, "--switch", mode, "--no-confirm"]
#!&& T.unwords [T.pack myOptimusManager, "--switch", mode, "--no-confirm"] #!&& "killall xmonad"
#!&& "killall xmonad"
runOptimusPrompt :: SometimesX runOptimusPrompt :: SometimesX
runOptimusPrompt = runOptimusPrompt = Sometimes "graphics switcher"
Sometimes (\x -> xpfOptimus x && xpfBattery x) [s]
"graphics switcher"
(\x -> xpfOptimus x && xpfBattery x)
[s]
where where
s = Subfeature {sfData = r, sfName = "optimus manager"} s = Subfeature { sfData = r, sfName = "optimus manager" }
r = IORoot runOptimusPrompt' t r = IORoot runOptimusPrompt' t
t = t = And1 promptFontDep
And1 promptFontDep $ $ listToAnds (socketExists "optimus-manager" [] socketName)
listToAnds (socketExists "optimus-manager" [] socketName) $ $ sysExe optimusPackages <$> [myOptimusManager, myPrimeOffload]
sysExe optimusPackages <$> [myOptimusManager, myPrimeOffload]
socketName = (</> "optimus-manager") <$> getTemporaryDirectory socketName = (</> "optimus-manager") <$> getTemporaryDirectory
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Universal power prompt -- | Universal power prompt
data PowerMaybeAction data PowerMaybeAction = Poweroff
= Poweroff | Shutdown
| Shutdown | Hibernate
| Hibernate | Reboot
| Reboot deriving (Eq)
deriving (Eq)
fromPMA :: PowerMaybeAction -> Int instance Enum PowerMaybeAction where
fromPMA a = case a of toEnum 0 = Poweroff
Poweroff -> 0 toEnum 1 = Shutdown
Shutdown -> 1 toEnum 2 = Hibernate
Hibernate -> 2 toEnum 3 = Reboot
Reboot -> 3 toEnum _ = errorWithoutStackTrace "Main.Enum.PowerMaybeAction.toEnum: bad argument"
toPMA :: Int -> Maybe PowerMaybeAction fromEnum Poweroff = 0
toPMA x = case x of fromEnum Shutdown = 1
0 -> Just Poweroff fromEnum Hibernate = 2
1 -> Just Shutdown fromEnum Reboot = 3
2 -> Just Hibernate
3 -> Just Reboot
_ -> Nothing
data PowerPrompt = PowerPrompt data PowerPrompt = PowerPrompt
instance XPrompt PowerPrompt where instance XPrompt PowerPrompt where
showXPrompt PowerPrompt = "(P)oweroff (S)uspend (H)ibernate (R)eboot:" showXPrompt PowerPrompt = "(P)oweroff (S)uspend (H)ibernate (R)eboot:"
runPowerPrompt :: SometimesX runPowerPrompt :: SometimesX
runPowerPrompt = Sometimes "power prompt" (const True) [sf] runPowerPrompt = Sometimes "power prompt" (const True) [sf]
@ -207,28 +197,24 @@ runPowerPrompt = Sometimes "power prompt" (const True) [sf]
tree = And12 (,) lockTree promptFontDep tree = And12 (,) lockTree promptFontDep
lockTree = Or (Only $ IOSometimes runScreenLock id) (Only $ IOConst skip) lockTree = Or (Only $ IOSometimes runScreenLock id) (Only $ IOConst skip)
powerPrompt :: X () -> XT.FontBuilder -> X () powerPrompt :: X () -> T.FontBuilder -> X ()
powerPrompt lock fb = mkXPrompt PowerPrompt theme comp executeMaybeAction powerPrompt lock fb = mkXPrompt PowerPrompt theme comp executeMaybeAction
where where
comp = mkComplFunFromList theme [] comp = mkComplFunFromList theme []
theme = (XT.promptTheme fb) {promptKeymap = keymap} theme = (T.promptTheme fb) { promptKeymap = keymap }
keymap = keymap = M.fromList
M.fromList $ $ ((controlMask, xK_g), quit) :
((controlMask, xK_g), quit) map (first $ (,) 0)
: map [ (xK_p, sendMaybeAction Poweroff)
(first $ (,) 0) , (xK_s, sendMaybeAction Shutdown)
[ (xK_p, sendMaybeAction Poweroff) , (xK_h, sendMaybeAction Hibernate)
, (xK_s, sendMaybeAction Shutdown) , (xK_r, sendMaybeAction Reboot)
, (xK_h, sendMaybeAction Hibernate) , (xK_Return, quit)
, (xK_r, sendMaybeAction Reboot) , (xK_Escape, quit)
, (xK_Return, quit) ]
, (xK_Escape, quit) sendMaybeAction a = setInput (show $ fromEnum a) >> setSuccess True >> setDone True
] executeMaybeAction a = case toEnum $ read a of
sendMaybeAction a = setInput (show $ fromPMA a) >> setSuccess True >> setDone True Poweroff -> runPowerOff
executeMaybeAction a = case toPMA =<< readMaybe a of Shutdown -> lock >> runSuspend
Just Poweroff -> liftIO runPowerOff Hibernate -> lock >> runHibernate
Just Shutdown -> lock >> liftIO runSuspend Reboot -> runReboot
Just Hibernate -> lock >> liftIO runHibernate
Just Reboot -> liftIO runReboot
-- TODO log an error here since this should never happen
Nothing -> skip

View File

@ -1,85 +1,91 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Concurrent module to handle events from acpid -- | Concurrent module to handle events from acpid
module XMonad.Internal.Concurrent.ACPIEvent module XMonad.Internal.Concurrent.ACPIEvent
( runPowermon ( runPowermon
, runHandleACPI , runHandleACPI
) ) where
where
import Data.Internal.XIO import Control.Exception
import Network.Socket import Control.Monad
import Network.Socket.ByteString
import RIO import Data.ByteString hiding (readFile)
import qualified RIO.ByteString as B import Data.ByteString.Char8 as C hiding (readFile)
import XMonad.Core import Data.Connection
import XMonad.Internal.Command.Power import Data.Internal.Dependency
import XMonad.Internal.Concurrent.ClientMessage
import XMonad.Internal.Shell import Text.Read (readMaybe)
import XMonad.Internal.Theme (FontBuilder)
import System.IO.Streams as S (read)
import System.IO.Streams.UnixSocket
import XMonad.Core
import XMonad.Internal.Command.Power
import XMonad.Internal.Concurrent.ClientMessage
import XMonad.Internal.Shell
import XMonad.Internal.Theme (FontBuilder)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Data structure to hold the ACPI events I care about -- | Data structure to hold the ACPI events I care about
-- --
-- Enumerate so these can be converted to strings and back when sent in a -- Enumerate so these can be converted to strings and back when sent in a
-- ClientMessage event to X -- ClientMessage event to X
data ACPIEvent data ACPIEvent = Power
= Power | Sleep
| Sleep | LidClose
| LidClose deriving (Eq)
deriving (Eq)
fromACPIEvent :: ACPIEvent -> Int instance Enum ACPIEvent where
fromACPIEvent x = case x of toEnum 0 = Power
Power -> 0 toEnum 1 = Sleep
Sleep -> 1 toEnum 2 = LidClose
LidClose -> 2 toEnum _ = errorWithoutStackTrace "ACPI.Enum.ACPIEvent.toEnum: bad argument"
toACPIEvent :: Int -> Maybe ACPIEvent fromEnum Power = 0
toACPIEvent x = case x of fromEnum Sleep = 1
0 -> Just Power fromEnum LidClose = 2
1 -> Just Sleep
2 -> Just LidClose
_ -> Nothing
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Internal functions -- | Internal functions
-- | Convert a string to an ACPI event (this string is assumed to come from -- | Convert a string to an ACPI event (this string is assumed to come from
-- the acpid socket) -- the acpid socket)
parseLine :: ByteString -> Maybe ACPIEvent parseLine :: ByteString -> Maybe ACPIEvent
parseLine line = parseLine line =
case splitLine line of case splitLine line of
(_ : "PBTN" : _) -> Just Power (_:"PBTN":_) -> Just Power
(_ : "PWRF" : _) -> Just Power (_:"PWRF":_) -> Just Power
(_ : "SLPB" : _) -> Just Sleep (_:"SLPB":_) -> Just Sleep
(_ : "SBTN" : _) -> Just Sleep (_:"SBTN":_) -> Just Sleep
(_ : "LID" : "close" : _) -> Just LidClose (_:"LID":"close":_) -> Just LidClose
_ -> Nothing _ -> Nothing
where where
splitLine = B.split space . B.reverse . B.dropWhile (== newline) . B.reverse splitLine = C.words . C.reverse . C.dropWhile (== '\n') . C.reverse
newline = 10
space = 32
-- | Send an ACPIEvent to the X server as a ClientMessage -- | Send an ACPIEvent to the X server as a ClientMessage
sendACPIEvent :: ACPIEvent -> IO () sendACPIEvent :: ACPIEvent -> IO ()
sendACPIEvent = sendXMsg ACPI . show . fromACPIEvent sendACPIEvent = sendXMsg ACPI . show . fromEnum
isDischarging :: IO (Maybe Bool) isDischarging :: IO (Maybe Bool)
isDischarging = do isDischarging = do
status <- tryIO $ B.readFile "/sys/class/power_supply/BAT0/status" status <- try $ readFile "/sys/class/power_supply/BAT0/status"
:: IO (Either IOException String)
case status of case status of
Left _ -> return Nothing Left _ -> return Nothing
Right s -> return $ Just (s == "Discharging") Right s -> return $ Just (s == "Discharging")
listenACPI :: IO () listenACPI :: IO ()
listenACPI = do listenACPI = do
sock <- socket AF_UNIX Stream defaultProtocol Connection { source = s } <- connect acpiPath
connect sock $ SockAddrUnix acpiPath forever $ readStream s
forever $ do where
out <- recv sock 1024 readStream s = do
mapM_ sendACPIEvent $ parseLine out out <- S.read s
mapM_ sendACPIEvent $ parseLine =<< out
acpiPath :: FilePath acpiPath :: FilePath
acpiPath = "/var/run/acpid.socket" acpiPath = "/var/run/acpid.socket"
@ -91,31 +97,29 @@ socketDep = Only_ $ pathR acpiPath [Package Official "acpid"]
-- Xmonad's event hook) -- Xmonad's event hook)
handleACPI :: FontBuilder -> X () -> String -> X () handleACPI :: FontBuilder -> X () -> String -> X ()
handleACPI fb lock tag = do handleACPI fb lock tag = do
let acpiTag = toACPIEvent =<< readMaybe tag :: Maybe ACPIEvent let acpiTag = toEnum <$> readMaybe tag :: Maybe ACPIEvent
forM_ acpiTag $ \case forM_ acpiTag $ \case
Power -> powerPrompt lock fb Power -> powerPrompt lock fb
Sleep -> suspendPrompt fb Sleep -> suspendPrompt fb
LidClose -> do LidClose -> do
status <- io isDischarging status <- io isDischarging
-- only run suspend if battery exists and is discharging -- only run suspend if battery exists and is discharging
forM_ status $ flip when $ liftIO runSuspend forM_ status $ flip when runSuspend
lock lock
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Exported API -- | Exported API
-- | Spawn a new thread that will listen for ACPI events on the acpid socket -- | Spawn a new thread that will listen for ACPI events on the acpid socket
-- and send ClientMessage events when it receives them -- and send ClientMessage events when it receives them
runPowermon :: SometimesIO runPowermon :: SometimesIO
runPowermon = sometimesIO_ "ACPI event monitor" "acpid" socketDep $ io listenACPI runPowermon = sometimesIO_ "ACPI event monitor" "acpid" socketDep listenACPI
runHandleACPI :: Always (String -> X ()) runHandleACPI :: Always (String -> X ())
runHandleACPI = Always "ACPI event handler" $ Option sf fallback runHandleACPI = Always "ACPI event handler" $ Option sf fallback
where where
sf = Subfeature withLock "acpid prompt" sf = Subfeature withLock "acpid prompt"
withLock = withLock = IORoot (uncurry handleACPI)
IORoot (uncurry handleACPI) $ $ And12 (,) promptFontDep $ Only
And12 (,) promptFontDep $ $ IOSometimes runScreenLock id
Only $
IOSometimes runScreenLock id
fallback = Always_ $ FallbackAlone $ const skip fallback = Always_ $ FallbackAlone $ const skip

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Core ClientMessage module to 'achieve' concurrency in XMonad -- | Core ClientMessage module to 'achieve' concurrency in XMonad
-- --
-- Since XMonad is single threaded, the only way to have multiple threads that -- Since XMonad is single threaded, the only way to have multiple threads that
-- listen/react to non-X events is to spawn other threads the run outside of -- listen/react to non-X events is to spawn other threads the run outside of
@ -16,61 +16,55 @@
-- much like something from X even though it isn't -- much like something from X even though it isn't
module XMonad.Internal.Concurrent.ClientMessage module XMonad.Internal.Concurrent.ClientMessage
( XMsgType (..) ( XMsgType(..)
, sendXMsg , sendXMsg
, splitXMsg , splitXMsg
) ) where
where
import Data.Char import Data.Char
import Graphics.X11.Types
import Graphics.X11.Xlib.Atom import Graphics.X11.Types
import Graphics.X11.Xlib.Display import Graphics.X11.Xlib.Atom
import Graphics.X11.Xlib.Event import Graphics.X11.Xlib.Display
import Graphics.X11.Xlib.Extras import Graphics.X11.Xlib.Event
import RIO import Graphics.X11.Xlib.Extras
import XMonad.Internal.IO
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Data structure for the ClientMessage -- | Data structure for the ClientMessage
-- --
-- These are the "types" of client messages to send; add more here as needed -- These are the "types" of client messages to send; add more here as needed
-- TODO is there a way to do this in the libraries that import this one? -- TODO is there a way to do this in the libraries that import this one?
data XMsgType data XMsgType = ACPI
= ACPI | Workspace
| Workspace | Unknown
| Unknown deriving (Eq, Show)
deriving (Eq, Show)
fromXMsgType :: XMsgType -> Int instance Enum XMsgType where
fromXMsgType x = case x of toEnum 0 = ACPI
ACPI -> 0 toEnum 1 = Workspace
Workspace -> 1 toEnum _ = errorWithoutStackTrace "ACPI.Enum.ACPIEvent.toEnum: bad argument"
Unknown -> 2
toXMsgType :: Int -> Maybe XMsgType fromEnum ACPI = 0
toXMsgType x = case x of fromEnum Workspace = 1
0 -> Just ACPI fromEnum Unknown = 2
1 -> Just Workspace
2 -> Just Unknown
_ -> Nothing
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Exported API -- | Exported API
-- | Given a string from the data field in a ClientMessage event, return the -- | Given a string from the data field in a ClientMessage event, return the
-- type and payload -- type and payload
splitXMsg :: (Integral a) => [a] -> (XMsgType, String) splitXMsg :: (Integral a) => [a] -> (XMsgType, String)
splitXMsg [] = (Unknown, "") splitXMsg [] = (Unknown, "")
splitXMsg (x : xs) = (fromMaybe Unknown xtype, tag) splitXMsg (x:xs) = (xtype, tag)
where where
xtype = toXMsgType $ fromIntegral x xtype = toEnum $ fromInteger $ toInteger x
tag = chr . fromIntegral <$> takeWhile (/= 0) xs tag = map (chr . fromInteger . toInteger) $ takeWhile (/= 0) xs
-- | Emit a ClientMessage event to the X server with the given type and payloud -- | Emit a ClientMessage event to the X server with the given type and payloud
sendXMsg :: XMsgType -> String -> IO () sendXMsg :: XMsgType -> String -> IO ()
sendXMsg xtype tag = withOpenDisplay $ \dpy -> do sendXMsg xtype tag = do
dpy <- openDisplay ""
root <- rootWindow dpy $ defaultScreen dpy root <- rootWindow dpy $ defaultScreen dpy
allocaXEvent $ \e -> do allocaXEvent $ \e -> do
setEventType e clientMessage setEventType e clientMessage
@ -88,8 +82,10 @@ sendXMsg xtype tag = withOpenDisplay $ \dpy -> do
-- longer will be clipped to 19, and anything less than 19 will be padded -- longer will be clipped to 19, and anything less than 19 will be padded
-- with 0 (note this used to be random garbage before). See this function -- with 0 (note this used to be random garbage before). See this function
-- for more details. -- for more details.
setClientMessageEvent' e root bITMAP 8 (x : t) setClientMessageEvent' e root bITMAP 8 (x:t)
sendEvent dpy root False substructureNotifyMask e sendEvent dpy root False substructureNotifyMask e
flush dpy
closeDisplay dpy
where where
x = fromIntegral $ fromXMsgType xtype x = fromIntegral $ fromEnum xtype
t = fmap (fromIntegral . fromEnum) tag t = fmap (fromIntegral . fromEnum) tag

View File

@ -1,5 +1,7 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Automatically Manage Dynamic Workspaces -- | Automatically Manage Dynamic Workspaces
-- This is a somewhat convoluted wrapper for the Dymamic Workspaces module -- This is a somewhat convoluted wrapper for the Dymamic Workspaces module
-- in the contrib library. The general behavior this allows: -- in the contrib library. The general behavior this allows:
-- 1) launch app -- 1) launch app
@ -24,64 +26,63 @@
-- 3) Virtualbox (should always be by itself anyways) -- 3) Virtualbox (should always be by itself anyways)
module XMonad.Internal.Concurrent.DynamicWorkspaces module XMonad.Internal.Concurrent.DynamicWorkspaces
( DynWorkspace (..) ( DynWorkspace(..)
, appendShift , appendShift
, appendViewShift , appendViewShift
, removeDynamicWorkspace , removeDynamicWorkspace
, runWorkspaceMon , runWorkspaceMon
, spawnOrSwitch , spawnOrSwitch
, doSink , doSink
) ) where
where
import qualified Data.ByteString.Char8 as BC import Data.List (deleteBy, find)
import Data.Internal.XIO import qualified Data.Map as M
import Graphics.X11.Types import Data.Maybe
import Graphics.X11.Xlib.Atom
import Graphics.X11.Xlib.Display import Control.Concurrent
import Graphics.X11.Xlib.Event import Control.Monad
import Graphics.X11.Xlib.Extras import Control.Monad.Reader
import Graphics.X11.Xlib.Misc
import Graphics.X11.Xlib.Types import Graphics.X11.Types
import RIO hiding
( Display import Graphics.X11.Xlib.Atom
, display import Graphics.X11.Xlib.Display
) import Graphics.X11.Xlib.Event
import RIO.List (deleteBy, find) import Graphics.X11.Xlib.Extras
import qualified RIO.Map as M import Graphics.X11.Xlib.Misc
import qualified RIO.Set as S import Graphics.X11.Xlib.Types
import System.Process
import XMonad.Actions.DynamicWorkspaces import XMonad.Actions.DynamicWorkspaces
import XMonad.Core import XMonad.Core
( ManageHook ( ManageHook
, WorkspaceId , WorkspaceId
, X , X
, io , io
, withWindowSet , withWindowSet
) )
import XMonad.Hooks.ManageHelpers (MaybeManageHook) import XMonad.Hooks.ManageHelpers (MaybeManageHook)
import XMonad.Internal.Concurrent.ClientMessage import XMonad.Internal.Concurrent.ClientMessage
import XMonad.Internal.IO import XMonad.Internal.Process
import XMonad.ManageHook import XMonad.ManageHook
import XMonad.Operations import XMonad.Operations
import qualified XMonad.StackSet as W import qualified XMonad.StackSet as W
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Dynamic Workspace datatype -- | Dynamic Workspace datatype
-- This holds all the data needed to tie an app to a particular dynamic workspace -- This hold all the data needed to tie an app to a particular dynamic workspace
data DynWorkspace = DynWorkspace data DynWorkspace = DynWorkspace
{ dwName :: String { dwName :: String
, dwTag :: WorkspaceId , dwTag :: WorkspaceId
, dwClass :: String , dwClass :: String
, dwHook :: [MaybeManageHook] , dwHook :: [MaybeManageHook]
, dwKey :: Char , dwKey :: Char
, dwCmd :: Maybe (X ()) , dwCmd :: Maybe (X ())
-- TODO this should also have the layout for this workspace -- TODO this should also have the layout for this workspace
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Manager thread -- | Manager thread
-- The main thread that watches for new windows. When a match is found, this -- The main thread that watches for new windows. When a match is found, this
-- thread spawns a new thread the waits for the PID of the window to exit. When -- thread spawns a new thread the waits for the PID of the window to exit. When
-- the PID exits, it sends a ClientMessage event to X -- the PID exits, it sends a ClientMessage event to X
@ -90,93 +91,79 @@ data DynWorkspace = DynWorkspace
-- the same as that in XMonad itself (eg with Query types) -- the same as that in XMonad itself (eg with Query types)
-- type MatchTags = M.Map String String -- type MatchTags = M.Map String String
data WEnv = WEnv type WatchedPIDs = MVar [Pid]
{ wDisplay :: !Display
, wDynWorkspaces :: ![DynWorkspace]
, wCurPIDs :: !(MVar (S.Set Pid))
, wXEnv :: !XEnv
}
instance HasLogFunc WEnv where data WConf = WConf
logFuncL = lens wXEnv (\x y -> x {wXEnv = y}) . logFuncL { display :: Display
, dynWorkspaces :: [DynWorkspace]
}
type WIO a = RIO WEnv a newtype W a = W (ReaderT WConf IO a)
deriving (Functor, Monad, MonadIO, MonadReader WConf)
runWorkspaceMon :: [DynWorkspace] -> XIO () instance Applicative W where
runWorkspaceMon dws = withOpenDisplay $ \dpy -> do pure = return
root <- liftIO $ rootWindow dpy $ defaultScreen dpy (<*>) = ap
runW :: WConf -> W a -> IO a
runW c (W a) = runReaderT a c
runWorkspaceMon :: [DynWorkspace] -> IO ()
runWorkspaceMon dws = do
dpy <- openDisplay ""
root <- rootWindow dpy $ defaultScreen dpy
curPIDs <- newMVar [] -- TODO this is ugly, use a mutable state monad
-- listen only for substructure change events (which includes MapNotify) -- listen only for substructure change events (which includes MapNotify)
liftIO $ allocaSetWindowAttributes $ \a -> do allocaSetWindowAttributes $ \a -> do
set_event_mask a substructureNotifyMask set_event_mask a substructureNotifyMask
changeWindowAttributes dpy root cWEventMask a changeWindowAttributes dpy root cWEventMask a
withRunInIO $ \runIO -> do let c = WConf { display = dpy, dynWorkspaces = dws }
void $ allocaXEvent $ runIO . withEvents dpy _ <- allocaXEvent $ \e ->
where runW c $ forever $ handle curPIDs =<< io (nextEvent dpy e >> getEvent e)
wrapEnv dpy ps x = return ()
WEnv
{ wDisplay = dpy
, wDynWorkspaces = dws
, wCurPIDs = ps
, wXEnv = x
}
withEvents dpy e = do
ps <- newMVar S.empty
mapRIO (wrapEnv dpy ps) $ do
forever $
handleEvent =<< io (nextEvent dpy e >> getEvent e)
handleEvent :: Event -> WIO () handle :: WatchedPIDs -> Event -> W ()
-- | assume this fires at least once when a new window is created (also could -- | assume this fires at least once when a new window is created (also could
-- use CreateNotify but that is really noisy) -- use CreateNotify but that is really noisy)
handleEvent MapNotifyEvent {ev_window = w} = do handle curPIDs MapNotifyEvent { ev_window = w } = do
dpy <- asks wDisplay dpy <- asks display
hint <- io $ getClassHint dpy w hint <- io $ getClassHint dpy w
dws <- asks wDynWorkspaces dws <- asks dynWorkspaces
let tag = let m = M.fromList $ fmap (\DynWorkspace { dwTag = t, dwClass = c } -> (c, t)) dws
M.lookup (resClass hint) $ let tag = M.lookup (resClass hint) m
M.fromList $ io $ forM_ tag $ \t -> do
fmap (\DynWorkspace {dwTag = t, dwClass = c} -> (c, t)) dws a <- internAtom dpy "_NET_WM_PID" False
forM_ tag $ \t -> do pid <- getWindowProperty32 dpy a w
a <- io $ internAtom dpy "_NET_WM_PID" False
pid <- io $ getWindowProperty32 dpy a w
case pid of case pid of
-- ASSUMPTION windows will only have one PID at one time -- ASSUMPTION windows will only have one PID at one time
Just [p] -> let p' = fromIntegral p in void $ async $ withUniquePid p' t Just [p] -> let p' = fromIntegral p
_ -> return () in void $ forkIO $ withUniquePid curPIDs p' $ waitAndKill t p'
handleEvent _ = return () _ -> return ()
withUniquePid :: Pid -> String -> WIO () handle _ _ = return ()
withUniquePid pid tag = do
ps <- asks wCurPIDs waitAndKill :: String -> Pid -> IO ()
pids <- readMVar ps waitAndKill tag pid = waitUntilExit pid >> sendXMsg Workspace tag
unless (pid `elem` pids)
$ bracket_ withUniquePid :: WatchedPIDs -> Pid -> IO () -> IO ()
(modifyMVar_ ps (return . S.insert pid)) withUniquePid curPIDs pid f = do
(modifyMVar_ ps (return . S.delete pid)) pids <- readMVar curPIDs
$ do unless (pid `elem` pids) $ do
logInfo $ "waiting for pid " <> pid_ <> " to exit on workspace " <> tag_ modifyMVar_ curPIDs (return . (pid:))
waitUntilExit pid f
logInfo $ "pid " <> pid_ <> " exited on workspace " <> tag_ modifyMVar_ curPIDs (return . filter (/=pid))
liftIO $ sendXMsg Workspace tag
where
pid_ = "'" <> displayShow pid <> "'"
tag_ = "'" <> displayBytesUtf8 (BC.pack tag) <> "'"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Launching apps -- | Launching apps
-- When launching apps on dymamic workspaces, first check if they are running -- When launching apps on dymamic workspaces, first check if they are running
-- and launch if not, then switch to their workspace -- and launch if not, then switch to their workspace
wsOccupied :: Eq a1 => a1 -> W.StackSet a1 l a2 sid sd -> Bool wsOccupied :: Eq a1 => a1 -> W.StackSet a1 l a2 sid sd -> Bool
wsOccupied tag ws = wsOccupied tag ws = elem tag $ map W.tag $ filter (isJust . W.stack)
elem tag $ -- list of all workspaces with windows on them
map W.tag $ -- TODO is there not a better way to do this?
filter (isJust . W.stack) $ W.workspace (W.current ws) : W.hidden ws ++ map W.workspace (W.visible ws)
-- list of all workspaces with windows on them
-- TODO is there not a better way to do this?
$
W.workspace (W.current ws) : W.hidden ws ++ map W.workspace (W.visible ws)
spawnOrSwitch :: WorkspaceId -> X () -> X () spawnOrSwitch :: WorkspaceId -> X () -> X ()
spawnOrSwitch tag cmd = do spawnOrSwitch tag cmd = do
@ -184,7 +171,7 @@ spawnOrSwitch tag cmd = do
if occupied then windows $ W.view tag else cmd if occupied then windows $ W.view tag else cmd
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Managehook -- | Managehook
-- Move windows to new workspace if they are part of a dynamic workspace -- Move windows to new workspace if they are part of a dynamic workspace
-- shamelessly ripped off from appendWorkspace (this analogue doesn't exist) -- shamelessly ripped off from appendWorkspace (this analogue doesn't exist)
@ -205,31 +192,29 @@ appendViewShift tag = liftX (appendWorkspace tag) >> viewShift tag
-- TODO surprisingly this doesn't exist? We shouldn't need to TBH -- TODO surprisingly this doesn't exist? We shouldn't need to TBH
doSink :: ManageHook doSink :: ManageHook
doSink = doF $ \s -> case W.stack $ W.workspace $ W.current s of doSink = doF $ \s -> case W.stack $ W.workspace $ W.current s of
Just s' -> W.sink (W.focus s') s Just s' -> W.sink (W.focus s') s
Nothing -> s Nothing -> s
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Eventhook -- | Eventhook
-- When an app is closed, this will respond the event that is sent in the main -- When an app is closed, this will respond the event that is sent in the main
-- XMonad thread -- XMonad thread
removeDynamicWorkspace :: WorkspaceId -> X () removeDynamicWorkspace :: WorkspaceId -> X ()
removeDynamicWorkspace target = windows removeIfEmpty removeDynamicWorkspace target = windows removeIfEmpty
where where
-- remove workspace if it is empty and if there are hidden workspaces -- remove workspace if it is empty and if there are hidden workspaces
removeIfEmpty s@W.StackSet {W.visible = vis, W.hidden = hall@(h : hs)} removeIfEmpty s@W.StackSet { W.visible = vis, W.hidden = hall@(h:hs) }
-- if hidden, delete from hidden -- if hidden, delete from hidden
| Just x <- find isEmptyTarget hall = | Just x <- find isEmptyTarget hall
s {W.hidden = deleteBy (eq W.tag) x hall} = s { W.hidden = deleteBy (eq W.tag) x hall }
-- if visible, delete from visible and move first hidden to its place -- if visible, delete from visible and move first hidden to its place
| Just x <- find (isEmptyTarget . W.workspace) vis = | Just x <- find (isEmptyTarget . W.workspace) vis
s = s { W.visible = x { W.workspace = h } : deleteBy (eq W.screen) x vis
{ W.visible = x {W.workspace = h} : deleteBy (eq W.screen) x vis , W.hidden = hs }
, W.hidden = hs
}
-- if current, move the first hidden workspace to the current -- if current, move the first hidden workspace to the current
| isEmptyTarget $ W.workspace $ W.current s = | isEmptyTarget $ W.workspace $ W.current s
s {W.current = (W.current s) {W.workspace = h}, W.hidden = hs} = s { W.current = (W.current s) { W.workspace = h }, W.hidden = hs }
-- otherwise do nothing -- otherwise do nothing
| otherwise = s | otherwise = s
removeIfEmpty s = s removeIfEmpty s = s

View File

@ -1,52 +1,44 @@
{-# LANGUAGE ScopedTypeVariables #-}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- VirtualBox-specific functions -- | VirtualBox-specific functions
module XMonad.Internal.Concurrent.VirtualBox module XMonad.Internal.Concurrent.VirtualBox
( vmExists ( vmExists
, vmInstanceConfig ) where
, qual
)
where
import Data.Internal.XIO import Control.Exception
import RIO hiding (try)
import RIO.Directory
import RIO.FilePath
import qualified RIO.Text as T
import Text.XML.Light
import XMonad.Internal.Shell
vmExists :: T.Text -> IO (Maybe Msg) import Data.Internal.Dependency
vmExists vm = either (Just . Msg LevelError) (const Nothing) <$> vmInstanceConfig vm
vmInstanceConfig :: T.Text -> IO (Either T.Text FilePath) import Text.XML.Light
vmInstanceConfig vmName = do
either (return . Right) findInstance =<< vmDirectory import System.Directory
import XMonad.Internal.Shell
vmExists :: String -> IO (Maybe Msg)
vmExists vm = do
d <- vmDirectory
either (return . Just . Msg Error) findVMDir d
where where
path = T.unpack vmName </> addExtension (T.unpack vmName) "vbox" findVMDir vd = do
findInstance dir = do vs <- listDirectory vd
res <- findFile [dir] path return $ if vm `elem` vs then Nothing
return $ case res of else Just $ Msg Error $ "could not find " ++ singleQuote vm
Just p -> Right p
Nothing -> Left $ T.append "could not find VM instance: " $ singleQuote vmName
vmDirectory :: IO (Either String String) vmDirectory :: IO (Either String String)
vmDirectory = do vmDirectory = do
p <- vmConfig p <- vmConfig
s <- tryIO $ readFileUtf8 p (s :: Either IOException String) <- try $ readFile p
return $ case s of return $ case s of
(Left _) -> Left "could not read VirtualBox config file" (Left _) -> Left "could not read VirtualBox config file"
(Right x) -> (Right x) -> maybe (Left "Could not parse VirtualBox config file") Right
maybe (Left "Could not parse VirtualBox config file") Right $ $ findDir =<< parseXMLDoc x
findDir =<< parseXMLDoc x
where where
findDir e = findDir e = findAttr (unqual "defaultMachineFolder")
findAttr (unqual "defaultMachineFolder") =<< findChild (qual e "SystemProperties")
=<< findChild (qual e "SystemProperties") =<< findChild (qual e "Global") e
=<< findChild (qual e "Global") e qual e n = (elName e) { qName = n }
qual :: Element -> String -> QName
qual e n = (elName e) {qName = n}
vmConfig :: IO FilePath vmConfig :: IO FilePath
vmConfig = getXdgDirectory XdgConfig "VirtualBox/VirtualBox.xml" vmConfig = getXdgDirectory XdgConfig "VirtualBox/VirtualBox.xml"

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus module for Clevo Keyboard control -- | DBus module for Clevo Keyboard control
module XMonad.Internal.DBus.Brightness.ClevoKeyboard module XMonad.Internal.DBus.Brightness.ClevoKeyboard
( callGetBrightnessCK ( callGetBrightnessCK
@ -8,20 +8,24 @@ module XMonad.Internal.DBus.Brightness.ClevoKeyboard
, clevoKeyboardControls , clevoKeyboardControls
, clevoKeyboardSignalDep , clevoKeyboardSignalDep
, blPath , blPath
) ) where
where
import DBus import Control.Monad (when)
import Data.Internal.DBus
import Data.Internal.XIO import Data.Int (Int32)
import RIO import Data.Internal.DBus
import RIO.FilePath import Data.Internal.Dependency
import XMonad.Internal.DBus.Brightness.Common
import XMonad.Internal.IO import DBus
import System.FilePath.Posix
import XMonad.Internal.DBus.Brightness.Common
import XMonad.Internal.IO
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Low level sysfs functions -- | Low level sysfs functions
--
type Brightness = Float type Brightness = Float
type RawBrightness = Int32 type RawBrightness = Int32
@ -44,41 +48,41 @@ backlightDir = "/sys/devices/platform/tuxedo_keyboard"
stateFile :: FilePath stateFile :: FilePath
stateFile = backlightDir </> "state" stateFile = backlightDir </> "state"
stateChange :: MonadUnliftIO m => Bool -> m () stateChange :: Bool -> IO ()
stateChange = writeBool stateFile stateChange = writeBool stateFile
stateOn :: MonadUnliftIO m => m () stateOn :: IO ()
stateOn = stateChange True stateOn = stateChange True
stateOff :: MonadUnliftIO m => m () stateOff :: IO ()
stateOff = stateChange False stateOff = stateChange False
brightnessFile :: FilePath brightnessFile :: FilePath
brightnessFile = backlightDir </> "brightness" brightnessFile = backlightDir </> "brightness"
getBrightness :: MonadUnliftIO m => RawBounds -> m Brightness getBrightness :: RawBounds -> IO Brightness
getBrightness bounds = readPercent bounds brightnessFile getBrightness bounds = readPercent bounds brightnessFile
minBrightness :: MonadUnliftIO m => RawBounds -> m Brightness minBrightness :: RawBounds -> IO Brightness
minBrightness bounds = do minBrightness bounds = do
b <- writePercentMin bounds brightnessFile b <- writePercentMin bounds brightnessFile
stateOff stateOff
return b return b
maxBrightness :: MonadUnliftIO m => RawBounds -> m Brightness maxBrightness :: RawBounds -> IO Brightness
maxBrightness bounds = stateOn >> writePercentMax bounds brightnessFile maxBrightness bounds = stateOn >> writePercentMax bounds brightnessFile
incBrightness :: MonadUnliftIO m => RawBounds -> m Brightness incBrightness :: RawBounds -> IO Brightness
incBrightness bounds = stateOn >> incPercent steps brightnessFile bounds incBrightness bounds = stateOn >> incPercent steps brightnessFile bounds
decBrightness :: MonadUnliftIO m => RawBounds -> m Brightness decBrightness :: RawBounds -> IO Brightness
decBrightness bounds = do decBrightness bounds = do
b <- decPercent steps brightnessFile bounds b <- decPercent steps brightnessFile bounds
when (b == 0) stateOff when (b == 0) stateOff
return b return b
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus interface -- | DBus interface
blPath :: ObjectPath blPath :: ObjectPath
blPath = objectPath_ "/clevo_keyboard" blPath = objectPath_ "/clevo_keyboard"
@ -86,23 +90,22 @@ blPath = objectPath_ "/clevo_keyboard"
interface :: InterfaceName interface :: InterfaceName
interface = interfaceName_ "org.xmonad.Brightness" interface = interfaceName_ "org.xmonad.Brightness"
clevoKeyboardConfig :: MonadUnliftIO m => BrightnessConfig m RawBrightness Brightness clevoKeyboardConfig :: BrightnessConfig RawBrightness Brightness
clevoKeyboardConfig = clevoKeyboardConfig = BrightnessConfig
BrightnessConfig { bcMin = minBrightness
{ bcMin = minBrightness , bcMax = maxBrightness
, bcMax = maxBrightness , bcInc = incBrightness
, bcInc = incBrightness , bcDec = decBrightness
, bcDec = decBrightness , bcGet = getBrightness
, bcGet = getBrightness , bcGetMax = return maxRawBrightness
, bcGetMax = return maxRawBrightness , bcMinRaw = minRawBrightness
, bcMinRaw = minRawBrightness , bcPath = blPath
, bcPath = blPath , bcInterface = interface
, bcInterface = interface , bcName = "Clevo keyboard"
, bcName = "Clevo keyboard" }
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Exported haskell API -- | Exported haskell API
stateFileDep :: IODependency_ stateFileDep :: IODependency_
stateFileDep = pathRW stateFile [Package AUR "tuxedo-keyboard"] stateFileDep = pathRW stateFile [Package AUR "tuxedo-keyboard"]
@ -111,38 +114,17 @@ brightnessFileDep :: IODependency_
brightnessFileDep = pathR brightnessFile [Package AUR "tuxedo-keyboard"] brightnessFileDep = pathR brightnessFile [Package AUR "tuxedo-keyboard"]
clevoKeyboardSignalDep :: DBusDependency_ SesClient clevoKeyboardSignalDep :: DBusDependency_ SesClient
clevoKeyboardSignalDep = clevoKeyboardSignalDep = signalDep clevoKeyboardConfig
-- TODO do I need to get rid of the IO here?
signalDep (clevoKeyboardConfig :: BrightnessConfig IO RawBrightness Brightness)
exportClevoKeyboard exportClevoKeyboard :: Maybe SesClient -> SometimesIO
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) exportClevoKeyboard = brightnessExporter xpfClevoBacklight []
=> Maybe NamedSesConnection [stateFileDep, brightnessFileDep] clevoKeyboardConfig
-> Sometimes (m (), m ())
exportClevoKeyboard =
brightnessExporter
xpfClevoBacklight
[]
[stateFileDep, brightnessFileDep]
clevoKeyboardConfig
clevoKeyboardControls clevoKeyboardControls :: Maybe SesClient -> BrightnessControls
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m)
=> Maybe NamedSesConnection
-> BrightnessControls m
clevoKeyboardControls = brightnessControls xpfClevoBacklight clevoKeyboardConfig clevoKeyboardControls = brightnessControls xpfClevoBacklight clevoKeyboardConfig
callGetBrightnessCK callGetBrightnessCK :: SesClient -> IO (Maybe Brightness)
:: (SafeClient c, HasClient env, MonadReader (env c) m, MonadUnliftIO m)
=> m (Maybe Brightness)
callGetBrightnessCK = callGetBrightness clevoKeyboardConfig callGetBrightnessCK = callGetBrightness clevoKeyboardConfig
matchSignalCK matchSignalCK :: (Maybe Brightness -> IO ()) -> SesClient -> IO ()
:: ( SafeClient c
, HasClient env
, MonadReader (env c) m
, MonadUnliftIO m
)
=> (Maybe Brightness -> m ())
-> m ()
matchSignalCK = matchSignal clevoKeyboardConfig matchSignalCK = matchSignal clevoKeyboardConfig

View File

@ -1,194 +1,154 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus module for DBus brightness controls -- | DBus module for DBus brightness controls
module XMonad.Internal.DBus.Brightness.Common module XMonad.Internal.DBus.Brightness.Common
( BrightnessConfig (..) ( BrightnessConfig(..)
, BrightnessControls (..) , BrightnessControls(..)
, brightnessControls , brightnessControls
, brightnessExporter , brightnessExporter
, callGetBrightness , callGetBrightness
, matchSignal , matchSignal
, signalDep , signalDep
) ) where
where
import DBus import Control.Monad (void)
import DBus.Client
import qualified DBus.Introspection as I import Data.Int (Int32)
import Data.Internal.DBus import Data.Internal.DBus
import Data.Internal.XIO import Data.Internal.Dependency
import RIO
import qualified RIO.Text as T import DBus
import XMonad.Internal.DBus.Common import DBus.Client
import qualified DBus.Introspection as I
import XMonad.Core (io)
import XMonad.Internal.DBus.Common
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- External API -- | External API
-- --
-- Define four methods to increase, decrease, maximize, or minimize the -- Define four methods to increase, decrease, maximize, or minimize the
-- brightness. These methods will all return the current brightness as a 32-bit -- brightness. These methods will all return the current brightness as a 32-bit
-- integer and emit a signal with the same brightness value. Additionally, there -- integer and emit a signal with the same brightness value. Additionally, there
-- is one method to get the current brightness. -- is one method to get the current brightness.
data BrightnessConfig m a b = BrightnessConfig data BrightnessConfig a b = BrightnessConfig
{ bcMin :: (a, a) -> m b { bcMin :: (a, a) -> IO b
, bcMax :: (a, a) -> m b , bcMax :: (a, a) -> IO b
, bcDec :: (a, a) -> m b , bcDec :: (a, a) -> IO b
, bcInc :: (a, a) -> m b , bcInc :: (a, a) -> IO b
, bcGet :: (a, a) -> m b , bcGet :: (a, a) -> IO b
, bcMinRaw :: a , bcMinRaw :: a
, bcGetMax :: m a , bcGetMax :: IO a
, bcPath :: ObjectPath , bcPath :: ObjectPath
, bcInterface :: InterfaceName , bcInterface :: InterfaceName
, bcName :: T.Text , bcName :: String
} }
data BrightnessControls m = BrightnessControls data BrightnessControls = BrightnessControls
{ bctlMax :: Sometimes (m ()) { bctlMax :: SometimesIO
, bctlMin :: Sometimes (m ()) , bctlMin :: SometimesIO
, bctlInc :: Sometimes (m ()) , bctlInc :: SometimesIO
, bctlDec :: Sometimes (m ()) , bctlDec :: SometimesIO
} }
brightnessControls brightnessControls :: XPQuery -> BrightnessConfig a b -> Maybe SesClient
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m) -> BrightnessControls
=> XPQuery
-> BrightnessConfig m a b
-> Maybe NamedSesConnection
-> BrightnessControls m
brightnessControls q bc cl = brightnessControls q bc cl =
BrightnessControls BrightnessControls
{ bctlMax = cb "max brightness" memMax { bctlMax = cb "max brightness" memMax
, bctlMin = cb "min brightness" memMin , bctlMin = cb "min brightness" memMin
, bctlInc = cb "increase brightness" memInc , bctlInc = cb "increase brightness" memInc
, bctlDec = cb "decrease brightness" memDec , bctlDec = cb "decrease brightness" memDec
} }
where where
cb = callBacklight q cl bc cb = callBacklight q cl bc
callGetBrightness callGetBrightness :: (SafeClient c, Num n) => BrightnessConfig a b -> c
:: ( HasClient env -> IO (Maybe n)
, MonadReader (env c) m callGetBrightness BrightnessConfig { bcPath = p, bcInterface = i } client =
, MonadUnliftIO m
, SafeClient c
, Num n
)
=> BrightnessConfig m a b
-> m (Maybe n)
callGetBrightness BrightnessConfig {bcPath = p, bcInterface = i} =
either (const Nothing) bodyGetBrightness either (const Nothing) bodyGetBrightness
<$> callMethod xmonadSesBusName p i memGet <$> callMethod client xmonadBusName p i memGet
signalDep :: BrightnessConfig m a b -> DBusDependency_ c signalDep :: BrightnessConfig a b -> DBusDependency_ SesClient
signalDep BrightnessConfig {bcPath = p, bcInterface = i} = signalDep BrightnessConfig { bcPath = p, bcInterface = i } =
Endpoint [] xmonadSesBusName p i $ Signal_ memCur Endpoint [] xmonadBusName p i $ Signal_ memCur
matchSignal matchSignal :: (SafeClient c, Num n) => BrightnessConfig a b
:: ( HasClient env -> (Maybe n-> IO ()) -> c -> IO ()
, MonadReader (env c) m matchSignal BrightnessConfig { bcPath = p, bcInterface = i } cb =
, MonadUnliftIO m void . addMatchCallback brMatcher (cb . bodyGetBrightness)
, SafeClient c
, Num n
)
=> BrightnessConfig m a b
-> (Maybe n -> m ())
-> m ()
matchSignal BrightnessConfig {bcPath = p, bcInterface = i} cb =
void $ addMatchCallback brMatcher (cb . bodyGetBrightness)
where where
-- TODO add busname to this -- TODO add busname to this
brMatcher = brMatcher = matchAny
matchAny { matchPath = Just p
{ matchPath = Just p , matchInterface = Just i
, matchInterface = Just i , matchMember = Just memCur
, matchMember = Just memCur }
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Internal DBus Crap -- | Internal DBus Crap
brightnessExporter brightnessExporter :: RealFrac b => XPQuery -> [Fulfillment] -> [IODependency_]
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m, RealFrac b) -> BrightnessConfig a b -> Maybe SesClient -> SometimesIO
=> XPQuery brightnessExporter q ful deps bc@BrightnessConfig { bcName = n } cl =
-> [Fulfillment] Sometimes (n ++ " DBus Interface") q [Subfeature root "exporter"]
-> [IODependency_]
-> BrightnessConfig m a b
-> Maybe NamedSesConnection
-> Sometimes (m (), m ())
brightnessExporter q ful deps bc@BrightnessConfig {bcName = n} cl =
Sometimes (T.append n " DBus Interface") q [Subfeature root "exporter"]
where where
root = DBusRoot_ (exportBrightnessControlsInner bc) tree cl root = DBusRoot_ (exportBrightnessControls' bc) tree cl
tree = listToAnds (Bus ful xmonadSesBusName) $ fmap DBusIO deps tree = listToAnds (Bus ful xmonadBusName) $ fmap DBusIO deps
exportBrightnessControlsInner exportBrightnessControls' :: RealFrac b => BrightnessConfig a b -> SesClient -> IO ()
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m, RealFrac b) exportBrightnessControls' bc cl = do
=> BrightnessConfig m a b let ses = toClient cl
-> NamedSesConnection maxval <- bcGetMax bc -- assume the max value will never change
-> (m (), m ()) let bounds = (bcMinRaw bc, maxval)
exportBrightnessControlsInner bc = cmd let autoMethod' m f = autoMethod m $ emitBrightness bc ses =<< f bc bounds
let funget = bcGet bc
export ses (bcPath bc) defaultInterface
{ interfaceName = bcInterface bc
, interfaceMethods =
[ autoMethod' memMax bcMax
, autoMethod' memMin bcMin
, autoMethod' memInc bcInc
, autoMethod' memDec bcDec
, autoMethod memGet (round <$> funget bounds :: IO Int32)
]
, interfaceSignals = [sig]
}
where where
cmd = exportPair (bcPath bc) $ \cl_ -> do sig = I.Signal
-- assume the max value will never change { I.signalName = memCur
bounds <- (bcMinRaw bc,) <$> bcGetMax bc , I.signalArgs =
runIO <- askRunInIO [
let autoMethod' m f = autoMethod m $ runIO $ do I.SignalArg
val <- f bc bounds { I.signalArgName = "brightness"
emitBrightness bc cl_ val , I.signalArgType = TypeInt32
funget <- toIO $ bcGet bc bounds
return $
defaultInterface
{ interfaceName = bcInterface bc
, interfaceMethods =
[ autoMethod' memMax bcMax
, autoMethod' memMin bcMin
, autoMethod' memInc bcInc
, autoMethod' memDec bcDec
, autoMethod memGet (round <$> funget :: IO Int32)
]
, interfaceSignals = [sig]
} }
sig = ]
I.Signal }
{ I.signalName = memCur
, I.signalArgs =
[ I.SignalArg
{ I.signalArgName = "brightness"
, I.signalArgType = TypeInt32
}
]
}
emitBrightness emitBrightness :: RealFrac b => BrightnessConfig a b -> Client -> b -> IO ()
:: (MonadUnliftIO m, RealFrac b) emitBrightness BrightnessConfig{ bcPath = p, bcInterface = i } client cur =
=> BrightnessConfig m a b emit client $ sig { signalBody = [toVariant (round cur :: Int32)] }
-> Client
-> b
-> m ()
emitBrightness BrightnessConfig {bcPath = p, bcInterface = i} client cur =
liftIO $ emit client $ sig {signalBody = [toVariant (round cur :: Int32)]}
where where
sig = signal p i memCur sig = signal p i memCur
callBacklight callBacklight :: XPQuery -> Maybe SesClient -> BrightnessConfig a b -> String
:: (MonadReader env m, HasClient (DBusEnv env), MonadUnliftIO m) -> MemberName -> SometimesIO
=> XPQuery callBacklight q cl BrightnessConfig { bcPath = p
-> Maybe NamedSesConnection , bcInterface = i
-> BrightnessConfig m a b , bcName = n } controlName m =
-> T.Text Sometimes (unwords [n, controlName]) q [Subfeature root "method call"]
-> MemberName
-> Sometimes (m ())
callBacklight q cl BrightnessConfig {bcPath = p, bcInterface = i, bcName = n} controlName m =
Sometimes (T.unwords [n, controlName]) q [Subfeature root "method call"]
where where
root = DBusRoot_ cmd (Only_ $ Endpoint [] xmonadSesBusName p i $ Method_ m) cl root = DBusRoot_ cmd (Only_ $ Endpoint [] xmonadBusName p i $ Method_ m) cl
cmd c = void $ withDIO c $ callMethod xmonadSesBusName p i m cmd c = io $ void $ callMethod c xmonadBusName p i m
bodyGetBrightness :: Num a => [Variant] -> Maybe a bodyGetBrightness :: Num a => [Variant] -> Maybe a
bodyGetBrightness [b] = fromIntegral <$> (fromVariant b :: Maybe Int32) bodyGetBrightness [b] = fromIntegral <$> (fromVariant b :: Maybe Int32)
bodyGetBrightness _ = Nothing bodyGetBrightness _ = Nothing
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus Members -- | DBus Members
memCur :: MemberName memCur :: MemberName
memCur = memberName_ "CurrentBrightness" memCur = memberName_ "CurrentBrightness"

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus module for Intel Backlight control -- | DBus module for Intel Backlight control
module XMonad.Internal.DBus.Brightness.IntelBacklight module XMonad.Internal.DBus.Brightness.IntelBacklight
( callGetBrightnessIB ( callGetBrightnessIB
@ -8,20 +8,22 @@ module XMonad.Internal.DBus.Brightness.IntelBacklight
, intelBacklightControls , intelBacklightControls
, intelBacklightSignalDep , intelBacklightSignalDep
, blPath , blPath
) ) where
where
import DBus import Data.Int (Int32)
import Data.Internal.DBus import Data.Internal.DBus
import Data.Internal.XIO import Data.Internal.Dependency
import RIO
import RIO.FilePath import DBus
import XMonad.Internal.DBus.Brightness.Common
import XMonad.Internal.IO import System.FilePath.Posix
import XMonad.Internal.DBus.Brightness.Common
import XMonad.Internal.IO
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Low level sysfs functions -- | Low level sysfs functions
--
type Brightness = Float type Brightness = Float
type RawBrightness = Int32 type RawBrightness = Int32
@ -43,26 +45,26 @@ maxFile = backlightDir </> "max_brightness"
curFile :: FilePath curFile :: FilePath
curFile = backlightDir </> "brightness" curFile = backlightDir </> "brightness"
getMaxRawBrightness :: MonadUnliftIO m => m RawBrightness getMaxRawBrightness :: IO RawBrightness
getMaxRawBrightness = readInt maxFile getMaxRawBrightness = readInt maxFile
getBrightness :: MonadUnliftIO m => RawBounds -> m Brightness getBrightness :: RawBounds -> IO Brightness
getBrightness bounds = readPercent bounds curFile getBrightness bounds = readPercent bounds curFile
minBrightness :: MonadUnliftIO m => RawBounds -> m Brightness minBrightness :: RawBounds -> IO Brightness
minBrightness bounds = writePercentMin bounds curFile minBrightness bounds = writePercentMin bounds curFile
maxBrightness :: MonadUnliftIO m => RawBounds -> m Brightness maxBrightness :: RawBounds -> IO Brightness
maxBrightness bounds = writePercentMax bounds curFile maxBrightness bounds = writePercentMax bounds curFile
incBrightness :: MonadUnliftIO m => RawBounds -> m Brightness incBrightness :: RawBounds -> IO Brightness
incBrightness = incPercent steps curFile incBrightness = incPercent steps curFile
decBrightness :: MonadUnliftIO m => RawBounds -> m Brightness decBrightness :: RawBounds -> IO Brightness
decBrightness = decPercent steps curFile decBrightness = decPercent steps curFile
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus interface -- | DBus interface
blPath :: ObjectPath blPath :: ObjectPath
blPath = objectPath_ "/intelbacklight" blPath = objectPath_ "/intelbacklight"
@ -70,25 +72,22 @@ blPath = objectPath_ "/intelbacklight"
interface :: InterfaceName interface :: InterfaceName
interface = interfaceName_ "org.xmonad.Brightness" interface = interfaceName_ "org.xmonad.Brightness"
intelBacklightConfig intelBacklightConfig :: BrightnessConfig RawBrightness Brightness
:: MonadUnliftIO m intelBacklightConfig = BrightnessConfig
=> BrightnessConfig m RawBrightness Brightness { bcMin = minBrightness
intelBacklightConfig = , bcMax = maxBrightness
BrightnessConfig , bcInc = incBrightness
{ bcMin = minBrightness , bcDec = decBrightness
, bcMax = maxBrightness , bcGet = getBrightness
, bcInc = incBrightness , bcGetMax = getMaxRawBrightness
, bcDec = decBrightness , bcMinRaw = minRawBrightness
, bcGet = getBrightness , bcPath = blPath
, bcGetMax = getMaxRawBrightness , bcInterface = interface
, bcMinRaw = minRawBrightness , bcName = "Intel backlight"
, bcPath = blPath }
, bcInterface = interface
, bcName = "Intel backlight"
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Exported haskell API -- | Exported haskell API
curFileDep :: IODependency_ curFileDep :: IODependency_
curFileDep = pathRW curFile [] curFileDep = pathRW curFile []
@ -97,38 +96,17 @@ maxFileDep :: IODependency_
maxFileDep = pathR maxFile [] maxFileDep = pathR maxFile []
intelBacklightSignalDep :: DBusDependency_ SesClient intelBacklightSignalDep :: DBusDependency_ SesClient
intelBacklightSignalDep = intelBacklightSignalDep = signalDep intelBacklightConfig
-- TODO do I need to get rid of the IO here?
signalDep (intelBacklightConfig :: BrightnessConfig IO RawBrightness Brightness)
exportIntelBacklight exportIntelBacklight :: Maybe SesClient -> SometimesIO
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) exportIntelBacklight = brightnessExporter xpfIntelBacklight []
=> Maybe NamedSesConnection [curFileDep, maxFileDep] intelBacklightConfig
-> Sometimes (m (), m ())
exportIntelBacklight =
brightnessExporter
xpfIntelBacklight
[]
[curFileDep, maxFileDep]
intelBacklightConfig
intelBacklightControls intelBacklightControls :: Maybe SesClient -> BrightnessControls
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m)
=> Maybe NamedSesConnection
-> BrightnessControls m
intelBacklightControls = brightnessControls xpfIntelBacklight intelBacklightConfig intelBacklightControls = brightnessControls xpfIntelBacklight intelBacklightConfig
callGetBrightnessIB callGetBrightnessIB :: SesClient -> IO (Maybe Brightness)
:: (SafeClient c, HasClient env, MonadReader (env c) m, MonadUnliftIO m)
=> m (Maybe Brightness)
callGetBrightnessIB = callGetBrightness intelBacklightConfig callGetBrightnessIB = callGetBrightness intelBacklightConfig
matchSignalIB matchSignalIB :: (Maybe Brightness -> IO ()) -> SesClient -> IO ()
:: ( SafeClient c
, HasClient env
, MonadReader (env c) m
, MonadUnliftIO m
)
=> (Maybe Brightness -> m ())
-> m ()
matchSignalIB = matchSignal intelBacklightConfig matchSignalIB = matchSignal intelBacklightConfig

View File

@ -1,23 +1,18 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- High-level interface for managing XMonad's DBus -- | High-level interface for managing XMonad's DBus
module XMonad.Internal.DBus.Common module XMonad.Internal.DBus.Common
( xmonadSesBusName ( xmonadBusName
, xmonadSysBusName
, btBus , btBus
, notifyBus , notifyBus
, notifyPath , notifyPath
, networkManagerBus , networkManagerBus
) ) where
where
import DBus import DBus
xmonadSesBusName :: BusName xmonadBusName :: BusName
xmonadSesBusName = busName_ "org.xmonad.session" xmonadBusName = busName_ "org.xmonad"
xmonadSysBusName :: BusName
xmonadSysBusName = busName_ "org.xmonad.system"
btBus :: BusName btBus :: BusName
btBus = busName_ "org.bluez" btBus = busName_ "org.bluez"
@ -30,3 +25,4 @@ notifyPath = objectPath_ "/org/freedesktop/Notifications"
networkManagerBus :: BusName networkManagerBus :: BusName
networkManagerBus = busName_ "org.freedesktop.NetworkManager" networkManagerBus = busName_ "org.freedesktop.NetworkManager"

View File

@ -1,188 +1,83 @@
{-# LANGUAGE ScopedTypeVariables #-}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- High-level interface for managing XMonad's DBus -- | High-level interface for managing XMonad's DBus
module XMonad.Internal.DBus.Control module XMonad.Internal.DBus.Control
( Client ( Client
, DBusState (..) , DBusState(..)
, withDBusInterfaces
, withDBusX
, withDBusX_
, withDBus
, withDBus_
, connectDBus , connectDBus
, connectDBusX
, disconnectDBus , disconnectDBus
-- , disconnectDBusX , disconnectDBusX
, getDBusClient , getDBusClient
, withDBusClient , withDBusClient
, withDBusClient_ , withDBusClient_
, disconnect , disconnect
, dbusExporters , dbusExporters
) ) where
where
import DBus import Control.Monad
import DBus.Client
import Data.Internal.DBus import Data.Internal.DBus
import Data.Internal.XIO import Data.Internal.Dependency
import RIO
import XMonad.Internal.DBus.Brightness.ClevoKeyboard import DBus
import XMonad.Internal.DBus.Brightness.IntelBacklight import DBus.Client
import XMonad.Internal.DBus.Common
import XMonad.Internal.DBus.Screensaver import XMonad.Internal.DBus.Brightness.ClevoKeyboard
import XMonad.Internal.DBus.Brightness.IntelBacklight
import XMonad.Internal.DBus.Common
import XMonad.Internal.DBus.Screensaver
-- | Current connections to the DBus (session and system buses) -- | Current connections to the DBus (session and system buses)
data DBusState = DBusState data DBusState = DBusState
{ dbSesClient :: Maybe NamedSesConnection { dbSesClient :: Maybe SesClient
, dbSysClient :: Maybe NamedSysConnection , dbSysClient :: Maybe SysClient
} }
withDBusX_
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> (DBusState -> m a)
-> m ()
withDBusX_ = void . withDBusX
withDBusX
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> (DBusState -> m a)
-> m a
withDBusX = withDBus (Just xmonadSesBusName) Nothing
withDBus_
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> Maybe BusName
-> Maybe BusName
-> (DBusState -> m a)
-> m ()
withDBus_ sesname sysname = void . withDBus sesname sysname
withDBus
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> Maybe BusName
-> Maybe BusName
-> (DBusState -> m a)
-> m a
withDBus sesname sysname = bracket (connectDBus sesname sysname) disconnectDBus
-- | Connect to the DBus -- | Connect to the DBus
connectDBus connectDBus :: IO DBusState
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) connectDBus = do
=> Maybe BusName ses <- getDBusClient
-> Maybe BusName sys <- getDBusClient
-> m DBusState return DBusState { dbSesClient = ses, dbSysClient = sys }
connectDBus sesname sysname = do
ses <- getDBusClient sesname
sys <- getDBusClient sysname
return DBusState {dbSesClient = ses, dbSysClient = sys}
-- | Disconnect from the DBus -- | Disconnect from the DBus
disconnectDBus disconnectDBus :: DBusState -> IO ()
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> DBusState
-> m ()
disconnectDBus db = disc dbSesClient >> disc dbSysClient disconnectDBus db = disc dbSesClient >> disc dbSysClient
where where
disc
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m, SafeClient c)
=> (DBusState -> Maybe (NamedConnection c))
-> m ()
disc f = maybe (return ()) disconnectDBusClient $ f db disc f = maybe (return ()) disconnectDBusClient $ f db
-- -- | Connect to the DBus and request the XMonad name -- | Connect to the DBus and request the XMonad name
-- connectDBusX connectDBusX :: IO DBusState
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) connectDBusX = do
-- => m DBusState db <- connectDBus
-- connectDBusX = do forM_ (dbSesClient db) requestXMonadName
-- db <- connectDBus return db
-- requestXMonadName2 db
-- return db
-- -- | Disconnect from DBus and release the XMonad name -- | Disconnect from DBus and release the XMonad name
-- disconnectDBusX disconnectDBusX :: DBusState -> IO ()
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m) disconnectDBusX db = do
-- => DBusState forM_ (dbSesClient db) releaseXMonadName
-- -> m () disconnectDBus db
-- disconnectDBusX db = do
-- forM_ (dbSesClient db) releaseBusName
-- forM_ (dbSysClient db) releaseBusName
-- disconnectDBus db
-- requestXMonadName2
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
-- => DBusState
-- -> m ()
-- requestXMonadName2 db = do
-- forM_ (dbSesClient db) requestXMonadName
-- forM_ (dbSysClient db) requestXMonadName
withDBusInterfaces
:: DBusState
-> [Maybe NamedSesConnection -> Sometimes (XIO (), XIO ())]
-> ([XIO ()] -> XIO a)
-> XIO a
withDBusInterfaces db interfaces = bracket up sequence
where
up = do
pairs <- catMaybes <$> mapM (\f -> evalSometimes $ f $ dbSesClient db) interfaces
mapM_ fst pairs
return $ snd <$> pairs
-- | All exporter features to be assigned to the DBus -- | All exporter features to be assigned to the DBus
dbusExporters dbusExporters :: [Maybe SesClient -> SometimesIO]
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> [Maybe NamedSesConnection -> Sometimes (m (), m ())]
dbusExporters = [exportScreensaver, exportIntelBacklight, exportClevoKeyboard] dbusExporters = [exportScreensaver, exportIntelBacklight, exportClevoKeyboard]
-- releaseXMonadName releaseXMonadName :: SesClient -> IO ()
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m) releaseXMonadName ses = void $ releaseName (toClient ses) xmonadBusName
-- => c
-- -> m ()
-- releaseXMonadName cl = do
-- -- TODO this might error?
-- liftIO $ void $ releaseName (toClient cl) xmonadBusName
-- logInfo "released xmonad name"
-- releaseBusName requestXMonadName :: SesClient -> IO ()
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m) requestXMonadName ses = do
-- => BusName res <- requestName (toClient ses) xmonadBusName []
-- -> c -- TODO if the client is not released on shutdown the owner will be different
-- -> m () let msg | res == NamePrimaryOwner = Nothing
-- releaseBusName n cl = do | res == NameAlreadyOwner = Just $ "this process already owns " ++ xn
-- -- TODO this might error? | res == NameInQueue
-- liftIO $ void $ releaseName (toClient cl) n || res == NameExists = Just $ "another process owns " ++ xn
-- logInfo $ "released bus name: " <> displayBusName n | otherwise = Just $ "unknown error when requesting " ++ xn
forM_ msg putStrLn
-- requestBusName where
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m) xn = "'" ++ formatBusName xmonadBusName ++ "'"
-- => BusName
-- -> c
-- -> m ()
-- requestBusName n cl = do
-- res <- try $ liftIO $ requestName (toClient cl) n []
-- case res of
-- Left e -> logError $ displayBytesUtf8 $ BC.pack $ clientErrorMessage e
-- Right r -> do
-- let msg
-- | r == NamePrimaryOwner = "registering name"
-- | r == NameAlreadyOwner = "this process already owns name"
-- | r == NameInQueue
-- || r == NameExists =
-- "another process owns name"
-- -- this should never happen
-- | otherwise = "unknown error when requesting name"
-- logInfo $ msg <> ": " <> displayBusName n
-- requestXMonadName
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m)
-- => c
-- -> m ()
-- requestXMonadName cl = do
-- res <- liftIO $ requestName (toClient cl) xmonadBusName []
-- let msg
-- | res == NamePrimaryOwner = "registering name"
-- | res == NameAlreadyOwner = "this process already owns name"
-- | res == NameInQueue
-- || res == NameExists =
-- "another process owns name"
-- | otherwise = "unknown error when requesting name"
-- logInfo $ msg <> ": " <> displayBusName xmonadBusName

View File

@ -1,19 +1,22 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Module for monitoring removable drive events -- | Module for monitoring removable drive events
-- --
-- Currently, its only purpose is to play Super Mario sounds when a drive is -- Currently, its only purpose is to play Super Mario sounds when a drive is
-- inserted or removed. Why? Because I can. -- inserted or removed. Why? Because I can.
module XMonad.Internal.DBus.Removable (runRemovableMon) where module XMonad.Internal.DBus.Removable (runRemovableMon) where
import DBus import Control.Monad
import DBus.Client
import Data.Internal.DBus import Data.Internal.DBus
import Data.Internal.XIO import Data.Internal.Dependency
import RIO import Data.Map.Strict (Map, member)
import qualified RIO.Map as M
import XMonad.Core (io) import DBus
import XMonad.Internal.Command.Desktop import DBus.Client
import XMonad.Core (io)
import XMonad.Internal.Command.Desktop
bus :: BusName bus :: BusName
bus = busName_ "org.freedesktop.UDisks2" bus = busName_ "org.freedesktop.UDisks2"
@ -46,60 +49,40 @@ driveRemovedSound :: FilePath
driveRemovedSound = "smb_pipe.wav" driveRemovedSound = "smb_pipe.wav"
ruleUdisks :: MatchRule ruleUdisks :: MatchRule
ruleUdisks = ruleUdisks = matchAny
matchAny { matchPath = Just path
{ matchPath = Just path , matchInterface = Just interface
, matchInterface = Just interface }
}
driveFlag :: String driveFlag :: String
driveFlag = "org.freedesktop.UDisks2.Drive" driveFlag = "org.freedesktop.UDisks2.Drive"
addedHasDrive :: [Variant] -> Bool addedHasDrive :: [Variant] -> Bool
addedHasDrive [_, a] = addedHasDrive [_, a] = maybe False (member driveFlag)
maybe (fromVariant a :: Maybe (Map String (Map String Variant)))
False
(M.member driveFlag)
(fromVariant a :: Maybe (Map String (Map String Variant)))
addedHasDrive _ = False addedHasDrive _ = False
removedHasDrive :: [Variant] -> Bool removedHasDrive :: [Variant] -> Bool
removedHasDrive [_, a] = removedHasDrive [_, a] = maybe False (driveFlag `elem`)
maybe (fromVariant a :: Maybe [String])
False
(driveFlag `elem`)
(fromVariant a :: Maybe [String])
removedHasDrive _ = False removedHasDrive _ = False
playSoundMaybe :: MonadUnliftIO m => FilePath -> Bool -> m () playSoundMaybe :: FilePath -> Bool -> IO ()
playSoundMaybe p b = when b $ io $ playSound p playSoundMaybe p b = when b $ io $ playSound p
-- NOTE: the udisks2 service should be already running for this module to work. -- NOTE: the udisks2 service should be already running for this module to work.
-- If it not already, we won't see any signals from the dbus until it is -- If it not already, we won't see any signals from the dbus until it is
-- started (it will work after it is started however). It seems safe to simply -- started (it will work after it is started however). It seems safe to simply
-- enable the udisks2 service at boot; however this is not default behavior. -- enable the udisks2 service at boot; however this is not default behavior.
listenDevices listenDevices :: SysClient -> IO ()
:: ( HasClient (DBusEnv env)
, MonadReader env m
, MonadUnliftIO m
)
=> NamedSysConnection
-> m ()
listenDevices cl = do listenDevices cl = do
addMatch' memAdded driveInsertedSound addedHasDrive addMatch' memAdded driveInsertedSound addedHasDrive
addMatch' memRemoved driveRemovedSound removedHasDrive addMatch' memRemoved driveRemovedSound removedHasDrive
where where
addMatch' m p f = do addMatch' m p f = void $ addMatch (toClient cl) ruleUdisks { matchMember = Just m }
let rule = ruleUdisks {matchMember = Just m} $ playSoundMaybe p . f . signalBody
void $ withDIO cl $ addMatchCallback rule (playSoundMaybe p . f)
runRemovableMon runRemovableMon :: Maybe SysClient -> SometimesIO
:: ( HasClient (DBusEnv env)
, MonadReader env m
, MonadUnliftIO m
)
=> Maybe NamedSysConnection
-> Sometimes (m ())
runRemovableMon cl = runRemovableMon cl =
sometimesDBus cl "removeable device monitor" "dbus monitor" deps listenDevices sometimesDBus cl "removeable device monitor" "dbus monitor" deps listenDevices
where where

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus module for X11 screensave/DPMS control -- | DBus module for X11 screensave/DPMS control
module XMonad.Internal.DBus.Screensaver module XMonad.Internal.DBus.Screensaver
( exportScreensaver ( exportScreensaver
@ -7,48 +7,54 @@ module XMonad.Internal.DBus.Screensaver
, callQuery , callQuery
, matchSignal , matchSignal
, ssSignalDep , ssSignalDep
) ) where
where
import DBus import Control.Monad (void)
import DBus.Client
import qualified DBus.Introspection as I import Data.Internal.DBus
import Data.Internal.DBus import Data.Internal.Dependency
import Data.Internal.XIO
import Graphics.X11.XScreenSaver import DBus
import RIO import DBus.Client
import XMonad.Internal.DBus.Common import qualified DBus.Introspection as I
import XMonad.Internal.IO
import XMonad.Internal.Shell import Graphics.X11.XScreenSaver
import Graphics.X11.Xlib.Display
import XMonad.Internal.DBus.Common
import XMonad.Internal.Process
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Low-level functions -- | Low-level functions
type SSState = Bool -- true is enabled type SSState = Bool -- true is enabled
ssExecutable :: FilePath ssExecutable :: String
ssExecutable = "xset" ssExecutable = "xset"
toggle :: MonadUnliftIO m => m SSState toggle :: IO SSState
toggle = do toggle = do
st <- query st <- query
let args = if st then ["off", "-dpms"] else ["on", "+dpms"] -- TODO figure out how not to do this with shell commands
-- this needs to be done with shell commands, because as far as I know there void $ createProcess' $ proc ssExecutable $ "s" : args st
-- are no Haskell bindings for DPMSDisable/Enable (from libxext) -- TODO this assumes the command succeeds
rc <- runProcess (proc ssExecutable $ "s" : args) return $ not st
return $ if rc == ExitSuccess then not st else st where
args s = if s then ["off", "-dpms"] else ["on", "+dpms"]
query :: MonadUnliftIO m => m SSState query :: IO SSState
query = do query = do
xssi <- withOpenDisplay (liftIO . xScreenSaverQueryInfo) dpy <- openDisplay ""
xssi <- xScreenSaverQueryInfo dpy
closeDisplay dpy
return $ case xssi of return $ case xssi of
Just XScreenSaverInfo {xssi_state = ScreenSaverDisabled} -> False Just XScreenSaverInfo { xssi_state = ScreenSaverDisabled } -> False
Just XScreenSaverInfo {xssi_state = _} -> True Just XScreenSaverInfo { xssi_state = _ } -> True
-- TODO handle errors better (at least log them?) -- TODO handle errors better (at least log them?)
Nothing -> False Nothing -> False
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DBus Interface -- | DBus Interface
-- --
-- Define a methods to toggle the screensaver. This methods will emit signal -- Define a methods to toggle the screensaver. This methods will emit signal
-- with the new state when called. Define another method to get the current -- with the new state when called. Define another method to get the current
@ -73,88 +79,60 @@ sigCurrentState :: Signal
sigCurrentState = signal ssPath interface memState sigCurrentState = signal ssPath interface memState
ruleCurrentState :: MatchRule ruleCurrentState :: MatchRule
ruleCurrentState = ruleCurrentState = matchAny
matchAny { matchPath = Just ssPath
{ matchPath = Just ssPath , matchInterface = Just interface
, matchInterface = Just interface , matchMember = Just memState
, matchMember = Just memState }
}
emitState :: MonadUnliftIO m => Client -> SSState -> m () emitState :: Client -> SSState -> IO ()
emitState client sss = emitState client sss = emit client $ sigCurrentState { signalBody = [toVariant sss] }
liftIO $ emit client $ sigCurrentState {signalBody = [toVariant sss]}
bodyGetCurrentState :: [Variant] -> Maybe SSState bodyGetCurrentState :: [Variant] -> Maybe SSState
bodyGetCurrentState [b] = fromVariant b :: Maybe SSState bodyGetCurrentState [b] = fromVariant b :: Maybe SSState
bodyGetCurrentState _ = Nothing bodyGetCurrentState _ = Nothing
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Exported haskell API -- | Exported haskell API
exportScreensaver exportScreensaver :: Maybe SesClient -> SometimesIO
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
=> Maybe NamedSesConnection
-> Sometimes (m (), m ())
exportScreensaver ses = exportScreensaver ses =
sometimesDBus ses "screensaver toggle" "xset" (toAnd_ bus ssx) cmd sometimesDBus ses "screensaver toggle" "xset" (toAnd_ bus ssx) cmd
where where
cmd = exportPair ssPath $ \cl_ -> do cmd cl = let cl' = toClient cl in
liftIO $ withRunInIO $ \run -> export cl' ssPath defaultInterface
return $ { interfaceName = interface
defaultInterface , interfaceMethods =
{ interfaceName = interface [ autoMethod memToggle $ emitState cl' =<< toggle
, interfaceMethods = , autoMethod memQuery query
[ autoMethod memToggle $ run $ emitState cl_ =<< toggle ]
, autoMethod memQuery (run query) , interfaceSignals = [sig]
] }
, interfaceSignals = [sig] sig = I.Signal
} { I.signalName = memState
sig = , I.signalArgs =
I.Signal [
{ I.signalName = memState I.SignalArg
, I.signalArgs = { I.signalArgName = "enabled"
[ I.SignalArg , I.signalArgType = TypeBoolean
{ I.signalArgName = "enabled" }
, I.signalArgType = TypeBoolean ]
} }
] bus = Bus [] xmonadBusName
}
bus = Bus [] xmonadSesBusName
ssx = DBusIO $ sysExe [Package Official "xorg-xset"] ssExecutable ssx = DBusIO $ sysExe [Package Official "xorg-xset"] ssExecutable
callToggle callToggle :: Maybe SesClient -> SometimesIO
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m) callToggle = sometimesEndpoint "screensaver toggle" "dbus switch" []
=> Maybe NamedSesConnection xmonadBusName ssPath interface memToggle
-> Sometimes (m ())
callToggle =
sometimesEndpoint
"screensaver toggle"
"dbus switch"
[]
xmonadSesBusName
ssPath
interface
memToggle
callQuery callQuery :: SesClient -> IO (Maybe SSState)
:: (HasClient env, MonadReader (env SesClient) m, MonadUnliftIO m) callQuery ses = do
=> m (Maybe SSState) reply <- callMethod ses xmonadBusName ssPath interface memQuery
callQuery = do
reply <- callMethod xmonadSesBusName ssPath interface memQuery
return $ either (const Nothing) bodyGetCurrentState reply return $ either (const Nothing) bodyGetCurrentState reply
matchSignal matchSignal :: (Maybe SSState -> IO ()) -> SesClient -> IO ()
:: ( HasClient env matchSignal cb ses = void $ addMatchCallback ruleCurrentState
, MonadReader (env SesClient) m (cb . bodyGetCurrentState) ses
, MonadUnliftIO m
)
=> (Maybe SSState -> m ())
-> m ()
matchSignal cb =
void $
addMatchCallback
ruleCurrentState
(cb . bodyGetCurrentState)
ssSignalDep :: DBusDependency_ SesClient ssSignalDep :: DBusDependency_ SesClient
ssSignalDep = Endpoint [] xmonadSesBusName ssPath interface $ Signal_ memState ssSignalDep = Endpoint [] xmonadBusName ssPath interface $ Signal_ memState

View File

@ -1,5 +1,7 @@
{-# LANGUAGE ViewPatterns #-}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Random IO-ish functions used throughtout xmonad -- | Random IO-ish functions used throughtout xmonad
-- --
-- Most (probably all) of these functions are intended to work with sysfs where -- Most (probably all) of these functions are intended to work with sysfs where
-- some safe assumptions can be made about file contents. -- some safe assumptions can be made about file contents.
@ -17,124 +19,86 @@ module XMonad.Internal.IO
, incPercent , incPercent
-- , isReadable -- , isReadable
-- , isWritable -- , isWritable
, PermResult (..) , PermResult(..)
, getPermissionsSafe , getPermissionsSafe
, waitUntilExit ) where
, withOpenDisplay
)
where
import Data.Char import Data.Char
import Graphics.X11.Xlib.Display import Data.Text (pack, unpack)
import Graphics.X11.Xlib.Event import Data.Text.IO as T (readFile, writeFile)
import Graphics.X11.Xlib.Types
import RIO hiding (Display) import System.Directory
import RIO.Directory import System.IO.Error
import RIO.FilePath
import qualified RIO.Text as T
import System.IO.Error
import System.Process
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- read -- | read
readInt :: MonadIO m => (Read a, Integral a) => FilePath -> m a readInt :: (Read a, Integral a) => FilePath -> IO a
readInt = fmap (fromMaybe 0 . readMaybe . takeWhile isDigit . T.unpack) . readFileUtf8 readInt = fmap (read . takeWhile isDigit . unpack) . T.readFile
readBool :: MonadIO m => FilePath -> m Bool readBool :: FilePath -> IO Bool
readBool = fmap (== (1 :: Int)) . readInt readBool = fmap (==(1 :: Int)) . readInt
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- write -- | write
writeInt :: (MonadIO m, Show a) => FilePath -> a -> m () writeInt :: (Show a, Integral a) => FilePath -> a -> IO ()
writeInt f = writeFileUtf8 f . T.pack . show writeInt f = T.writeFile f . pack . show
writeBool :: MonadIO m => FilePath -> Bool -> m () writeBool :: FilePath -> Bool -> IO ()
writeBool f b = writeInt f ((if b then 1 else 0) :: Int) writeBool f b = writeInt f ((if b then 1 else 0) :: Int)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- percent-based read/write -- | percent-based read/write
-- --
-- "Raw" values are whatever is stored in sysfs and "percent" is the user-facing -- "Raw" values are whatever is stored in sysfs and "percent" is the user-facing
-- value. Assume that the file being read has a min of 0 and an unchanging max -- value. Assume that the file being read has a min of 0 and an unchanging max
-- given by a runtime argument, which is scaled linearly to the range 0-100 -- given by a runtime argument, which is scaled linearly to the range 0-100
-- (percent). -- (percent).
rawToPercent :: (Integral a, Integral b, RealFrac c) => (a, a) -> b -> c
rawToPercent :: (Integral a, Integral b, Read b, RealFrac c) => (a, a) -> b -> c
rawToPercent (lower, upper) raw = rawToPercent (lower, upper) raw =
100 * (fromIntegral raw - fromIntegral lower) / fromIntegral (upper - lower) 100 * (fromIntegral raw - fromIntegral lower) / fromIntegral (upper - lower)
-- rawToPercent upper raw = 100 * fromIntegral raw / fromIntegral upper -- rawToPercent upper raw = 100 * fromIntegral raw / fromIntegral upper
readPercent :: MonadIO m => (Integral a, RealFrac b) => (a, a) -> FilePath -> m b readPercent :: (Integral a, RealFrac b) => (a, a) -> FilePath -> IO b
readPercent bounds path = do readPercent bounds path = do
i <- readInt path i <- readInt path
return $ rawToPercent bounds (i :: Integer) return $ rawToPercent bounds (i :: Integer)
percentToRaw :: (Integral a, RealFrac b, Integral c) => (a, a) -> b -> c percentToRaw :: (Integral a, RealFrac b, Integral c) => (a, a) -> b -> c
percentToRaw (lower, upper) perc = percentToRaw (lower, upper) perc = round $
round $ fromIntegral lower + perc / 100.0 * (fromIntegral upper - fromIntegral lower)
fromIntegral lower + perc / 100.0 * (fromIntegral upper - fromIntegral lower)
writePercent writePercent :: (Integral a, RealFrac b) => (a, a) -> FilePath -> b -> IO b
:: (MonadIO m, Integral a, RealFrac b)
=> (a, a)
-> FilePath
-> b
-> m b
writePercent bounds path perc = do writePercent bounds path perc = do
let t let t | perc > 100 = 100
| perc > 100 = 100
| perc < 0 = 0 | perc < 0 = 0
| otherwise = perc | otherwise = perc
writeInt path (percentToRaw bounds t :: Int) writeInt path (percentToRaw bounds t :: Int)
return t return t
writePercentMin writePercentMin :: (Integral a, RealFrac b) => (a, a) -> FilePath -> IO b
:: (MonadIO m, Integral a, RealFrac b)
=> (a, a)
-> FilePath
-> m b
writePercentMin bounds path = writePercent bounds path 0 writePercentMin bounds path = writePercent bounds path 0
writePercentMax writePercentMax :: (Integral a, RealFrac b) => (a, a) -> FilePath -> IO b
:: (MonadIO m, Integral a, RealFrac b)
=> (a, a)
-> FilePath
-> m b
writePercentMax bounds path = writePercent bounds path 100 writePercentMax bounds path = writePercent bounds path 100
shiftPercent shiftPercent :: (Integral a, RealFrac b) => (b -> b -> b) -> Int -> FilePath
:: (MonadIO m, Integral a, RealFrac b) -> (a, a) -> IO b
=> (b -> b -> b) shiftPercent f steps path bounds = writePercent bounds path . f stepsize
-> Int
-> FilePath
-> (a, a)
-> m b
shiftPercent f steps path bounds =
writePercent bounds path . f stepsize
=<< readPercent bounds path =<< readPercent bounds path
where where
stepsize = 100 / fromIntegral steps stepsize = 100 / fromIntegral steps
incPercent incPercent :: (Integral a, RealFrac b) => Int -> FilePath -> (a, a) -> IO b
:: (MonadIO m, Integral a, RealFrac b)
=> Int
-> FilePath
-> (a, a)
-> m b
incPercent = shiftPercent (+) incPercent = shiftPercent (+)
decPercent decPercent :: (Integral a, RealFrac b) => Int -> FilePath -> (a, a) -> IO b
:: (MonadIO m, Integral a, RealFrac b)
=> Int
-> FilePath
-> (a, a)
-> m b
decPercent = shiftPercent subtract -- silly (-) operator thingy error decPercent = shiftPercent subtract -- silly (-) operator thingy error
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- permission query -- | permission query
data PermResult a = PermResult a | NotFoundError | PermError data PermResult a = PermResult a | NotFoundError | PermError
deriving (Show, Eq) deriving (Show, Eq)
@ -144,36 +108,19 @@ data PermResult a = PermResult a | NotFoundError | PermError
-- fmap _ NotFoundError = NotFoundError -- fmap _ NotFoundError = NotFoundError
-- fmap _ PermError = PermError -- fmap _ PermError = PermError
getPermissionsSafe :: MonadUnliftIO m => FilePath -> m (PermResult Permissions) getPermissionsSafe :: FilePath -> IO (PermResult Permissions)
getPermissionsSafe f = do getPermissionsSafe f = do
r <- tryIO $ getPermissions f r <- tryIOError $ getPermissions f
return $ case r of return $ case r of
Right z -> PermResult z Right z -> PermResult z
Left (isPermissionError -> True) -> PermError Left (isPermissionError -> True) -> PermError
Left (isDoesNotExistError -> True) -> NotFoundError Left (isDoesNotExistError -> True) -> NotFoundError
-- the above error should be the only ones thrown by getPermission, -- the above error should be the only ones thrown by getPermission,
-- so the catchall case should never happen -- so the catchall case should never happen
_ -> error "Unknown permission error" _ -> error "Unknown permission error"
-- isReadable :: FilePath -> IO (PermResult Bool) -- isReadable :: FilePath -> IO (PermResult Bool)
-- isReadable = fmap (fmap readable) . getPermissionsSafe -- isReadable = fmap (fmap readable) . getPermissionsSafe
-- isWritable :: FilePath -> IO (PermResult Bool) -- isWritable :: FilePath -> IO (PermResult Bool)
-- isWritable = fmap (fmap writable) . getPermissionsSafe -- isWritable = fmap (fmap writable) . getPermissionsSafe
-- | Block until a PID has exited.
-- Use this to control flow based on a process that was not explicitly started
-- by the Haskell runtime itself, and thus has no data structures to query.
waitUntilExit :: (MonadUnliftIO m) => Pid -> m ()
waitUntilExit pid = do
res <- doesDirectoryExist $ "/proc" </> show pid
when res $ do
threadDelay 100000
waitUntilExit pid
withOpenDisplay :: MonadUnliftIO m => (Display -> m a) -> m a
withOpenDisplay = bracket (liftIO $ openDisplay "") cleanup
where
cleanup dpy = liftIO $ do
flush dpy
closeDisplay dpy

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Functions for formatting and sending notifications -- | Functions for formatting and sending notifications
-- --
-- NOTE I use the DBus.Notify lib even though I don't actually use the DBus for -- NOTE I use the DBus.Notify lib even though I don't actually use the DBus for
-- notifications (just formation them into 'notify-send' commands and spawn a -- notifications (just formation them into 'notify-send' commands and spawn a
@ -7,58 +7,54 @@
-- decide to switch to using the DBus it will be easy. -- decide to switch to using the DBus it will be easy.
module XMonad.Internal.Notify module XMonad.Internal.Notify
( Note (..) ( Note(..)
, Body (..) , Body(..)
, defNote , defNote
, defNoteInfo , defNoteInfo
, defNoteError , defNoteError
, fmtNotifyCmd , fmtNotifyCmd
, spawnNotify , spawnNotify
) ) where
where
import DBus.Notify import Control.Monad.IO.Class
import RIO import Data.Maybe
import qualified RIO.Text as T
import XMonad.Internal.Shell import DBus.Notify
import XMonad.Internal.Shell
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Some nice default notes -- | Some nice default notes
defNote :: Note defNote :: Note
defNote = blankNote {summary = "\"xmonad\""} defNote = blankNote { summary = "\"xmonad\"" }
defNoteInfo :: Note defNoteInfo :: Note
defNoteInfo = defNoteInfo = defNote
defNote { appImage = Just $ Icon "dialog-information-symbolic" }
{ appImage = Just $ Icon "dialog-information-symbolic"
}
defNoteError :: Note defNoteError :: Note
defNoteError = defNoteError = defNote
defNote { appImage = Just $ Icon "dialog-error-symbolic" }
{ appImage = Just $ Icon "dialog-error-symbolic"
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Format a 'notify-send' command to be send to the shell -- | Format a 'notify-send' command to be send to the shell
parseBody :: Body -> Maybe T.Text parseBody :: Body -> Maybe String
parseBody (Text s) = Just $ T.pack s parseBody (Text s) = Just s
parseBody _ = Nothing parseBody _ = Nothing
fmtNotifyCmd :: Note -> T.Text fmtNotifyCmd :: Note -> String
fmtNotifyCmd = fmtCmd "notify-send" . fmtNotifyArgs fmtNotifyCmd = fmtCmd "notify-send" . fmtNotifyArgs
spawnNotify :: MonadIO m => Note -> m () spawnNotify :: MonadIO m => Note -> m ()
spawnNotify = spawnCmd "notify-send" . fmtNotifyArgs spawnNotify = spawnCmd "notify-send" . fmtNotifyArgs
fmtNotifyArgs :: Note -> [T.Text] fmtNotifyArgs :: Note -> [String]
fmtNotifyArgs n = getIcon n ++ getSummary n ++ getBody n fmtNotifyArgs n = getIcon n ++ getSummary n ++ getBody n
where where
-- TODO add the rest of the options as needed -- TODO add the rest of the options as needed
getSummary = (: []) . doubleQuote . T.pack . summary getSummary = (:[]) . doubleQuote . summary
getIcon n' = getIcon n' = maybe [] (\i -> ["-i", case i of { Icon s -> s; File s -> s }])
maybe [] (\i -> ["-i", T.pack $ case i of Icon s -> s; File s -> s]) $ $ appImage n'
appImage n'
getBody n' = maybeToList $ (fmap doubleQuote . parseBody) =<< body n' getBody n' = maybeToList $ (fmap doubleQuote . parseBody) =<< body n'

View File

@ -0,0 +1,92 @@
--------------------------------------------------------------------------------
-- | Functions for managing processes
module XMonad.Internal.Process
( waitUntilExit
, killHandle
, spawnPipe'
, spawnPipe
, spawnPipeArgs
, createProcess'
, readCreateProcessWithExitCode'
, proc'
, shell'
, spawn
, spawnAt
, module System.Process
) where
import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Data.Maybe
import System.Directory
import System.Exit
import System.IO
import System.Posix.Signals
import System.Process
import XMonad.Core hiding (spawn)
-- | Block until a PID has exited (in any form)
-- ASSUMPTION on linux PIDs will always increase until they overflow, in which
-- case they will start to recycle. Barring any fork bombs, this code should
-- work because we can reasonably expect that no processes will spawn with the
-- same PID within the delay limit
-- TODO this will not work if the process is a zombie (maybe I care...)
waitUntilExit :: Show t => t -> IO ()
waitUntilExit pid = do
res <- doesDirectoryExist $ "/proc/" ++ show pid
when res $ threadDelay 100000 >> waitUntilExit pid
killHandle :: ProcessHandle -> IO ()
killHandle ph = do
ec <- getProcessExitCode ph
unless (isJust ec) $ do
pid <- getPid ph
forM_ pid $ signalProcess sigTERM
-- this may fail if the process exits instantly and the handle
-- is destroyed by the time we get to this line (I think?)
void (try $ waitForProcess ph :: IO (Either IOException ExitCode))
withDefaultSignalHandlers :: IO a -> IO a
withDefaultSignalHandlers =
bracket_ uninstallSignalHandlers installSignalHandlers
addGroupSession :: CreateProcess -> CreateProcess
addGroupSession cp = cp { create_group = True, new_session = True }
createProcess' :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
createProcess' = withDefaultSignalHandlers . createProcess
readCreateProcessWithExitCode' :: CreateProcess -> String -> IO (ExitCode, String, String)
readCreateProcessWithExitCode' c i = withDefaultSignalHandlers
$ readCreateProcessWithExitCode c i
shell' :: String -> CreateProcess
shell' = addGroupSession . shell
proc' :: FilePath -> [String] -> CreateProcess
proc' cmd args = addGroupSession $ proc cmd args
spawn :: MonadIO m => String -> m ()
spawn = io . void . createProcess' . shell'
spawnAt :: MonadIO m => FilePath -> String -> m ()
spawnAt fp cmd = io $ void $ createProcess' $ (shell' cmd) { cwd = Just fp }
spawnPipe' :: CreateProcess -> IO (Handle, ProcessHandle)
spawnPipe' cp = do
-- ASSUME creating a pipe will always succeed in making a Just Handle
(Just h, _, _, p) <- createProcess' $ cp { std_in = CreatePipe }
hSetBuffering h LineBuffering
return (h, p)
spawnPipe :: String -> IO (Handle, ProcessHandle)
spawnPipe = spawnPipe' . shell
spawnPipeArgs :: FilePath -> [String] -> IO (Handle, ProcessHandle)
spawnPipeArgs cmd = spawnPipe' . proc cmd

View File

@ -1,156 +1,59 @@
-- Functions for formatting and spawning shell commands --------------------------------------------------------------------------------
-- | Functions for formatting and spawning shell commands
module XMonad.Internal.Shell module XMonad.Internal.Shell
( fmtCmd ( fmtCmd
, spawnCmd , spawnCmd
, spawn
, spawnPipe
, doubleQuote , doubleQuote
, singleQuote , singleQuote
, skip , skip
, runProcess
, proc
, shell
, (#!&&) , (#!&&)
, (#!||) , (#!||)
, (#!|) , (#!|)
, (#!>>) , (#!>>)
) ) where
where
import RIO import Control.Monad.IO.Class
import qualified RIO.Text as T
import qualified System.Process.Typed as P
import qualified XMonad.Core as X
import qualified XMonad.Util.Run as XR
-- | Fork a new process and wait for its exit code. import XMonad.Internal.Process
--
-- This function will work despite xmonad ignoring SIGCHLD.
--
-- A few facts about xmonad (and window managers in general):
-- 1) It is single-threaded (since X is single threaded)
-- 2) Because of (1), it ignores SIGCHLD, which means any subprocess started
-- by xmonad will instantly be reaped after spawning. This guarantees the
-- main thread running the WM will never be blocked.
--
-- In general, this means I can't wait for exit codes (since wait() doesn't
-- work) See https://github.com/xmonad/xmonad/issues/113.
--
-- If I want an exit code, The best solution (I can come up with), is to use
-- bracket to uninstall handlers, run process (with wait), and then reinstall
-- handlers. I can use this with a much higher-level interface which will make
-- things easier. This obviously means that if the process is running in the
-- main thread, it needs to be almost instantaneous. Note if using a high-level
-- API for this, the process needs to spawn, finish, and be reaped by the
-- xmonad process all while the signal handlers are 'disabled' (which limits
-- the functions I can use to those that call waitForProcess).
--
-- XMonad and contrib use their own method of spawning subprocesses using the
-- extremely low-level 'System.Process.Posix' API. See the code for
-- 'XMonad.Core.spawn' or 'XMonad.Util.Run.safeSpawn'. Specifically, the
-- sequence is (in terms of the low level Linux API):
-- 1) call fork()
-- 2) uninstall signal handlers (to allow wait() to work in subprocesses)
-- 3) call setsid() (so killing the child will kill its children, if any)
-- 4) start new thing with exec()
--
-- In contrast with high-level APIs like 'System.Process', this will leave no
-- trailing data structures to clean up, at the cost of being gross to look at
-- and possibly more error-prone.
runProcess :: MonadUnliftIO m => P.ProcessConfig a b c -> m ExitCode
runProcess = withDefaultSignalHandlers . P.runProcess
-- | Run an action without xmonad's signal handlers. --------------------------------------------------------------------------------
withDefaultSignalHandlers :: MonadUnliftIO m => m a -> m a -- | Opening subshell
withDefaultSignalHandlers =
bracket_ X.uninstallSignalHandlers X.installSignalHandlers
-- | Set a child process to create a new group and session spawnCmd :: MonadIO m => String -> [String] -> m ()
addGroupSession :: P.ProcessConfig x y z -> P.ProcessConfig x y z spawnCmd cmd args = spawn $ fmtCmd cmd args
addGroupSession = P.setCreateGroup True . P.setNewSession True
-- | Create a 'ProcessConfig' for a shell command --------------------------------------------------------------------------------
shell :: T.Text -> P.ProcessConfig () () () -- | Formatting commands
shell = addGroupSession . P.shell . T.unpack
-- | Create a 'ProcessConfig' for a command with arguments fmtCmd :: String -> [String] -> String
proc :: FilePath -> [T.Text] -> P.ProcessConfig () () () fmtCmd cmd args = unwords $ cmd : args
proc cmd args = addGroupSession $ P.proc cmd (T.unpack <$> args)
-- | Run 'XMonad.Core.spawn' with 'Text' input. (#!&&) :: String -> String -> String
spawn :: MonadIO m => T.Text -> m () cmdA #!&& cmdB = cmdA ++ " && " ++ cmdB
spawn = X.spawn . T.unpack
-- | Run 'XMonad.Run.Utils.spawnPipe' with 'Text' input.
spawnPipe :: MonadUnliftIO m => T.Text -> m Handle
spawnPipe = liftIO . XR.spawnPipe . T.unpack
-- spawnPipeRW
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
-- => T.Text
-- -> m Handle
-- spawnPipeRW x = do
-- (r, h) <- liftIO mkPipe
-- child r
-- liftIO $ closeFd r
-- return h
-- where
-- mkPipe = do
-- (r, w) <- createPipe
-- setFdOption w CloseOnExec True
-- h <- fdToHandle w
-- -- ASSUME we are using utf8 everywhere
-- hSetEncoding h utf8
-- hSetBuffering h LineBuffering
-- return (r, h)
-- child r = void $ withRunInIO $ \runIO -> do
-- X.xfork $ runIO $ do
-- void $ liftIO $ dupTo r stdInput
-- liftIO $ executeFile "/bin/sh" False ["-c", T.unpack x] Nothing
-- | Run 'XMonad.Core.spawn' with a command and arguments
spawnCmd :: MonadIO m => FilePath -> [T.Text] -> m ()
spawnCmd cmd = spawn . fmtCmd cmd
-- | Format a command and list of arguments as 'Text'
fmtCmd :: FilePath -> [T.Text] -> T.Text
fmtCmd cmd args = T.unwords $ T.pack cmd : args
op :: T.Text -> T.Text -> T.Text -> T.Text
op a x b = T.unwords [a, x, b]
-- | Format two shell expressions separated by "&&"
(#!&&) :: T.Text -> T.Text -> T.Text
cmdA #!&& cmdB = op cmdA "&&" cmdB
infixr 0 #!&& infixr 0 #!&&
-- | Format two shell expressions separated by "|" (#!|) :: String -> String -> String
(#!|) :: T.Text -> T.Text -> T.Text cmdA #!| cmdB = cmdA ++ " | " ++ cmdB
cmdA #!| cmdB = op cmdA "|" cmdB
infixr 0 #!| infixr 0 #!|
-- | Format two shell expressions separated by "||" (#!||) :: String -> String -> String
(#!||) :: T.Text -> T.Text -> T.Text cmdA #!|| cmdB = cmdA ++ " || " ++ cmdB
cmdA #!|| cmdB = op cmdA "||" cmdB
infixr 0 #!|| infixr 0 #!||
-- | Format two shell expressions separated by ";" (#!>>) :: String -> String -> String
(#!>>) :: T.Text -> T.Text -> T.Text cmdA #!>> cmdB = cmdA ++ "; " ++ cmdB
cmdA #!>> cmdB = op cmdA ";" cmdB
infixr 0 #!>> infixr 0 #!>>
-- | Wrap input in double quotes doubleQuote :: String -> String
doubleQuote :: T.Text -> T.Text doubleQuote s = "\"" ++ s ++ "\""
doubleQuote s = T.concat ["\"", s, "\""]
-- | Wrap input in single quotes singleQuote :: String -> String
singleQuote :: T.Text -> T.Text singleQuote s = "'" ++ s ++ "'"
singleQuote s = T.concat ["'", s, "'"]
skip :: Monad m => m () skip :: Monad m => m ()
skip = return () skip = return ()

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Theme for XMonad and Xmobar -- | Theme for XMonad and Xmobar
module XMonad.Internal.Theme module XMonad.Internal.Theme
( baseColor ( baseColor
@ -16,9 +16,9 @@ module XMonad.Internal.Theme
, backdropTextColor , backdropTextColor
, blend' , blend'
, darken' , darken'
, Slant (..) , Slant(..)
, Weight (..) , Weight(..)
, FontData (..) , FontData(..)
, FontBuilder , FontBuilder
, buildFont , buildFont
, fallbackFont , fallbackFont
@ -26,140 +26,125 @@ module XMonad.Internal.Theme
, defFontData , defFontData
, tabbedTheme , tabbedTheme
, promptTheme , promptTheme
) ) where
where
import Data.Char
import Data.Colour
import Data.Colour.SRGB
import Data.List
import Data.Colour
import Data.Colour.SRGB
import RIO
import qualified RIO.Text as T
import qualified XMonad.Layout.Decoration as D import qualified XMonad.Layout.Decoration as D
import qualified XMonad.Prompt as P import qualified XMonad.Prompt as P
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Colors - vocabulary roughly based on GTK themes -- | Colors - vocabulary roughly based on GTK themes
baseColor :: T.Text baseColor :: String
baseColor = "#f7f7f7" baseColor = "#f7f7f7"
bgColor :: T.Text bgColor :: String
bgColor = "#d6d6d6" bgColor = "#d6d6d6"
fgColor :: T.Text fgColor :: String
fgColor = "#2c2c2c" fgColor = "#2c2c2c"
bordersColor :: T.Text bordersColor :: String
bordersColor = darken' 0.3 bgColor bordersColor = darken' 0.3 bgColor
warningColor :: T.Text warningColor :: String
warningColor = "#ffca28" warningColor = "#ffca28"
errorColor :: T.Text errorColor :: String
errorColor = "#e53935" errorColor = "#e53935"
selectedFgColor :: T.Text selectedFgColor :: String
selectedFgColor = "#ffffff" selectedFgColor = "#ffffff"
selectedBgColor :: T.Text selectedBgColor :: String
selectedBgColor = "#4a79c7" selectedBgColor = "#4a79c7"
selectedBordersColor :: T.Text selectedBordersColor :: String
selectedBordersColor = "#4a79c7" selectedBordersColor = "#4a79c7"
backdropBaseColor :: T.Text backdropBaseColor :: String
backdropBaseColor = baseColor backdropBaseColor = baseColor
backdropTextColor :: T.Text backdropTextColor :: String
backdropTextColor = blend' 0.95 fgColor backdropBaseColor backdropTextColor = blend' 0.95 fgColor backdropBaseColor
backdropFgColor :: T.Text backdropFgColor :: String
backdropFgColor = blend' 0.75 fgColor bgColor backdropFgColor = blend' 0.75 fgColor bgColor
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Color functions -- | Color functions
blend' :: Float -> T.Text -> T.Text -> T.Text blend' :: Float -> String -> String -> String
blend' wt c0 c1 = sRGB24showT $ blend wt (sRGB24readT c0) (sRGB24readT c1) blend' wt c0 c1 = sRGB24show $ blend wt (sRGB24read c0) (sRGB24read c1)
darken' :: Float -> T.Text -> T.Text darken' :: Float -> String -> String
darken' wt = sRGB24showT . darken wt . sRGB24readT darken' wt = sRGB24show . darken wt . sRGB24read
sRGB24readT :: (RealFrac a, Floating a) => T.Text -> Colour a
sRGB24readT = sRGB24read . T.unpack
sRGB24showT :: (RealFrac a, Floating a) => Colour a -> T.Text
sRGB24showT = T.pack . sRGB24show
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Fonts -- | Fonts
data Slant data Slant = Roman
= Roman | Italic
| Italic | Oblique
| Oblique deriving (Eq, Show)
deriving (Eq, Show)
data Weight data Weight = Light
= Light | Medium
| Medium | Demibold
| Demibold | Bold
| Bold | Black
| Black deriving (Eq, Show)
deriving (Eq, Show)
data FontData = FontData data FontData = FontData
{ weight :: Maybe Weight { weight :: Maybe Weight
, slant :: Maybe Slant , slant :: Maybe Slant
, size :: Maybe Int , size :: Maybe Int
, pixelsize :: Maybe Int , pixelsize :: Maybe Int
, antialias :: Maybe Bool , antialias :: Maybe Bool
} }
type FontBuilder = FontData -> T.Text type FontBuilder = FontData -> String
buildFont :: Maybe T.Text -> FontData -> T.Text buildFont :: Maybe String -> FontData -> String
buildFont Nothing _ = "fixed" buildFont Nothing _ = "fixed"
buildFont buildFont (Just fam) FontData { weight = w
(Just fam) , slant = l
FontData , size = s
{ weight = w , pixelsize = p
, slant = l , antialias = a
, size = s }
, pixelsize = p = intercalate ":" $ ["xft", fam] ++ elems
, antialias = a where
} = elems = [ k ++ "=" ++ v | (k, Just v) <- [ ("weight", showLower w)
T.intercalate ":" $ ["xft", fam] ++ elems , ("slant", showLower l)
where , ("size", showLower s)
elems = , ("pixelsize", showLower p)
[ T.concat [k, "=", v] , ("antialias", showLower a)
| (k, Just v) <- ]
[ ("weight", showLower w) ]
, ("slant", showLower l) showLower :: Show a => Maybe a -> Maybe String
, ("size", showLower s) showLower = fmap (fmap toLower . show)
, ("pixelsize", showLower p)
, ("antialias", showLower a)
]
]
showLower :: Show a => Maybe a -> Maybe T.Text
showLower = fmap (T.toLower . T.pack . show)
fallbackFont :: FontBuilder fallbackFont :: FontBuilder
fallbackFont = buildFont Nothing fallbackFont = buildFont Nothing
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Default font and data -- | Default font and data
defFontData :: FontData defFontData :: FontData
defFontData = defFontData = FontData
FontData { size = Just 10
{ size = Just 10 , antialias = Just True
, antialias = Just True , weight = Nothing
, weight = Nothing , slant = Nothing
, slant = Nothing , pixelsize = Nothing
, pixelsize = Nothing }
}
defFontFamily :: T.Text defFontFamily :: String
defFontFamily = "DejaVu Sans" defFontFamily = "DejaVu Sans"
-- defFontDep :: IODependency FontBuilder -- defFontDep :: IODependency FontBuilder
@ -169,42 +154,44 @@ defFontFamily = "DejaVu Sans"
-- defFontTree = fontTree "DejaVu Sans" -- defFontTree = fontTree "DejaVu Sans"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Complete themes -- | Complete themes
tabbedTheme :: FontBuilder -> D.Theme tabbedTheme :: FontBuilder -> D.Theme
tabbedTheme fb = tabbedTheme fb = D.def
D.def { D.fontName = fb $ defFontData { weight = Just Bold }
{ D.fontName = T.unpack $ fb $ defFontData {weight = Just Bold}
, D.activeTextColor = T.unpack fgColor
, D.activeColor = T.unpack bgColor
, D.activeBorderColor = T.unpack bgColor
, D.inactiveTextColor = T.unpack backdropTextColor
, D.inactiveColor = T.unpack backdropFgColor
, D.inactiveBorderColor = T.unpack backdropFgColor
, D.urgentTextColor = T.unpack $ darken' 0.5 errorColor
, D.urgentColor = T.unpack errorColor
, D.urgentBorderColor = T.unpack errorColor
, -- this is in a newer version
-- , D.activeBorderWidth = 0
-- , D.inactiveBorderWidth = 0
-- , D.urgentBorderWidth = 0
D.decoHeight = 20 , D.activeTextColor = fgColor
, D.windowTitleAddons = [] , D.activeColor = bgColor
, D.windowTitleIcons = [] , D.activeBorderColor = bgColor
}
, D.inactiveTextColor = backdropTextColor
, D.inactiveColor = backdropFgColor
, D.inactiveBorderColor = backdropFgColor
, D.urgentTextColor = darken' 0.5 errorColor
, D.urgentColor = errorColor
, D.urgentBorderColor = errorColor
-- this is in a newer version
-- , D.activeBorderWidth = 0
-- , D.inactiveBorderWidth = 0
-- , D.urgentBorderWidth = 0
, D.decoHeight = 20
, D.windowTitleAddons = []
, D.windowTitleIcons = []
}
promptTheme :: FontBuilder -> P.XPConfig promptTheme :: FontBuilder -> P.XPConfig
promptTheme fb = promptTheme fb = P.def
P.def { P.font = fb $ defFontData { size = Just 12 }
{ P.font = T.unpack $ fb $ defFontData {size = Just 12} , P.bgColor = bgColor
, P.bgColor = T.unpack bgColor , P.fgColor = fgColor
, P.fgColor = T.unpack fgColor , P.fgHLight = selectedFgColor
, P.fgHLight = T.unpack selectedFgColor , P.bgHLight = selectedBgColor
, P.bgHLight = T.unpack selectedBgColor , P.borderColor = bordersColor
, P.borderColor = T.unpack bordersColor , P.promptBorderWidth = 1
, P.promptBorderWidth = 1 , P.height = 35
, P.height = 35 , P.position = P.CenteredAt 0.5 0.5
, P.position = P.CenteredAt 0.5 0.5 , P.historySize = 0
, P.historySize = 0 }
}

View File

@ -1,155 +0,0 @@
--------------------------------------------------------------------------------
-- NetworkManager Connection plugin
--
-- Show active connections of varying types.
--
-- This plugin exclusively monitors the */ActiveConnection/* paths in the
-- NetworkManager DBus path for state changes. It does not pin these to any
-- particular interface but instead looks at all connections equally and filters
-- based on their Type (ethernet, wifi, VPN, etc). For many use cases this will
-- track well enough with either one or a collection of similar interfaces (ie
-- all ethernet or all wifi).
module Xmobar.Plugins.ActiveConnection
( ActiveConnection (..)
, devDep
, connAlias
)
where
import DBus
import Data.Internal.DBus
import Data.Internal.XIO
import RIO
import qualified RIO.Map as M
import qualified RIO.NonEmpty as NE
import qualified RIO.Text as T
import XMonad.Internal.Command.Desktop
import XMonad.Internal.DBus.Common
import Xmobar
import Xmobar.Plugins.Common
newtype ActiveConnection
= ActiveConnection (NE.NonEmpty T.Text, T.Text, Colors)
deriving (Read, Show)
connAlias :: NE.NonEmpty T.Text -> T.Text
connAlias = T.intercalate "_" . NE.toList
instance Exec ActiveConnection where
alias (ActiveConnection (contypes, _, _)) = T.unpack $ connAlias contypes
start (ActiveConnection (contypes, text, colors)) cb =
withDBusClientConnection cb Nothing (Just "ethernet.log") $ \c -> do
let dpy cb' = displayMaybe cb' formatter . Just =<< readState
i <- withDIO c $ initialState contypes
s <- newMVar i
let mapEnv c' = mapRIO (PluginEnv c' s dpy cb)
mapEnv c $ addListener mapEnv >> pluginDisplay
where
formatter names = return $ case names of
[] -> colorText colors False text
xs -> T.unwords [colorText colors True text, T.intercalate "|" xs]
addListener mapEnv = do
res <- matchSignalFull nmBus Nothing (Just nmActiveInterface) (Just stateChanged)
case res of
Nothing -> logError "could not start listener"
Just rule ->
-- Start a new connection and RIO process since the parent thread
-- will have died before these callbacks fire, therefore the logging
-- file descriptor will be closed. This makes a new one
-- TODO can I recycle the client?
void $
addMatchCallbackSignal rule $ \sig ->
withDBusClientConnection cb Nothing (Just "ethernet-cb.log") $ \c' ->
mapEnv c' $
testActiveType contypes sig
nmBus :: BusName
nmBus = "org.freedesktop.NetworkManager"
nmPath :: ObjectPath
nmPath = "/org/freedesktop/NetworkManager"
nmInterface :: InterfaceName
nmInterface = "org.freedesktop.NetworkManager"
nmObjectTreePath :: ObjectPath
nmObjectTreePath = "/org/freedesktop"
nmActiveInterface :: InterfaceName
nmActiveInterface = "org.freedesktop.NetworkManager.Connection.Active"
stateChanged :: MemberName
stateChanged = "StateChanged"
-- semi-random method to test to ensure that NetworkManager is up and on DBus
devDep :: DBusDependency_ SysClient
devDep =
Endpoint networkManagerPkgs networkManagerBus nmPath nmInterface $
Method_ "GetDeviceByIpIface"
type EthIO = PluginIO EthState SysClient
type EthState = M.Map ObjectPath T.Text
getConnectionProp :: MemberName -> ObjectPath -> EthIO [Variant]
getConnectionProp prop path = callPropertyGet nmBus path nmActiveInterface prop
getConnectionId :: ObjectPath -> EthIO (Maybe T.Text)
getConnectionId = fmap fromSingletonVariant . getConnectionProp "Id"
getConnectionType :: ObjectPath -> EthIO (Maybe T.Text)
getConnectionType = fmap fromSingletonVariant . getConnectionProp "Type"
updateConnected :: NE.NonEmpty T.Text -> ObjectPath -> EthIO ()
updateConnected contypes path = do
typeRes <- getConnectionType path
logMaybe "type" getId typeRes
where
path' = displayBytesUtf8 $ T.encodeUtf8 $ T.pack $ formatObjectPath path
logMaybe what = maybe (logError ("could not get " <> what <> " for " <> path'))
getId contype = do
when (contype `elem` contypes) $ do
idRes <- getConnectionId path
logMaybe "ID" insertId idRes
insertId i = do
s <- asks plugState
modifyMVar_ s $ return . M.insert path i
updateDisconnected :: ObjectPath -> EthIO ()
updateDisconnected path = do
s <- asks plugState
modifyMVar_ s $ return . M.delete path
testActiveType :: NE.NonEmpty T.Text -> Signal -> EthIO ()
testActiveType contypes sig = do
case signalBody sig of
[state, _] -> case fromVariant state of
Just (2 :: Word32) -> updateConnected contypes path >> pluginDisplay
Just 4 -> updateDisconnected path >> pluginDisplay
_ -> return ()
_ -> return ()
where
path = signalPath sig
initialState
:: ( SafeClient c
, MonadUnliftIO m
, MonadReader (env c) m
, HasClient env
, HasLogFunc (env c)
)
=> NE.NonEmpty T.Text
-> m EthState
initialState contypes =
M.mapMaybe go <$> callGetManagedObjects nmBus nmObjectTreePath
where
go = getId <=< M.lookup nmActiveInterface
getId m =
fromVariant
=<< (\t -> if t `elem` contypes then M.lookup "Id" m else Nothing)
=<< fromVariant
=<< M.lookup "Type" m
readState :: EthIO [T.Text]
readState = M.elems <$> (readMVar =<< asks plugState)

View File

@ -1,28 +1,21 @@
-- Common backlight plugin bits --------------------------------------------------------------------------------
-- | Common backlight plugin bits
-- --
-- Use the custom DBus interface exported by the XMonad process so I can react -- Use the custom DBus interface exported by the XMonad process so I can react
-- to signals spawned by commands -- to signals spawned by commands
module Xmobar.Plugins.BacklightCommon (startBacklight) where module Xmobar.Plugins.BacklightCommon (startBacklight) where
import DBus import Data.Internal.DBus
import Data.Internal.DBus
import RIO
import qualified RIO.Text as T
import Xmobar.Plugins.Common
startBacklight import Xmobar.Plugins.Common
:: (MonadUnliftIO m, RealFrac a)
=> Maybe BusName startBacklight :: RealFrac a => ((Maybe a -> IO ()) -> SesClient -> IO ())
-> Maybe FilePath -> (SesClient -> IO (Maybe a)) -> String -> Callback -> IO ()
-> ((Maybe a -> DIO SimpleApp SesClient ()) -> DIO SimpleApp SesClient ()) startBacklight matchSignal callGetBrightness icon cb = do
-> DIO SimpleApp SesClient (Maybe a) withDBusClientConnection cb $ \c -> do
-> T.Text matchSignal display c
-> Callback display =<< callGetBrightness c
-> m () where
startBacklight n name matchSignal callGetBrightness icon cb = do formatBrightness b = return $ icon ++ show (round b :: Integer) ++ "%"
withDBusClientConnection cb n name $ \c -> withDIO c $ do display = displayMaybe cb formatBrightness
matchSignal dpy
dpy =<< callGetBrightness
where
formatBrightness b = return $ T.concat [icon, T.pack $ show (round b :: Integer), "%"]
dpy = displayMaybe cb formatBrightness

View File

@ -1,5 +1,5 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Bluetooth plugin -- | Bluetooth plugin
-- --
-- Use the bluez interface on DBus to check status -- Use the bluez interface on DBus to check status
-- --
@ -7,89 +7,97 @@
-- Manager. The adapter is located at path "/org/bluez/hci<X>" where X is -- Manager. The adapter is located at path "/org/bluez/hci<X>" where X is
-- usually 0, and each device is "/org/bluez/hci<X>/<MAC_ADDRESS>". -- usually 0, and each device is "/org/bluez/hci<X>/<MAC_ADDRESS>".
-- --
-- Simple and somewhat crude way to do this is to have two monitors, one -- This plugin will reflect if the adapter is powered and if any device is
-- watching the powered state of the adaptor and one listening for connection -- connected to it. The rough outline for this procedure:
-- changes. The former is easy since this is just one /org/bluez/hciX. For the -- 1) get the adapter from the object manager
-- latter, each 'Connected' property is embedded in each individual device path -- 2) get all devices associated with the adapter using the object interface
-- on `org.bluez.Device1', so just watch the entire bluez bus for property -- 3) determine if the adapter is powered
-- changes and filter those that correspond to the aforementioned -- 4) determine if any devices are connected
-- interface/property. Track all this in a state which keeps the powered -- 5) format the icon; powered vs not powered controls the color and connected
-- property and a running list of connected devices. -- vs not connected controls the icon (connected bluetooth symbol has two
-- dots flanking it)
--
-- Step 3 can be accomplished using the "org.bluez.Adapter1" interface and
-- querying the "Powered" property. Step 4 can be done using the
-- "org.bluez.Device1" interface and the "Connected" property for each device
-- path. Since these are properties, we can asynchronously read changes to them
-- via the "PropertiesChanged" signal.
--
-- If any devices are added/removed, steps 2-4 will need to be redone and any
-- listeners will need to be updated. (TODO not sure which signals to use in
-- determining if a device is added)
-- --
-- TODO also not sure if I need to care about multiple adapters and/or the -- TODO also not sure if I need to care about multiple adapters and/or the
-- adapter changing. For now it should just get the first adaptor and only pay -- adapter changing.
-- attention to devices associated with it.
module Xmobar.Plugins.Bluetooth module Xmobar.Plugins.Bluetooth
( Bluetooth (..) ( Bluetooth(..)
, btAlias , btAlias
, btDep , btDep
) ) where
where
import DBus import Control.Concurrent.MVar
import DBus.Client import Control.Monad
import Data.Internal.DBus
import Data.Internal.XIO
import RIO
import RIO.FilePath
import RIO.List
import qualified RIO.Map as M
import qualified RIO.Set as S
import qualified RIO.Text as T
import XMonad.Internal.DBus.Common
import Xmobar
import Xmobar.Plugins.Common
btAlias :: T.Text import Data.Internal.DBus
import Data.Internal.Dependency
import Data.List
import Data.List.Split
import qualified Data.Map as M
import Data.Maybe
import DBus
import DBus.Client
import XMonad.Internal.DBus.Common
import Xmobar
import Xmobar.Plugins.Common
btAlias :: String
btAlias = "bluetooth" btAlias = "bluetooth"
btDep :: DBusDependency_ SysClient btDep :: DBusDependency_ SysClient
btDep = btDep = Endpoint [Package Official "bluez"] btBus btOMPath omInterface
Endpoint [Package Official "bluez"] btBus btOMPath omInterface $ $ Method_ getManagedObjects
Method_ getManagedObjects
data Bluetooth = Bluetooth Icons Colors deriving (Read, Show) data Bluetooth = Bluetooth Icons Colors deriving (Read, Show)
instance Exec Bluetooth where instance Exec Bluetooth where
alias (Bluetooth _ _) = T.unpack btAlias alias (Bluetooth _ _) = btAlias
start (Bluetooth icons colors) cb = start (Bluetooth icons colors) cb =
withDBusClientConnection cb Nothing (Just "bluetooth.log") $ withDBusClientConnection cb $ startAdapter icons colors cb
startAdapter icons colors cb
startAdapter startAdapter :: Icons -> Colors -> Callback -> SysClient -> IO ()
:: Icons
-> Colors
-> Callback
-> NamedSysConnection
-> RIO SimpleApp ()
startAdapter is cs cb cl = do startAdapter is cs cb cl = do
ot <- getBtObjectTree cl
state <- newMVar emptyState state <- newMVar emptyState
let dpy cb' = displayIcon cb' (iconFormatter is cs) let display = displayIcon cb (iconFormatter is cs) state
mapRIO (PluginEnv cl state dpy cb) $ do forM_ (findAdapter ot) $ \adapter -> do
ot <- getBtObjectTree -- set up adapter
case findAdaptor ot of initAdapter state adapter cl
Nothing -> logError "could not find bluetooth adapter" -- TODO this step could fail; at least warn the user...
Just adaptor -> do void $ addAdaptorListener state display adapter cl
initAdapterState adaptor -- set up devices on the adapter (and listeners for adding/removing devices)
initDevicesState adaptor ot let devices = findDevices adapter ot
startAdaptorListener adaptor addDeviceAddedListener state display adapter cl
startConnectedListener adaptor addDeviceRemovedListener state display adapter cl
pluginDisplay forM_ devices $ \d -> addAndInitDevice state display d cl
-- after setting things up, show the icon based on the initialized state
display
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Icon Display -- | Icon Display
-- --
-- Color corresponds to the adaptor powered state, and the icon corresponds to -- Color corresponds to the adaptor powered state, and the icon corresponds to
-- if it is paired or not. If the adaptor state is undefined, display "N/A" -- if it is paired or not. If the adaptor state is undefined, display "N/A"
type IconFormatter = (Maybe Bool -> Bool -> T.Text) type IconFormatter = (Maybe Bool -> Bool -> String)
type Icons = (T.Text, T.Text) type Icons = (String, String)
displayIcon :: Callback -> IconFormatter -> BTIO () displayIcon :: Callback -> IconFormatter -> MutableBtState -> IO ()
displayIcon callback formatter = displayIcon callback formatter =
liftIO . callback . T.unpack . uncurry formatter =<< readState callback . uncurry formatter <=< readState
-- TODO maybe I want this to fail when any of the device statuses are Nothing -- TODO maybe I want this to fail when any of the device statuses are Nothing
iconFormatter :: Icons -> Colors -> IconFormatter iconFormatter :: Icons -> Colors -> IconFormatter
@ -99,176 +107,177 @@ iconFormatter (iconConn, iconDisc) cs powered connected =
icon = if connected then iconConn else iconDisc icon = if connected then iconConn else iconDisc
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Connection State -- | Connection State
--
-- The signal handlers all run on separate threads, yet the icon depends on
-- the state reflected by all these signals. The best (only?) way to do this is
-- is to track the shared state of the bluetooth adaptor and its devices using
-- an MVar.
type BTIO = PluginIO BtState SysClient data BTDevice = BTDevice
{ btDevConnected :: Maybe Bool
, btDevSigHandler :: SignalHandler
}
type ConnectedDevices = M.Map ObjectPath BTDevice
data BtState = BtState data BtState = BtState
{ btDevices :: S.Set ObjectPath { btDevices :: ConnectedDevices
, btPowered :: Maybe Bool , btPowered :: Maybe Bool
} }
type MutableBtState = MVar BtState
emptyState :: BtState emptyState :: BtState
emptyState = emptyState = BtState
BtState { btDevices = M.empty
{ btDevices = S.empty , btPowered = Nothing
, btPowered = Nothing }
}
readState :: BTIO (Maybe Bool, Bool) readState :: MutableBtState -> IO (Maybe Bool, Bool)
readState = do readState state = do
p <- readPowered p <- readPowered state
c <- readDevices c <- readDevices state
return (p, not $ null c) return (p, anyDevicesConnected c)
modifyState :: (BtState -> BTIO (BtState, a)) -> BTIO a
modifyState f = do
m <- asks plugState
modifyMVar m f
beforeDisplay :: BTIO () -> BTIO ()
beforeDisplay f = f >> pluginDisplay
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Object manager -- | Object manager
findAdaptor :: ObjectTree -> Maybe ObjectPath findAdapter :: ObjectTree -> Maybe ObjectPath
findAdaptor = find (("/org/bluez/hci" `isPrefixOf`) . formatObjectPath) . M.keys findAdapter = find (("/org/bluez/hci" `isPrefixOf`) . formatObjectPath) . M.keys
-- | Search the object tree for devices which are in a connected state. findDevices :: ObjectPath -> ObjectTree -> [ObjectPath]
-- Return the object path for said devices. findDevices adapter = filter (adaptorHasDevice adapter) . M.keys
findConnectedDevices :: ObjectPath -> ObjectTree -> [ObjectPath]
findConnectedDevices adaptor =
filter (adaptorHasDevice adaptor) . M.keys . M.filter isConnectedDev
where
isConnectedDev m = Just True == lookupState m
lookupState =
fromVariant
<=< M.lookup (memberNameT devConnected)
<=< M.lookup devInterface
adaptorHasDevice :: ObjectPath -> ObjectPath -> Bool adaptorHasDevice :: ObjectPath -> ObjectPath -> Bool
adaptorHasDevice adaptor device = case splitPathNoRoot device of adaptorHasDevice adaptor device = case splitPath device of
[org, bluez, hciX, _] -> splitPathNoRoot adaptor == [org, bluez, hciX] [org, bluez, hciX, _] -> splitPath adaptor == [org, bluez, hciX]
_ -> False _ -> False
splitPathNoRoot :: ObjectPath -> [FilePath] splitPath :: ObjectPath -> [String]
splitPathNoRoot = dropWhile (== "/") . splitDirectories . formatObjectPath splitPath = splitOn "/" . dropWhile (=='/') . formatObjectPath
getBtObjectTree getBtObjectTree :: SysClient -> IO ObjectTree
:: ( HasClient env getBtObjectTree sys = callGetManagedObjects sys btBus btOMPath
, SafeClient c
, MonadReader (env c) m
, HasLogFunc (env c)
, MonadUnliftIO m
)
=> m ObjectTree
getBtObjectTree = callGetManagedObjects btBus btOMPath
btOMPath :: ObjectPath btOMPath :: ObjectPath
btOMPath = objectPath_ "/" btOMPath = objectPath_ "/"
-------------------------------------------------------------------------------- addBtOMListener :: SignalCallback -> SysClient -> IO ()
-- Adapter addBtOMListener sc = void . addInterfaceAddedListener btBus btOMPath sc
-- | Get powered state of adaptor and log the result addDeviceAddedListener :: MutableBtState -> IO () -> ObjectPath -> SysClient -> IO ()
initAdapterState :: ObjectPath -> BTIO () addDeviceAddedListener state display adapter client =
initAdapterState adapter = do addBtOMListener addDevice client
reply <- callGetPowered adapter
putPowered $ fromSingletonVariant reply
matchBTProperty
:: ( SafeClient c
, HasClient env
, MonadReader (env c) m
, HasLogFunc (env c)
, MonadUnliftIO m
)
=> ObjectPath
-> m (Maybe MatchRule)
matchBTProperty p = matchPropertyFull btBus (Just p)
-- | Start a listener that monitors changes to the powered state of an adaptor
startAdaptorListener :: ObjectPath -> BTIO ()
startAdaptorListener adaptor = do
res <- matchBTProperty adaptor
case res of
Just rule -> void $ addMatchCallback rule callback
Nothing -> do
logError $
"could not add listener for prop "
<> displayMemberName adaptorPowered
<> " on path "
<> displayObjectPath adaptor
where where
callback sig = addDevice = pathCallback adapter display $ \d ->
withNestedDBusClientConnection Nothing Nothing $ addAndInitDevice state display d client
withSignalMatch procMatch $
matchPropertyChanged adaptorInterface adaptorPowered sig
procMatch = beforeDisplay . putPowered
callGetPowered addDeviceRemovedListener :: MutableBtState -> IO () -> ObjectPath -> SysClient -> IO ()
:: ( HasClient env addDeviceRemovedListener state display adapter sys =
, MonadReader (env c) m addBtOMListener remDevice sys
, HasLogFunc (env c) where
, SafeClient c remDevice = pathCallback adapter display $ \d -> do
, MonadUnliftIO m old <- removeDevice state d
) forM_ old $ removeMatch (toClient sys) . btDevSigHandler
=> ObjectPath
-> m [Variant]
callGetPowered adapter =
callPropertyGet btBus adapter adaptorInterface adaptorPowered
putPowered :: Maybe Bool -> BTIO () pathCallback :: ObjectPath -> IO () -> (ObjectPath -> IO ()) -> SignalCallback
putPowered ds = modifyState $ \s -> return (s {btPowered = ds}, ()) pathCallback adapter display f [device, _] = forM_ (fromVariant device) $ \d ->
when (adaptorHasDevice adapter d) $ f d >> display
pathCallback _ _ _ _ = return ()
readPowered :: BTIO (Maybe Bool) --------------------------------------------------------------------------------
readPowered = fmap btPowered $ readMVar =<< asks plugState -- | Adapter
adaptorInterface :: InterfaceName initAdapter :: MutableBtState -> ObjectPath -> SysClient -> IO ()
adaptorInterface = interfaceName_ "org.bluez.Adapter1" initAdapter state adapter client = do
reply <- callGetPowered adapter client
putPowered state $ fromSingletonVariant reply
adaptorPowered :: MemberName matchBTProperty :: SysClient -> ObjectPath -> IO (Maybe MatchRule)
matchBTProperty sys p = matchPropertyFull sys btBus (Just p)
addAdaptorListener :: MutableBtState -> IO () -> ObjectPath -> SysClient
-> IO (Maybe SignalHandler)
addAdaptorListener state display adaptor sys = do
rule <- matchBTProperty sys adaptor
forM rule $ \r -> addMatchCallback r (procMatch . matchPowered) sys
where
procMatch = withSignalMatch $ \b -> putPowered state b >> display
callGetPowered :: ObjectPath -> SysClient -> IO [Variant]
callGetPowered adapter = callPropertyGet btBus adapter adapterInterface
$ memberName_ adaptorPowered
matchPowered :: [Variant] -> SignalMatch Bool
matchPowered = matchPropertyChanged adapterInterface adaptorPowered
putPowered :: MutableBtState -> Maybe Bool -> IO ()
putPowered m ds = modifyMVar_ m (\s -> return s { btPowered = ds })
readPowered :: MutableBtState -> IO (Maybe Bool)
readPowered = fmap btPowered . readMVar
adapterInterface :: InterfaceName
adapterInterface = interfaceName_ "org.bluez.Adapter1"
adaptorPowered :: String
adaptorPowered = "Powered" adaptorPowered = "Powered"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Devices -- | Devices
initDevicesState :: ObjectPath -> ObjectTree -> BTIO () addAndInitDevice :: MutableBtState -> IO () -> ObjectPath -> SysClient -> IO ()
initDevicesState adaptor ot = do addAndInitDevice state display device client = do
let devices = findConnectedDevices adaptor ot sh <- addDeviceListener state display device client
modifyState $ \s -> return (s {btDevices = S.fromList devices}, ()) -- TODO add some intelligent error messages here
forM_ sh $ \s -> initDevice state s device client
startConnectedListener :: ObjectPath -> BTIO () initDevice :: MutableBtState -> SignalHandler -> ObjectPath -> SysClient -> IO ()
startConnectedListener adaptor = do initDevice state sh device sys = do
reply <- matchPropertyFull btBus Nothing reply <- callGetConnected device sys
case reply of void $ insertDevice state device $
Just rule -> do BTDevice { btDevConnected = fromVariant =<< listToMaybe reply
void $ addMatchCallbackSignal rule callback , btDevSigHandler = sh
logInfo $ "Started listening for device connections on " <> adaptor_ }
Nothing -> logError "Could not listen for connection changes"
addDeviceListener :: MutableBtState -> IO () -> ObjectPath -> SysClient
-> IO (Maybe SignalHandler)
addDeviceListener state display device sys = do
rule <- matchBTProperty sys device
forM rule $ \r -> addMatchCallback r (procMatch . matchConnected) sys
where where
adaptor_ = displayWrapQuote $ displayObjectPath adaptor procMatch = withSignalMatch $ \c -> updateDevice state device c >> display
callback sig =
withNestedDBusClientConnection Nothing Nothing $ do
let devpath = signalPath sig
when (adaptorHasDevice adaptor devpath) $
withSignalMatch (update devpath) $
matchConnected $
signalBody sig
matchConnected = matchPropertyChanged devInterface devConnected
update _ Nothing = return ()
update devpath (Just x) = do
let f = if x then S.insert else S.delete
beforeDisplay $
modifyState $
\s -> return (s {btDevices = f devpath $ btDevices s}, ())
readDevices :: BTIO (S.Set ObjectPath) matchConnected :: [Variant] -> SignalMatch Bool
readDevices = fmap btDevices $ readMVar =<< asks plugState matchConnected = matchPropertyChanged devInterface devConnected
callGetConnected :: ObjectPath -> SysClient -> IO [Variant]
callGetConnected p = callPropertyGet btBus p devInterface $ memberName_ devConnected
insertDevice :: MutableBtState -> ObjectPath -> BTDevice -> IO Bool
insertDevice m device dev = modifyMVar m $ \s -> do
let new = M.insert device dev $ btDevices s
return (s { btDevices = new }, anyDevicesConnected new)
updateDevice :: MutableBtState -> ObjectPath -> Maybe Bool -> IO Bool
updateDevice m device status = modifyMVar m $ \s -> do
let new = M.update (\d -> Just d { btDevConnected = status }) device $ btDevices s
return (s { btDevices = new }, anyDevicesConnected new)
anyDevicesConnected :: ConnectedDevices -> Bool
anyDevicesConnected = or . mapMaybe btDevConnected . M.elems
removeDevice :: MutableBtState -> ObjectPath -> IO (Maybe BTDevice)
removeDevice m device = modifyMVar m $ \s -> do
let devs = btDevices s
return (s { btDevices = M.delete device devs }, M.lookup device devs)
readDevices :: MutableBtState -> IO ConnectedDevices
readDevices = fmap btDevices . readMVar
devInterface :: InterfaceName devInterface :: InterfaceName
devInterface = interfaceName_ "org.bluez.Device1" devInterface = interfaceName_ "org.bluez.Device1"
devConnected :: MemberName devConnected :: String
devConnected = "Connected" devConnected = "Connected"

View File

@ -1,32 +1,26 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Clevo Keyboard plugin -- | Clevo Keyboard plugin
-- --
-- Use the custom DBus interface exported by the XMonad process so I can react -- Use the custom DBus interface exported by the XMonad process so I can react
-- to signals spawned by commands -- to signals spawned by commands
module Xmobar.Plugins.ClevoKeyboard module Xmobar.Plugins.ClevoKeyboard
( ClevoKeyboard (..) ( ClevoKeyboard(..)
, ckAlias , ckAlias
) ) where
where
import RIO import Xmobar
import qualified RIO.Text as T
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
import Xmobar
import Xmobar.Plugins.BacklightCommon
newtype ClevoKeyboard = ClevoKeyboard T.Text deriving (Read, Show) import Xmobar.Plugins.BacklightCommon
ckAlias :: T.Text import XMonad.Internal.DBus.Brightness.ClevoKeyboard
newtype ClevoKeyboard = ClevoKeyboard String deriving (Read, Show)
ckAlias :: String
ckAlias = "clevokeyboard" ckAlias = "clevokeyboard"
instance Exec ClevoKeyboard where instance Exec ClevoKeyboard where
alias (ClevoKeyboard _) = T.unpack ckAlias alias (ClevoKeyboard _) = ckAlias
start (ClevoKeyboard icon) = start (ClevoKeyboard icon) =
startBacklight startBacklight matchSignalCK callGetBrightnessCK icon
(Just "org.xmobar.clevo")
(Just "clevo_kbd.log")
matchSignalCK
callGetBrightnessCK
icon

View File

@ -4,137 +4,60 @@ module Xmobar.Plugins.Common
, procSignalMatch , procSignalMatch
, na , na
, fromSingletonVariant , fromSingletonVariant
, withNestedDBusClientConnection
, withDBusClientConnection , withDBusClientConnection
, Callback , Callback
, Colors (..) , Colors(..)
, displayMaybe , displayMaybe
, displayMaybe' , displayMaybe'
, xmobarFGColor , xmobarFGColor
, PluginEnv (..)
, PluginIO
, pluginDisplay
) )
where where
import DBus import Control.Monad
import DBus.Client
import Data.Internal.DBus
import Data.Internal.XIO
import RIO
import qualified RIO.Text as T
import XMonad.Hooks.DynamicLog (xmobarColor)
data PluginEnv s c = PluginEnv import Data.Internal.DBus
{ plugClient :: !(NamedConnection c)
, plugState :: !(MVar s)
, plugDisplay :: !(Callback -> PluginIO s c ())
, plugCallback :: !Callback
, plugEnv :: !SimpleApp
}
pluginDisplay :: PluginIO s c () import DBus
pluginDisplay = do import DBus.Client
cb <- asks plugCallback
dpy <- asks plugDisplay
dpy cb
type PluginIO s c = RIO (PluginEnv s c) import XMonad.Hooks.DynamicLog (xmobarColor)
instance HasClient (PluginEnv s) where
clientL = lens plugClient (\x y -> x {plugClient = y})
instance HasLogFunc (PluginEnv s c) where
logFuncL = lens plugEnv (\x y -> x {plugEnv = y}) . logFuncL
-- use string here since all the callbacks in xmobar use strings :(
type Callback = String -> IO () type Callback = String -> IO ()
data Colors = Colors data Colors = Colors
{ colorsOn :: T.Text { colorsOn :: String
, colorsOff :: T.Text , colorsOff :: String
} }
deriving (Eq, Show, Read) deriving (Eq, Show, Read)
startListener startListener :: (SafeClient c, IsVariant a) => MatchRule -> (c -> IO [Variant])
:: ( HasClient env -> ([Variant] -> SignalMatch a) -> (a -> IO String) -> Callback
, MonadReader (env c) m -> c -> IO ()
, MonadUnliftIO m startListener rule getProp fromSignal toColor cb client = do
, SafeClient c reply <- getProp client
, IsVariant a
)
=> MatchRule
-> m [Variant]
-> ([Variant] -> SignalMatch a)
-> (a -> m T.Text)
-> Callback
-> m ()
startListener rule getProp fromSignal toColor cb = do
reply <- getProp
displayMaybe cb toColor $ fromSingletonVariant reply displayMaybe cb toColor $ fromSingletonVariant reply
void $ addMatchCallback rule (procMatch . fromSignal) void $ addMatchCallback rule (procMatch . fromSignal) client
where where
procMatch = procSignalMatch cb toColor procMatch = procSignalMatch cb toColor
procSignalMatch procSignalMatch :: Callback -> (a -> IO String) -> SignalMatch a -> IO ()
:: MonadUnliftIO m => Callback -> (a -> m T.Text) -> SignalMatch a -> m ()
procSignalMatch cb f = withSignalMatch (displayMaybe cb f) procSignalMatch cb f = withSignalMatch (displayMaybe cb f)
colorText :: Colors -> Bool -> T.Text -> T.Text colorText :: Colors -> Bool -> String -> String
colorText Colors {colorsOn = c} True = xmobarFGColor c colorText Colors { colorsOn = c } True = xmobarFGColor c
colorText Colors {colorsOff = c} False = xmobarFGColor c colorText Colors { colorsOff = c } False = xmobarFGColor c
xmobarFGColor :: T.Text -> T.Text -> T.Text xmobarFGColor :: String -> String -> String
xmobarFGColor c = T.pack . xmobarColor (T.unpack c) "" . T.unpack xmobarFGColor c = xmobarColor c ""
na :: T.Text na :: String
na = "N/A" na = "N/A"
displayMaybe :: MonadUnliftIO m => Callback -> (a -> m T.Text) -> Maybe a -> m () displayMaybe :: Callback -> (a -> IO String) -> Maybe a -> IO ()
displayMaybe cb f = (liftIO . cb . T.unpack) <=< maybe (return na) f displayMaybe cb f = cb <=< maybe (return na) f
displayMaybe' :: MonadUnliftIO m => Callback -> (a -> m ()) -> Maybe a -> m () displayMaybe' :: Callback -> (a -> IO ()) -> Maybe a -> IO ()
displayMaybe' cb = maybe (liftIO $ cb $ T.unpack na) displayMaybe' cb = maybe (cb na)
withDBusClientConnection withDBusClientConnection :: SafeClient c => Callback -> (c -> IO ()) -> IO ()
:: (MonadUnliftIO m, SafeClient c) withDBusClientConnection cb f = displayMaybe' cb f =<< getDBusClient
=> Callback
-> Maybe BusName
-> Maybe FilePath
-> (NamedConnection c -> RIO SimpleApp ())
-> m ()
withDBusClientConnection cb n logfile f =
maybe (run stderr) (`withLogFile` run) logfile
where
run h = do
logOpts <- setLogVerboseFormat True . setLogUseTime True <$> logOptionsHandle h False
withLogFunc logOpts $ \lf -> do
env <- mkSimpleApp lf Nothing
runRIO env $ displayMaybe' cb f =<< getDBusClient n
-- | Run a plugin action with a new DBus client and logfile path. This is
-- necessary for DBus callbacks which run in separate threads, which will
-- usually fire when the parent thread already exited and killed off its DBus
-- connection and closed its logfile. NOTE: unlike 'withDBusClientConnection'
-- this function will open and new logfile and client connection and close both
-- on completion. 'withDBusClientConnection' will only close the log file but
-- keep the client connection active upon termination; this client will only be
-- killed when the entire process is killed. This distinction is important
-- because callbacks only need ephemeral connections, while listeners (started
-- with 'withDBusClientConnection') need long-lasting connections.
withNestedDBusClientConnection
:: (MonadUnliftIO m, SafeClient c, MonadReader (PluginEnv s c) m)
=> Maybe BusName
-> Maybe FilePath
-> PluginIO s c ()
-> m ()
withNestedDBusClientConnection n logfile f = do
dpy <- asks plugDisplay
s <- asks plugState
cb <- asks plugCallback
let run h = do
logOpts <- setLogVerboseFormat True . setLogUseTime True <$> logOptionsHandle h False
withLogFunc logOpts $ \lf -> do
env <- mkSimpleApp lf Nothing
runRIO env $ withDBusClient_ n $ \cl -> mapRIO (PluginEnv cl s dpy cb) f
maybe (run stderr) (`withLogFile` run) logfile

View File

@ -0,0 +1,72 @@
--------------------------------------------------------------------------------
-- | Device plugin
--
-- Display different text depending on whether or not the interface has
-- connectivity
module Xmobar.Plugins.Device
( Device(..)
, devDep
) where
import Control.Monad
import Data.Internal.DBus
import Data.Internal.Dependency
import Data.Word
import DBus
import XMonad.Internal.Command.Desktop
import XMonad.Internal.DBus.Common
import Xmobar
import Xmobar.Plugins.Common
newtype Device = Device (String, String, Colors) deriving (Read, Show)
nmPath :: ObjectPath
nmPath = objectPath_ "/org/freedesktop/NetworkManager"
nmInterface :: InterfaceName
nmInterface = interfaceName_ "org.freedesktop.NetworkManager"
nmDeviceInterface :: InterfaceName
nmDeviceInterface = interfaceName_ "org.freedesktop.NetworkManager.Device"
getByIP :: MemberName
getByIP = memberName_ "GetDeviceByIpIface"
devSignal :: String
devSignal = "Ip4Connectivity"
devDep :: DBusDependency_ SysClient
devDep = Endpoint networkManagerPkgs networkManagerBus nmPath nmInterface
$ Method_ getByIP
getDevice :: SysClient -> String -> IO (Maybe ObjectPath)
getDevice sys iface = bodyToMaybe <$> callMethod' sys mc
where
mc = (methodCallBus networkManagerBus nmPath nmInterface getByIP)
{ methodCallBody = [toVariant iface]
}
getDeviceConnected :: ObjectPath -> SysClient -> IO [Variant]
getDeviceConnected path = callPropertyGet networkManagerBus path nmDeviceInterface
$ memberName_ devSignal
matchStatus :: [Variant] -> SignalMatch Word32
matchStatus = matchPropertyChanged nmDeviceInterface devSignal
instance Exec Device where
alias (Device (iface, _, _)) = iface
start (Device (iface, text, colors)) cb = do
withDBusClientConnection cb $ \sys -> do
path <- getDevice sys iface
displayMaybe' cb (listener sys) path
where
listener sys path = do
rule <- matchPropertyFull sys networkManagerBus (Just path)
-- TODO warn the user here rather than silently drop the listener
forM_ rule $ \r ->
startListener r (getDeviceConnected path) matchStatus chooseColor' cb sys
chooseColor' = return . (\s -> colorText colors s text) . (> 1)

View File

@ -1,32 +1,26 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Intel backlight plugin -- | Intel backlight plugin
-- --
-- Use the custom DBus interface exported by the XMonad process so I can react -- Use the custom DBus interface exported by the XMonad process so I can react
-- to signals spawned by commands -- to signals spawned by commands
module Xmobar.Plugins.IntelBacklight module Xmobar.Plugins.IntelBacklight
( IntelBacklight (..) ( IntelBacklight(..)
, blAlias , blAlias
) ) where
where
import RIO import Xmobar
import qualified RIO.Text as T
import XMonad.Internal.DBus.Brightness.IntelBacklight
import Xmobar
import Xmobar.Plugins.BacklightCommon
newtype IntelBacklight = IntelBacklight T.Text deriving (Read, Show) import Xmobar.Plugins.BacklightCommon
blAlias :: T.Text import XMonad.Internal.DBus.Brightness.IntelBacklight
newtype IntelBacklight = IntelBacklight String deriving (Read, Show)
blAlias :: String
blAlias = "intelbacklight" blAlias = "intelbacklight"
instance Exec IntelBacklight where instance Exec IntelBacklight where
alias (IntelBacklight _) = T.unpack blAlias alias (IntelBacklight _) = blAlias
start (IntelBacklight icon) = start (IntelBacklight icon) =
startBacklight startBacklight matchSignalIB callGetBrightnessIB icon
(Just "org.xmobar.intelbacklight")
(Just "intel_backlight.log")
matchSignalIB
callGetBrightnessIB
icon

View File

@ -1,36 +1,30 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Screensaver plugin -- | Screensaver plugin
-- --
-- Use the custom DBus interface exported by the XMonad process so I can react -- Use the custom DBus interface exported by the XMonad process so I can react
-- to signals spawned by commands -- to signals spawned by commands
module Xmobar.Plugins.Screensaver module Xmobar.Plugins.Screensaver
( Screensaver (..) ( Screensaver(..)
, ssAlias , ssAlias
) ) where
where
import Data.Internal.DBus import Xmobar
import RIO
import qualified RIO.Text as T
import XMonad.Internal.DBus.Screensaver
import Xmobar
import Xmobar.Plugins.Common
newtype Screensaver = Screensaver (T.Text, Colors) deriving (Read, Show) import XMonad.Internal.DBus.Screensaver
import Xmobar.Plugins.Common
ssAlias :: T.Text newtype Screensaver = Screensaver (String, Colors) deriving (Read, Show)
ssAlias :: String
ssAlias = "screensaver" ssAlias = "screensaver"
instance Exec Screensaver where instance Exec Screensaver where
alias (Screensaver _) = T.unpack ssAlias alias (Screensaver _) = ssAlias
start (Screensaver (text, colors)) cb = start (Screensaver (text, colors)) cb = do
withDBusClientConnection withDBusClientConnection cb $ \sys -> do
cb matchSignal display sys
(Just "org.xmobar.screensaver") display =<< callQuery sys
(Just "screensaver.log")
$ \cl -> withDIO cl $ do
matchSignal dpy
dpy =<< callQuery
where where
dpy = displayMaybe cb $ return . (\s -> colorText colors s text) display = displayMaybe cb $ return . (\s -> colorText colors s text)

124
lib/Xmobar/Plugins/VPN.hs Normal file
View File

@ -0,0 +1,124 @@
--------------------------------------------------------------------------------
-- | VPN plugin
--
-- Use the networkmanager to detect when a VPN interface is added or removed.
-- Specifically, monitor the object tree to detect paths with the interface
-- "org.freedesktop.NetworkManager.Device.Tun".
module Xmobar.Plugins.VPN
( VPN(..)
, vpnAlias
, vpnDep
) where
import Control.Concurrent.MVar
import Control.Monad
import Data.Internal.DBus
import Data.Internal.Dependency
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Set as S
import DBus
import XMonad.Internal.Command.Desktop
import XMonad.Internal.DBus.Common
import Xmobar
import Xmobar.Plugins.Common
newtype VPN = VPN (String, Colors) deriving (Read, Show)
instance Exec VPN where
alias (VPN _) = vpnAlias
start (VPN (text, colors)) cb =
withDBusClientConnection cb $ \c -> do
state <- initState c
let display = displayMaybe cb iconFormatter . Just =<< readState state
let signalCallback' f = f state display
vpnAddedListener (signalCallback' addedCallback) c
vpnRemovedListener (signalCallback' removedCallback) c
display
where
iconFormatter b = return $ colorText colors b text
--------------------------------------------------------------------------------
-- | VPN State
--
-- Maintain a set of paths which are the currently active VPNs. Most of the time
-- this will be a null or singleton set, but this setup could handle the edge
-- case of multiple VPNs being active at once without puking.
type VPNState = S.Set ObjectPath
type MutableVPNState = MVar VPNState
initState :: SysClient -> IO MutableVPNState
initState client = do
ot <- getVPNObjectTree client
newMVar $ findTunnels ot
readState :: MutableVPNState -> IO Bool
readState = fmap (not . null) . readMVar
updateState :: (ObjectPath -> VPNState -> VPNState) -> MutableVPNState
-> ObjectPath -> IO ()
updateState f state op = modifyMVar_ state $ return . f op
--------------------------------------------------------------------------------
-- | Tunnel Device Detection
--
getVPNObjectTree :: SysClient -> IO ObjectTree
getVPNObjectTree sys = callGetManagedObjects sys vpnBus vpnPath
findTunnels :: ObjectTree -> VPNState
findTunnels = S.fromList . M.keys . M.filter (elem vpnDeviceTun . M.keys)
vpnAddedListener :: SignalCallback -> SysClient -> IO ()
vpnAddedListener cb = void . addInterfaceAddedListener vpnBus vpnPath cb
vpnRemovedListener :: SignalCallback -> SysClient -> IO ()
vpnRemovedListener cb = void . addInterfaceRemovedListener vpnBus vpnPath cb
addedCallback :: MutableVPNState -> IO () -> SignalCallback
addedCallback state display [device, added] = update >> display
where
added' = fromVariant added :: Maybe (M.Map String (M.Map String Variant))
is = M.keys $ fromMaybe M.empty added'
update = updateDevice S.insert state device is
addedCallback _ _ _ = return ()
removedCallback :: MutableVPNState -> IO () -> SignalCallback
removedCallback state display [device, interfaces] = update >> display
where
is = fromMaybe [] $ fromVariant interfaces :: [String]
update = updateDevice S.delete state device is
removedCallback _ _ _ = return ()
updateDevice :: (ObjectPath -> VPNState -> VPNState) -> MutableVPNState
-> Variant -> [String] -> IO ()
updateDevice f state device interfaces = when (vpnDeviceTun `elem` interfaces) $
forM_ d $ updateState f state
where
d = fromVariant device :: Maybe ObjectPath
--------------------------------------------------------------------------------
-- | DBus Interface
--
vpnBus :: BusName
vpnBus = busName_ "org.freedesktop.NetworkManager"
vpnPath :: ObjectPath
vpnPath = objectPath_ "/org/freedesktop"
vpnDeviceTun :: String
vpnDeviceTun = "org.freedesktop.NetworkManager.Device.Tun"
vpnAlias :: String
vpnAlias = "vpn"
vpnDep :: DBusDependency_ SysClient
vpnDep = Endpoint networkManagerPkgs networkManagerBus vpnPath omInterface
$ Method_ getManagedObjects

View File

@ -1,8 +0,0 @@
libx11
libxrandr
libxinerama
libxss
alsa-lib
wireless_tools
libxft
libxpm

90
my-xmonad.cabal Normal file
View File

@ -0,0 +1,90 @@
name: my-xmonad
version: 0.1.0.0
build-type: Simple
cabal-version: >=1.10
library
hs-source-dirs: lib
exposed-modules: XMonad.Internal.Concurrent.ClientMessage
, XMonad.Internal.Concurrent.ACPIEvent
, XMonad.Internal.Concurrent.DynamicWorkspaces
, XMonad.Internal.Concurrent.VirtualBox
, XMonad.Internal.Theme
, XMonad.Internal.Notify
, XMonad.Internal.Shell
, XMonad.Internal.IO
, XMonad.Internal.Command.Desktop
, XMonad.Internal.Command.DMenu
, XMonad.Internal.Command.Power
, XMonad.Internal.DBus.Brightness.IntelBacklight
, XMonad.Internal.DBus.Brightness.ClevoKeyboard
, XMonad.Internal.DBus.Brightness.Common
, XMonad.Internal.DBus.Control
, XMonad.Internal.DBus.Common
, XMonad.Internal.DBus.Screensaver
, XMonad.Internal.DBus.Removable
, XMonad.Internal.Process
, Xmobar.Plugins.Common
, Xmobar.Plugins.BacklightCommon
, Xmobar.Plugins.Bluetooth
, Xmobar.Plugins.ClevoKeyboard
, Xmobar.Plugins.Device
, Xmobar.Plugins.IntelBacklight
, Xmobar.Plugins.Screensaver
, Xmobar.Plugins.VPN
, Data.Internal.Dependency
, Data.Internal.DBus
build-depends: X11 >= 1.9.1
, base
, bytestring >= 0.10.8.2
, colour >= 2.3.5
, containers >= 0.6.0.1
, dbus >= 1.2.7
, fdo-notify
, io-streams >= 1.5.1.0
, mtl >= 2.2.2
, unix >= 2.7.2.2
, tcp-streams >= 1.0.1.1
, text >= 1.2.3.1
, directory >= 1.3.3.0
, process >= 1.6.5.0
, filepath >= 1.4.2.1
, split >= 0.2.3.4
, xmobar
, xmonad-extras >= 0.15.2
, xmonad >= 0.13
, xmonad-contrib >= 0.13
, aeson >= 2.0.3.0
, yaml >=0.11.8.0
, unordered-containers >= 0.2.16.0
, hashable >= 1.3.5.0
, xml >= 1.3.14
ghc-options: -Wall -Werror -Wpartial-fields -fno-warn-missing-signatures
default-language: Haskell2010
executable xmonad
main-is: bin/xmonad.hs
build-depends: X11 >= 1.9.1
, base
, process >= 1.6.5.0
, my-xmonad
, xmonad >= 0.13
, xmonad-contrib >= 0.13
, lifted-base >= 0.2.3.12
default-language: Haskell2010
ghc-options: -Wall -Werror -Wpartial-fields -fno-warn-missing-signatures -threaded
executable xmobar
main-is: bin/xmobar.hs
build-depends: base
, dbus >= 1.2.7
, my-xmonad
, xmobar
, xmonad >= 0.13
, process >= 1.6.5.0
, filepath >= 1.4.2.1
, xmonad-contrib >= 0.13
, directory >= 1.3.3.0
, unix >= 2.7.2.2
default-language: Haskell2010
ghc-options: -Wall -Werror -Wpartial-fields -fno-warn-missing-signatures -threaded

View File

@ -1,111 +0,0 @@
name: xmonad-config
version: 0.1.0.0
license: BSD3
author: "Nathan Dwarshuis"
maintainer: "ndwar@yavin4.ch"
copyright: "2022 Nathan Dwarshuis"
extra-source-files:
- README.md
- fourmolu.yaml
- make_pkgs
- runtime_pkgs
- assets/icons/*
- assets/sound/*
- scripts/*
default-extensions:
- OverloadedStrings
- FlexibleContexts
- FlexibleInstances
- InstanceSigs
- MultiParamTypeClasses
- EmptyCase
- LambdaCase
- MultiWayIf
- NamedFieldPuns
- TupleSections
- DeriveFoldable
- DeriveFunctor
- DeriveGeneric
- DeriveLift
- DeriveTraversable
- DerivingStrategies
- DeriveDataTypeable
- EmptyDataDecls
- PartialTypeSignatures
- GeneralizedNewtypeDeriving
- StandaloneDeriving
- BangPatterns
- TypeOperators
- ScopedTypeVariables
- TypeApplications
- ConstraintKinds
- RankNTypes
- GADTs
- DefaultSignatures
- NoImplicitPrelude
- FunctionalDependencies
- DataKinds
- TypeFamilies
- BinaryLiterals
- ViewPatterns
dependencies:
- rio >= 0.1.21.0
- X11 >= 1.9.1
- base
- bytestring >= 0.10.8.2
- colour >= 2.3.5
- dbus >= 1.2.7
- fdo-notify
- unix >= 2.7.2.2
- text >= 1.2.3.1
- process >= 1.6.5.0
- xmobar
- xmonad-extras >= 0.15.2
- xmonad >= 0.13
- xmonad-contrib >= 0.13
- aeson >= 2.0.3.0
- yaml >=0.11.8.0
- xml >= 1.3.14
- utf8-string >= 1.0.2
- typed-process >= 0.2.8.0
- network >= 3.1.2.7
- unliftio >= 0.2.21.0
- optparse-applicative >= 0.16.1.0
ghc-options:
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wredundant-constraints
- -Wpartial-fields
- -Werror
- -O2
library:
source-dirs: lib/
executables:
xmobar: &bin
main: xmobar.hs
source-dirs: bin
dependencies:
- xmonad-config
ghc-options:
- -threaded
xmonad:
<<: *bin
main: xmonad.hs
ghc-options:
- -threaded
# this is needed to avoid writing super complex layout types
- -fno-warn-missing-signatures
vbox-start:
<<: *bin
main: vbox-start.hs
ghc-options:
- -threaded

View File

@ -1,29 +0,0 @@
#!/bin/bash
# Print list of packages to be installed via pacman
filter_type () {
# echo "$1" | jq --raw-output "select(.type==\"$2\") | .name" | sort | uniq
echo "$1" | sed -n "/$2/p" | cut -f2
}
raw=$(echo -e "$(xmonad --deps)\n$(xmobar --deps)")
# these are extra packages that pertain to processes outside xmonad but are
# still required/desired to make it work correctly
xmonad_pkgs=(xorg-xinit xorg-server autorandr picom)
mapfile -t official < <(filter_type "$raw" "Official")
mapfile -t local < <(filter_type "$raw" "AUR")
if ! pacman -Si "${official[@]}" > /dev/null; then
echo "At least one official package doesn't exist."
exit 1
fi
if ! yay -Si "${local[@]}" > /dev/null; then
echo "At least one local package doesn't exist."
exit 1
fi
echo "${xmonad_pkgs[@]}" "${official[@]}" "${local[@]}" | tr ' ' '\n'

View File

@ -1,22 +0,0 @@
#! /bin/bash
## capture a screenshot using scrot
SS_DIR="$XDG_CACHE_HOME/screenshots"
while getopts ":sw" opt; do
case ${opt} in
s)
scrot "$SS_DIR/desktop/%Y-%m-%d-%H:%M:%S_desktop.png"
notify-send "Screen captured"
;;
w)
scrot -u "$SS_DIR/window/%Y-%m-%d-%H:%M:%S-\$wx\$h.png"
notify-send "Window captured"
;;
\?)
echo "invalid option, read the code"
;;
esac
done

View File

@ -1,61 +0,0 @@
#! /bin/bash
## lock the screen using i3lock (and maybe suspend)
## usage: screenlock [SUSPEND]
# WORKAROUND make the date show up in the right place on 2+ monitor setups
# I want it to only show up on the primary screen, so use xrandr to get the
# dimensions and position of the primary monitor and calculate the date position
# from that
geometry=$(xrandr | sed -n 's/^.*primary \([0-9]*\)x[0-9]*+\([0-9]\)*+[0-9]* .*/\1 \2/p')
width=$(echo "$geometry" | cut -f1 -d" ")
xpos=$(echo "$geometry" | cut -f2 -d" ")
xoffset=$(("$xpos" + "$width" / 2))
datepos="$xoffset:600"
# lock and fork so we can suspend with the screen locked
i3lock --color=000000 \
--pass-media-keys \
--nofork \
--ignore-empty-password \
--screen=0 \
--indicator \
--inside-color=00000055 \
--insidever-color=00000055 \
--insidewrong-color=00000055 \
--ring-color=555555ff \
--ringwrong-color=ff3333ff \
--ringver-color=99ceffff \
--keyhl-color=99ceffff \
--bshl-color=9523ffff \
--line-color=00000000 \
--separator-color=00000000 \
--clock \
--verif-color=99ceffff \
--wrong-color=ff8282ff \
--time-color=ffffffff \
--time-size=72 \
--time-str="%H:%M" \
--date-color=ffffffff \
--date-size=42 \
--date-str="%b %d, %Y" \
--date-align 0 \
--date-pos="$datepos" \
--wrong-size=72 \
--verif-size=72 \
--radius=300 \
--ring-width=25 &
# suspend if we want, and if this machine is currently using a battery
batpath=/sys/class/power_supply/BAT0/status
if [ -f "$batpath" ] && \
[ "$(cat $batpath)" == "Discharging" ] && \
[ "$1" == "true" ]; then
systemctl suspend
fi
# block until the screen is unlocked (since xss-lock expects the locker to exit
# only when unlocked)
wait

View File

@ -17,8 +17,9 @@
# #
# resolver: ./custom-snapshot.yaml # resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-19.33 #resolver: lts-17.4
#resolver: nightly-2022-03-03 resolver: lts-19.10
# resolver: nightly-2022-03-03
# User packages to be built. # User packages to be built.
# Various formats can be used as shown in the example below. # Various formats can be used as shown in the example below.
@ -86,3 +87,15 @@ flags:
# #
# Allow a newer minor version of GHC than the snapshot specifies # Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor # compiler-check: newer-minor
nix:
enable: true
packages:
- xorg.libX11
- xorg.libXrandr
- xorg.libXScrnSaver
- xorg.libXext
- xorg.libXft
- xorg.libXpm
- alsa-lib
- wirelesstools
- pkg-config