nix flake update
This commit is contained in:
@@ -8,6 +8,31 @@ run:
|
||||
restart:
|
||||
@scripts/taffybar-restart
|
||||
|
||||
# Restart with no vendor CSS (taffybar's built-in stylesheet) loaded.
|
||||
restart-no-vendor-css:
|
||||
@TAFFYBAR_DISABLE_VENDOR_CSS=1 scripts/taffybar-restart
|
||||
|
||||
# Restart with no user CSS loaded (ignores ~/.config/taffybar/taffybar.css and cssPaths).
|
||||
restart-no-user-css:
|
||||
@TAFFYBAR_DISABLE_USER_CSS=1 scripts/taffybar-restart
|
||||
|
||||
# Restart with no taffybar CSS loaded at all (GTK theme only).
|
||||
restart-empty-css:
|
||||
@TAFFYBAR_DISABLE_VENDOR_CSS=1 TAFFYBAR_DISABLE_USER_CSS=1 scripts/taffybar-restart
|
||||
|
||||
# Restart with an explicit colon-separated list of CSS files.
|
||||
# Example: just restart-css-paths 'menu-debug.css:taffybar.css'
|
||||
restart-css-paths paths:
|
||||
@TAFFYBAR_CSS_PATHS='{{paths}}' scripts/taffybar-restart
|
||||
|
||||
# Restart using only the minimal debug stylesheet in menu-debug.css.
|
||||
restart-menu-debug:
|
||||
@TAFFYBAR_CSS_PATHS='menu-debug.css' scripts/taffybar-restart
|
||||
|
||||
# Restart using `scratch.css` (initially empty) for bisecting CSS leakage into menus.
|
||||
restart-scratch:
|
||||
@TAFFYBAR_CSS_PATHS='scratch.css' scripts/taffybar-restart
|
||||
|
||||
# Capture the reserved top area (taffybar) on the focused monitor.
|
||||
screenshot:
|
||||
@scripts/taffybar-screenshot
|
||||
@@ -16,6 +41,20 @@ screenshot:
|
||||
screenshot-all:
|
||||
@scripts/taffybar-screenshot-all
|
||||
|
||||
# Screenshot the entire focused monitor (useful when debugging popup menus).
|
||||
screenshot-monitor:
|
||||
@scripts/taffybar-screenshot-focused-monitor
|
||||
|
||||
# Restart taffybar, pop up an SNI menu automatically (and a submenu if present),
|
||||
# then screenshot the focused monitor. Optional argument selects the SNI item
|
||||
# by substring match.
|
||||
capture-sni-menu match="":
|
||||
@if [[ -n "{{match}}" ]]; then scripts/taffybar-capture-sni-menu "{{match}}"; else scripts/taffybar-capture-sni-menu; fi
|
||||
|
||||
# Trigger an SNI menu popup via DBus (taffybar.debug).
|
||||
popup-sni-menu match="" submenu="1":
|
||||
@scripts/taffybar-popup-sni-menu "{{match}}" "{{submenu}}"
|
||||
|
||||
# Crop the top bar out of an existing screenshot (defaults to 56px high).
|
||||
crop in out="":
|
||||
@if [[ -n "{{out}}" ]]; then scripts/taffybar-crop-bar "{{in}}" "{{out}}"; else scripts/taffybar-crop-bar "{{in}}"; fi
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
packages: . taffybar/
|
||||
packages:
|
||||
.
|
||||
taffybar/
|
||||
|
||||
@@ -16,12 +16,16 @@ executable taffybar
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
ghc-prof-options: -fprof-auto
|
||||
build-depends: base
|
||||
, dbus
|
||||
, dbus-menu
|
||||
, X11
|
||||
, bytestring
|
||||
, containers
|
||||
, filepath
|
||||
, gi-gdk3
|
||||
, gi-gtk3
|
||||
, gi-gdkpixbuf
|
||||
, gi-glib
|
||||
, gtk-sni-tray
|
||||
, gtk-strut
|
||||
, haskell-gi-base
|
||||
|
||||
Submodule dotfiles/config/taffybar/taffybar updated: 2c0ddcab23...6ed126fe6e
@@ -9,13 +9,11 @@
|
||||
|
||||
/* Base typography + foreground color for the bar itself.
|
||||
*
|
||||
* IMPORTANT: menus/popovers created by SNI items are separate GtkWindows but
|
||||
* inherit style context from the "attach widget" chain. If we apply a blanket
|
||||
* `color:` rule here, it will bleed into those menus and override the GTK
|
||||
* theme, making submenu text unreadable.
|
||||
* IMPORTANT: SNI menus are popup windows but inherit style context from the
|
||||
* attach-widget chain. Avoid broad descendant selectors (especially `*`) here
|
||||
* so we don't accidentally override menu theming.
|
||||
*/
|
||||
.taffy-window .taffy-box :not(menu):not(menuitem):not(popover):not(window),
|
||||
.taffy-window .taffy-box :not(menu):not(menuitem):not(popover):not(window) * {
|
||||
.taffy-box {
|
||||
/* Most text should come from Iosevka Aile; icon glyphs (Font Awesome / Nerd
|
||||
Font PUA) should come from a Nerd Font family to avoid tiny fallback glyphs. */
|
||||
font-family: "Iosevka Aile", "Iosevka Nerd Font", "Iosevka NF", "Noto Sans", sans-serif;
|
||||
@@ -71,17 +69,22 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Make each widget's squircle background feel "solid": avoid GTK nodes and
|
||||
labels painting their own backgrounds on top of `.outer-pad`.
|
||||
Exclude menu/menuitem/popover so popup menus attached via menuAttachToWidget
|
||||
aren't forced transparent. */
|
||||
.outer-pad :not(menu):not(menuitem):not(popover):not(window),
|
||||
.inner-pad,
|
||||
.inner-pad :not(menu):not(menuitem):not(popover):not(window),
|
||||
.inner-pad :not(menu):not(menuitem):not(popover):not(window) *,
|
||||
.contents,
|
||||
.contents :not(menu):not(menuitem):not(popover):not(window),
|
||||
.contents :not(menu):not(menuitem):not(popover):not(window) * {
|
||||
/* Make each widget's squircle background feel "solid": avoid child widgets
|
||||
painting their own backgrounds on top of `.outer-pad`.
|
||||
*
|
||||
* We intentionally avoid broad descendant selectors (especially `*`) here
|
||||
* because SNI menus inherit style context via their attach-widget chain and
|
||||
* those selectors can leak into menu windows, making them transparent.
|
||||
*/
|
||||
.outer-pad label,
|
||||
.outer-pad image,
|
||||
.outer-pad button,
|
||||
.inner-pad label,
|
||||
.inner-pad image,
|
||||
.inner-pad button,
|
||||
.contents label,
|
||||
.contents image,
|
||||
.contents button {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -105,8 +108,7 @@
|
||||
inset 0 0 0 1px @pill-border,
|
||||
0 10px 24px @pill-shadow;
|
||||
}
|
||||
.workspaces .outer-pad :not(menu):not(menuitem):not(popover):not(window),
|
||||
.workspaces .outer-pad :not(menu):not(menuitem):not(popover):not(window) * {
|
||||
.workspaces .outer-pad {
|
||||
color: @font-color;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,19 +5,32 @@
|
||||
module Main (main) where
|
||||
|
||||
import Control.Monad.IO.Class (MonadIO, liftIO)
|
||||
import Control.Monad.Trans.Reader (ask)
|
||||
import Control.Applicative ((<|>))
|
||||
import Control.Concurrent.MVar (readMVar)
|
||||
import Control.Monad (void)
|
||||
import Data.Int (Int32)
|
||||
import Data.List (nub)
|
||||
import Data.IORef (newIORef, readIORef, writeIORef)
|
||||
import Data.List (find, isInfixOf, nub)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
|
||||
import Data.Maybe (catMaybes, fromMaybe, listToMaybe, mapMaybe)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified GI.GdkPixbuf.Objects.Pixbuf as Gdk
|
||||
import DBus
|
||||
import qualified DBus.Client as DBusClient
|
||||
import Data.GI.Base (castTo)
|
||||
import qualified DBusMenu
|
||||
import qualified GI.Gdk as Gdk
|
||||
import qualified GI.Gdk.Enums as GdkE
|
||||
import qualified GI.GdkPixbuf.Objects.Pixbuf as GdkPixbuf
|
||||
import qualified GI.Gtk as Gtk
|
||||
import qualified GI.GLib as GLib
|
||||
import Network.HostName (getHostName)
|
||||
import System.Environment (lookupEnv)
|
||||
import System.Environment.XDG.BaseDir (getUserConfigFile)
|
||||
import System.Log.Logger (Priority(WARNING), rootLoggerName, setLevel, updateGlobalLogger)
|
||||
import System.Log.Logger (Priority(WARNING), logM, rootLoggerName, setLevel, updateGlobalLogger)
|
||||
import System.Taffybar (startTaffybar)
|
||||
import System.Taffybar.Context (Backend (BackendWayland, BackendX11), TaffyIO, detectBackend)
|
||||
import System.Taffybar.Context (Backend (BackendWayland, BackendX11), Context(..), TaffyIO, detectBackend)
|
||||
import System.Taffybar.DBus
|
||||
import System.Taffybar.DBus.Toggle
|
||||
import System.Taffybar.Hooks (withLogLevels)
|
||||
@@ -39,7 +52,151 @@ import qualified StatusNotifier.Tray as SNITray (MenuBackend (HaskellDBusMenu),
|
||||
import System.Taffybar.Widget.Util (buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI)
|
||||
import qualified System.Taffybar.Widget.Workspaces as X11Workspaces
|
||||
import System.Taffybar.WindowIcon (pixBufFromColor)
|
||||
import Data.Ratio ((%))
|
||||
import qualified StatusNotifier.Tray as SNITray (MenuBackend (HaskellDBusMenu), defaultTrayParams, trayMenuBackend, trayOverlayScale)
|
||||
|
||||
-- ** Debug: Programmatic SNI Menu Popup
|
||||
|
||||
-- | DBus Properties.Get helper.
|
||||
propsGet :: DBusClient.Client -> BusName -> ObjectPath -> String -> String -> IO (Maybe Variant)
|
||||
propsGet client dest obj iface prop = do
|
||||
let mc =
|
||||
(methodCall obj "org.freedesktop.DBus.Properties" "Get")
|
||||
{ methodCallDestination = Just dest
|
||||
, methodCallBody = [toVariant iface, toVariant prop]
|
||||
}
|
||||
result <- DBusClient.call client mc
|
||||
case result of
|
||||
Left _ -> pure Nothing
|
||||
Right reply ->
|
||||
case methodReturnBody reply of
|
||||
[v] -> pure (fromVariant v)
|
||||
_ -> pure Nothing
|
||||
|
||||
-- | Return (bus name, object path, display string) for currently registered SNI
|
||||
-- entries from the watcher. Prefer this over RegisteredStatusNotifierItems,
|
||||
-- which is only bus names and doesn't include object paths.
|
||||
getRegisteredSNIEntries :: DBusClient.Client -> IO [(BusName, ObjectPath, String)]
|
||||
getRegisteredSNIEntries client = do
|
||||
mv <- propsGet client watcherName watcherPath "org.kde.StatusNotifierWatcher" "RegisteredSNIEntries"
|
||||
let raw :: [(String, String)]
|
||||
raw = fromMaybe [] $ mv >>= fromVariant
|
||||
pure
|
||||
[ (busName_ bus, objectPath_ path, bus <> path)
|
||||
| (bus, path) <- raw
|
||||
]
|
||||
where
|
||||
watcherName = busName_ "org.kde.StatusNotifierWatcher"
|
||||
watcherPath = objectPath_ "/StatusNotifierWatcher"
|
||||
|
||||
getSNIItemMenuPath :: DBusClient.Client -> BusName -> ObjectPath -> IO (Maybe ObjectPath)
|
||||
getSNIItemMenuPath client itemBus itemPath = do
|
||||
mv <- propsGet client itemBus itemPath "org.kde.StatusNotifierItem" "Menu"
|
||||
pure $ mv >>= fromVariant
|
||||
|
||||
-- | Pop up the first submenu we can find under a menu.
|
||||
popupFirstSubmenu :: Gtk.Menu -> IO ()
|
||||
popupFirstSubmenu rootMenu = do
|
||||
children <- Gtk.containerGetChildren rootMenu
|
||||
let go [] = pure ()
|
||||
go (w:ws) = do
|
||||
mi <- castTo Gtk.MenuItem w
|
||||
case mi of
|
||||
Nothing -> go ws
|
||||
Just menuItem -> do
|
||||
smw <- Gtk.menuItemGetSubmenu menuItem
|
||||
case smw of
|
||||
Nothing -> go ws
|
||||
Just sw -> do
|
||||
sm <- castTo Gtk.Menu sw
|
||||
case sm of
|
||||
Nothing -> go ws
|
||||
Just submenu -> do
|
||||
Gtk.widgetShowAll submenu
|
||||
Gtk.menuPopupAtWidget
|
||||
submenu
|
||||
menuItem
|
||||
GdkE.GravityNorthEast
|
||||
GdkE.GravityNorthWest
|
||||
Nothing
|
||||
go children
|
||||
|
||||
-- | When enabled by env vars, pop up an SNI menu (and a submenu if present) so
|
||||
-- we can screenshot it in automation loops.
|
||||
--
|
||||
-- Env vars:
|
||||
-- - TAFFYBAR_DEBUG_POPUP_SNI_MENU=1 to enable
|
||||
-- - TAFFYBAR_DEBUG_SNI_MATCH=<substring> to choose an item (matches the raw item id)
|
||||
debugPopupSNIMenuHook :: TaffyIO ()
|
||||
debugPopupSNIMenuHook = do
|
||||
enabled <- liftIO $ lookupEnv "TAFFYBAR_DEBUG_POPUP_SNI_MENU"
|
||||
case enabled of
|
||||
Nothing -> pure ()
|
||||
Just _ -> do
|
||||
match <- liftIO $ fromMaybe "" <$> lookupEnv "TAFFYBAR_DEBUG_SNI_MATCH"
|
||||
ctx <- ask
|
||||
-- Poll until the tray watcher has registered items; on startup this can
|
||||
-- take a few seconds.
|
||||
liftIO $ do
|
||||
triesRef <- newIORef (40 :: Int) -- ~10s at 250ms
|
||||
void $ GLib.timeoutAdd GLib.PRIORITY_LOW 250 $ do
|
||||
let client = sessionDBusClient ctx
|
||||
entries <- getRegisteredSNIEntries client
|
||||
remaining <- readIORef triesRef
|
||||
if not (null entries)
|
||||
then do
|
||||
logM "TaffybarDebug" WARNING $
|
||||
"SNI debug popup: registered entries=" <> show (length entries)
|
||||
let chosen =
|
||||
case match of
|
||||
"" -> listToMaybe entries
|
||||
_ -> find (\(_, _, disp) -> isInfixOf match disp) entries <|> listToMaybe entries
|
||||
case chosen of
|
||||
Nothing -> do
|
||||
logM "TaffybarDebug" WARNING "SNI debug popup: no suitable item found."
|
||||
pure False
|
||||
Just (itemBus, itemPath, disp) -> do
|
||||
mMenuPath <- getSNIItemMenuPath client itemBus itemPath
|
||||
case mMenuPath of
|
||||
Nothing ->
|
||||
do
|
||||
logM "TaffybarDebug" WARNING $
|
||||
"SNI debug popup: entry has no Menu property: " <> disp
|
||||
pure False
|
||||
Just menuPath -> do
|
||||
logM "TaffybarDebug" WARNING $
|
||||
"SNI debug popup: popping menu for " <> disp <> " menu=" <> show menuPath
|
||||
gtkMenu <- DBusMenu.buildMenu client itemBus menuPath
|
||||
-- Attach to the bar window if possible to keep CSS parent chain realistic.
|
||||
wins <- readMVar (existingWindows ctx)
|
||||
case wins of
|
||||
((_, win):_) -> Gtk.menuAttachToWidget gtkMenu win Nothing
|
||||
_ -> pure ()
|
||||
_ <- Gtk.onWidgetHide gtkMenu $
|
||||
void $ GLib.idleAdd GLib.PRIORITY_LOW $ do
|
||||
Gtk.widgetDestroy gtkMenu
|
||||
pure False
|
||||
|
||||
Gtk.widgetShowAll gtkMenu
|
||||
case wins of
|
||||
((_, win):_) ->
|
||||
Gtk.menuPopupAtWidget
|
||||
gtkMenu
|
||||
win
|
||||
GdkE.GravitySouthWest
|
||||
GdkE.GravityNorthWest
|
||||
Nothing
|
||||
_ -> Gtk.menuPopupAtPointer gtkMenu Nothing
|
||||
|
||||
popupFirstSubmenu gtkMenu
|
||||
pure False
|
||||
else if remaining <= 0
|
||||
then do
|
||||
logM "TaffybarDebug" WARNING "SNI debug popup: timed out waiting for tray items."
|
||||
pure False
|
||||
else do
|
||||
writeIORef triesRef (remaining - 1)
|
||||
pure True
|
||||
-- | Wrap the widget in a "TaffyBox" (via 'buildContentsBox') and add a CSS class.
|
||||
decorateWithClassAndBox :: MonadIO m => Text -> Gtk.Widget -> m Gtk.Widget
|
||||
decorateWithClassAndBox klass widget = do
|
||||
@@ -132,7 +289,7 @@ isPathCandidate name =
|
||||
T.isInfixOf "/" name ||
|
||||
any (`T.isSuffixOf` name) [".png", ".svg", ".xpm"]
|
||||
|
||||
hyprlandIconFromCandidate :: Int32 -> Text -> TaffyIO (Maybe Gdk.Pixbuf)
|
||||
hyprlandIconFromCandidate :: Int32 -> Text -> TaffyIO (Maybe GdkPixbuf.Pixbuf)
|
||||
hyprlandIconFromCandidate size name
|
||||
| isPathCandidate name =
|
||||
liftIO $ getPixbufFromFilePath (T.unpack name)
|
||||
@@ -147,7 +304,7 @@ hyprlandManualIconGetter =
|
||||
foldl maybeTCombine (return Nothing) $
|
||||
map (hyprlandIconFromCandidate size) (hyprlandIconCandidates windowData)
|
||||
|
||||
fallbackIconPixbuf :: Int32 -> TaffyIO (Maybe Gdk.Pixbuf)
|
||||
fallbackIconPixbuf :: Int32 -> TaffyIO (Maybe GdkPixbuf.Pixbuf)
|
||||
fallbackIconPixbuf size = do
|
||||
let fallbackNames =
|
||||
[ "application-x-executable"
|
||||
@@ -365,6 +522,7 @@ mkSimpleTaffyConfig hostName backend cssFiles =
|
||||
, barHeight = ScreenRatio $ 1 / 33
|
||||
, cssPaths = cssFiles
|
||||
, centerWidgets = [sniTrayWidget]
|
||||
, startupHook = debugPopupSNIMenuHook
|
||||
}
|
||||
|
||||
-- ** Entry Point
|
||||
@@ -382,4 +540,5 @@ main = do
|
||||
withLogServer $
|
||||
withLogLevels $
|
||||
withToggleServer $
|
||||
withDebugServer $
|
||||
toTaffybarConfig simpleTaffyConfig
|
||||
|
||||
28
nixos/flake.lock
generated
28
nixos/flake.lock
generated
@@ -116,11 +116,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770491193,
|
||||
"narHash": "sha256-zdnWeXmPZT8BpBo52s4oansT1Rq0SNzksXKpEcMc5lE=",
|
||||
"lastModified": 1770687088,
|
||||
"narHash": "sha256-WM353TQnhVCbgMGUqoPIsLEdF8HHtMo/dFryzBSEswI=",
|
||||
"owner": "sadjow",
|
||||
"repo": "claude-code-nix",
|
||||
"rev": "f68a2683e812d1e4f9a022ff3e0206d46347d019",
|
||||
"rev": "5fb242d2c746009f9fa3b63e9f346e8ea64328ea",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -579,11 +579,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770613567,
|
||||
"narHash": "sha256-t0wC7BrU7YwqvZcZpCB50dk37/bVcpIoE/qME/kw8PA=",
|
||||
"lastModified": 1770693358,
|
||||
"narHash": "sha256-rqUwaRsxMd9OOkDUxAlQlGfohlV8ZK4s2j5qZ9dJQVI=",
|
||||
"owner": "taffybar",
|
||||
"repo": "gtk-sni-tray",
|
||||
"rev": "fe5a75bc20228544217f96a1e6895be12e6198c7",
|
||||
"rev": "a7a72c72d719bb3a6e4c36974671f2f6581f55ff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1134,11 +1134,11 @@
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770591402,
|
||||
"narHash": "sha256-7qxOxkj11ExOhpxcsFK3O8Ktegkgw0SCq9nfoAOvjxM=",
|
||||
"lastModified": 1770673695,
|
||||
"narHash": "sha256-7lLVzlUSkAPo9LDe0/M9CdAh6HHDfzq8pfn98PXoKu0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nix",
|
||||
"rev": "e4ce788f9d8de1bc5e58002d01088cd71c6703d0",
|
||||
"rev": "845d951682008a009a9727437c9d913403053d06",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1585,11 +1585,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770410595,
|
||||
"lastModified": 1770668379,
|
||||
"narHash": "sha256-sqDK58NI/+tfIBd5gzYKXMhMv3CNtFBtnR958KqQhlk=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "01dc1d61477037e19ebe7a58c71581020bf7eea0",
|
||||
"revCount": 145,
|
||||
"rev": "f65d0110607dba433ea746d445b1b66d5ead6f6c",
|
||||
"revCount": 149,
|
||||
"type": "git",
|
||||
"url": "ssh://gitea@dev.railbird.ai:1123/railbird/secrets-flake.git"
|
||||
},
|
||||
@@ -1735,8 +1735,8 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770661157,
|
||||
"narHash": "sha256-Le3mnGVNLrD66/CV5jqMeSSQsMMMEaFrFbYzl3KCD3A=",
|
||||
"lastModified": 1770685149,
|
||||
"narHash": "sha256-3PY90Kt/8z4E0TNHBQuEzqaTfrRfUTK5HTfoIM3Z7W4=",
|
||||
"path": "/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar",
|
||||
"type": "path"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user