Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Nathan Dwarshuis | ffa4e593bc |
|
@ -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
|
30
README.md
30
README.md
|
@ -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)
|
|
||||||
```
|
|
||||||
|
|
|
@ -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
|
|
664
bin/xmobar.hs
664
bin/xmobar.hs
|
@ -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]
|
|
||||||
|
|
1159
bin/xmonad.hs
1159
bin/xmonad.hs
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
||||||
|
|
|
@ -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]
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
|
@ -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 ()
|
||||||
|
|
|
@ -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
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -1,8 +0,0 @@
|
||||||
libx11
|
|
||||||
libxrandr
|
|
||||||
libxinerama
|
|
||||||
libxss
|
|
||||||
alsa-lib
|
|
||||||
wireless_tools
|
|
||||||
libxft
|
|
||||||
libxpm
|
|
|
@ -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
|
111
package.yaml
111
package.yaml
|
@ -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
|
|
29
runtime_pkgs
29
runtime_pkgs
|
@ -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'
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
17
stack.yaml
17
stack.yaml
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue