packages feed

bustle 0.6.2 → 0.7.1

raw patch · 33 files changed

+1971/−1371 lines, 33 filessetup-changed

Files

Bustle/Diagram.hs view
@@ -270,11 +270,12 @@ --  diagramBounds :: Diagram -> ((Double, Double), (Double, Double))-diagramBounds shapes = ((minimum (0:x1s), minimum (0:y1s))-                       ,(maximum (0:x2s), maximum (0:y2s))+diagramBounds shapes = ((minimum (0:x1s) - padding, minimum (0:y1s) - padding)+                       ,(maximum (0:x2s) + padding, maximum (0:y2s) + padding)                        )   where     (x1s, y1s, x2s, y2s) = unzip4 $ map bounds shapes+    padding              = 6  diagramDimensions :: Diagram -> (Double, Double) diagramDimensions shapes = (x2 - x1, y2 - y1)
Bustle/Loader/Pcap.hs view
@@ -1,8 +1,8 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, MultiParamTypeClasses #-} {- Bustle.Loader.Pcap: loads logs out of pcap files Copyright © 2011–2012 Collabora Ltd.-Copyright © 2017      Will Thompson+Copyright © 2017–2018 Will Thompson  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public@@ -28,11 +28,17 @@  import Data.Maybe (fromMaybe) import Data.Either (partitionEithers)+import Data.List (isSuffixOf) import qualified Data.Map as Map import Data.Map (Map)-import Control.Exception (try)+import Control.Exception (try, tryJust) import Control.Monad.State-import System.IO.Error (mkIOError, userErrorType)+import System.IO.Error ( mkIOError+                       , userErrorType+                       , isUserError+                       , ioeGetErrorString+                       , ioeSetErrorString+                       )  import Network.Pcap @@ -41,6 +47,7 @@ import qualified Data.ByteString as BS  import qualified Bustle.Types as B+import Bustle.Translation (__)  -- Conversions from dbus-core's types into Bustle's more stupid types. This -- whole section is pretty upsetting.@@ -254,7 +261,8 @@ readPcap :: FilePath          -> IO (Either IOError ([String], [B.DetailedEvent])) readPcap path = try $ do-    p <- openOffline path+    p_ <- tryJust matchSnaplenBug $ openOffline path+    p <- either ioError return p_     dlt <- datalink p     -- DLT_NULL for extremely old logs.     -- DLT_DBUS is missing: https://github.com/bos/pcap/pull/8@@ -263,3 +271,15 @@         ioError $ mkIOError userErrorType message Nothing (Just path)      liftM partitionEithers $ evalStateT (mapBodies p convert) Map.empty+  where+    snaplenErrorString = "invalid file capture length 134217728, bigger than maximum of 262144"+    snaplenBugReference = __ "libpcap 1.8.0 and 1.8.1 are incompatible with Bustle. See \+                             \https://bugs.freedesktop.org/show_bug.cgi?id=100220#c7 for \+                             \details. Distributions should apply downstream patches until \+                             \until a new upstream release is made; users should install \+                             \Bustle from Flathub, which already includes the necessary \+                             \patches: https://flathub.org/apps/details/org.freedesktop.Bustle"+    matchSnaplenBug e =+      if isUserError e && (snaplenErrorString `isSuffixOf` (ioeGetErrorString e))+          then Just $ ioeSetErrorString e snaplenBugReference+          else Nothing
+ Bustle/Missing.hs view
@@ -0,0 +1,38 @@+{-+Bustle.Missing: missing GLib bindings+Copyright © 2018 Will Thompson++This library is free software; you can redistribute it and/or+modify it under the terms of the GNU Lesser General Public+License as published by the Free Software Foundation; either+version 2.1 of the License, or (at your option) any later version.++This library is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this library; if not, write to the Free Software+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA+-}+{-# LANGUAGE ForeignFunctionInterface #-}+module Bustle.Missing+  ( formatSize+  )+where++import Foreign.C+import System.IO.Unsafe (unsafePerformIO)++import System.Glib.UTFString++foreign import ccall "g_format_size"+    g_format_size :: CULLong+                  -> IO CString++formatSize :: Int -- (could be Word64 but D-Bus' max message size is 2 ** 27)+           -> String+formatSize size = unsafePerformIO $ do+    ret <- g_format_size (fromIntegral size)+    readUTFString ret
Bustle/Monitor.hs view
@@ -1,6 +1,7 @@ {- Bustle.Monitor: Haskell binding for pcap-monitor.c Copyright © 2012 Collabora Ltd.+Copyright © 2018 Will Thompson  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public@@ -29,6 +30,7 @@  -- * Signals   , monitorMessageLogged+  , monitorStopped   ) where @@ -55,10 +57,11 @@     toGObject = GObject . castForeignPtr . unMonitor     unsafeCastGObject = Monitor . castForeignPtr . unGObject --- Dirty ugly foreign imports+-- Foreign imports foreign import ccall "bustle_pcap_monitor_new"     bustle_pcap_monitor_new :: CInt                     -> CString+                    -> CString                     -> Ptr (Ptr ())                     -> IO (Ptr Monitor) foreign import ccall "bustle_pcap_monitor_stop"@@ -73,16 +76,22 @@     Enum  -- Throws a GError if the file can't be opened, we can't get on the bus, or whatever.-monitorNew :: BusType+monitorNew :: Either BusType String            -> FilePath            -> IO Monitor-monitorNew busType filename =+monitorNew target filename =     wrapNewGObject mkMonitor $       propagateGError $ \gerrorPtr ->-        withCString filename $ \c_filename ->-          bustle_pcap_monitor_new (fromIntegral $ fromEnum busType)-                                  c_filename-                                  gerrorPtr+        withAddress $ \c_address ->+          withCString filename $ \c_filename ->+            bustle_pcap_monitor_new c_busType c_address c_filename gerrorPtr+  where+    c_busType = fromIntegral . fromEnum $ case target of+        Left busType  -> busType+        Right _       -> BusTypeNone+    withAddress f = case target of+        Left _        -> f nullPtr+        Right address -> withCString address f  monitorStop :: Monitor             -> IO ()@@ -91,14 +100,12 @@  messageLoggedHandler :: (Microseconds -> BS.ByteString -> IO ())                      -> a-                     -> Ptr ()-                     -> CInt                      -> CLong                      -> CLong                      -> Ptr CChar                      -> CUInt                      -> IO ()-messageLoggedHandler user _obj _messageObject _isIncoming sec usec blob blobLength = do+messageLoggedHandler user _obj sec usec blob blobLength = do     blobBS <- BS.packCStringLen (blob, fromIntegral blobLength)     let µsec = fromIntegral sec * (10 ^ (6 :: Int)) + fromIntegral usec     failOnGError $ user µsec blobBS@@ -107,3 +114,18 @@ monitorMessageLogged =     Signal $ \after_ obj user ->         connectGeneric "message-logged" after_ obj $ messageLoggedHandler user++stoppedHandler :: (Quark -> Int -> String -> IO ())+             -> a+             -> CUInt+             -> CInt+             -> Ptr CChar+             -> IO ()+stoppedHandler user _obj domain code messagePtr = do+    message <- peekCString messagePtr+    failOnGError $ user domain (fromIntegral code) message++monitorStopped :: Signal Monitor (Quark -> Int -> String -> IO ())+monitorStopped =+    Signal $ \after_ obj user ->+        connectGeneric "stopped" after_ obj $ stoppedHandler user
Bustle/Renderer.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveFunctor, OverloadedStrings #-}+{-# LANGUAGE DeriveFunctor, OverloadedStrings, GeneralizedNewtypeDeriving #-} {- Bustle.Renderer: render nice Cairo diagrams from a list of D-Bus messages Copyright (C) 2008 Collabora Ltd.@@ -17,7 +17,6 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA -}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Bustle.Renderer     (     -- * Processing entire logs@@ -554,7 +553,7 @@     shape $ Rule leftMargin rightMargin (current + 15)      let appColumns :: Applications -> [Double]-        appColumns = catMaybes . Map.fold ((:) . aiCurrentColumn) []+        appColumns = catMaybes . Map.foldr ((:) . aiCurrentColumn) []     xs <- (++) <$> getsApps appColumns SessionBus                <*> getsApps appColumns SystemBus     case xs of
Bustle/StatisticsPane.hs view
@@ -27,6 +27,7 @@ import Control.Monad (forM_) import Text.Printf import Graphics.UI.Gtk+import Bustle.Missing (formatSize) import Bustle.Stats import Bustle.Translation (__) import Bustle.Types (Log)@@ -40,20 +41,18 @@               }  statsPaneNew :: Builder-             -> Maybe Pixbuf-             -> Maybe Pixbuf              -> IO StatsPane-statsPaneNew builder methodIcon signalIcon = do+statsPaneNew builder = do   [frequencySW, durationSW, sizeSW] <- mapM (builderGetObject builder castToScrolledWindow)       ["frequencySW", "durationSW", "sizeSW"] -  (countStore, countView) <- newCountView methodIcon signalIcon+  (countStore, countView) <- newCountView   containerAdd frequencySW countView    (timeStore, timeView) <- newTimeView   containerAdd durationSW timeView -  (sizeStore, sizeView) <- newSizeView methodIcon signalIcon+  (sizeStore, sizeView) <- newSizeView   containerAdd sizeSW sizeView    widgetShow countView@@ -127,40 +126,23 @@ addTextStatColumn view store title f =     addStatColumn view store title (Marquee.escape . f) --- If we managed to load the method and signal icons...-maybeAddTypeIconColumn :: CellLayoutClass layout-                       => layout-                       -> ListStore a-                       -> Maybe Pixbuf-                       -> Maybe Pixbuf-                       -> (a -> Bool)-                       -> IO ()-maybeAddTypeIconColumn nameColumn store (Just m) (Just s) isMethod = do-    typeRenderer <- cellRendererPixbufNew-    cellLayoutPackStart nameColumn typeRenderer False-    cellLayoutSetAttributes nameColumn typeRenderer store $ \row ->-            [ cellPixbuf := if isMethod row then m else s ]-maybeAddTypeIconColumn _ _ _ _ _ = return ()--newCountView :: Maybe Pixbuf-             -> Maybe Pixbuf-             -> IO (ListStore FrequencyInfo, TreeView)-newCountView method signal = do+newCountView :: IO (ListStore FrequencyInfo, TreeView)+newCountView = do   countStore <- listStoreNew []   countView <- treeViewNewWithModel countStore -  set countView [ treeViewHeadersVisible := False ]+  set countView [ treeViewHeadersVisible := True ]    nameColumn <- treeViewColumnNew-  treeViewColumnSetTitle nameColumn (__ "Name")+  treeViewColumnSetTitle nameColumn (__ "Member")   set nameColumn [ treeViewColumnResizable := True                  , treeViewColumnExpand := True                  ] -  maybeAddTypeIconColumn nameColumn countStore method signal $ \fi ->-      case fiType fi of-          TallyMethod -> True-          TallySignal -> False+  addTextRenderer nameColumn countStore False $ \fi ->+      Marquee.escape $ case fiType fi of+          TallyMethod -> __ "Method"+          TallySignal -> __ "Signal"    addMemberRenderer nameColumn countStore True $ \fi ->       Marquee.formatMember (fiInterface fi) (fiMember fi)@@ -222,23 +204,8 @@             SizeError  -> Marquee.red             _          -> id -formatSize :: Int -> Marquee-formatSize s-    | s < maxB = value 1 `mappend` units (__ "B")-    | s < maxKB = value 1024 `mappend` units (__ "KB")-    | otherwise = value (1024 * 1024) `mappend` units (__ "MB")-  where-    maxB = 10000-    maxKB = 10000 * 1024--    units = Marquee.escape . (' ':)--    value factor = Marquee.escape (show (s `div` factor))--newSizeView :: Maybe Pixbuf-            -> Maybe Pixbuf-            -> IO (ListStore SizeInfo, TreeView)-newSizeView methodIcon_ signalIcon_ = do+newSizeView :: IO (ListStore SizeInfo, TreeView)+newSizeView = do   sizeStore <- listStoreNew []   sizeView <- treeViewNewWithModel sizeStore @@ -250,16 +217,17 @@                  , treeViewColumnExpand := True                  ] -  maybeAddTypeIconColumn nameColumn sizeStore methodIcon_ signalIcon_ $ \si ->-      case siType si of-          SizeSignal -> False-          -- We distinguish between call, return and error by <i> and red.-          _          -> True+  addTextRenderer nameColumn sizeStore False $ \si ->+      Marquee.escape $ case siType si of+          SizeCall   -> __ "Method call"+          SizeReturn -> __ "Method return"+          SizeError  -> __ "Error"+          SizeSignal -> __ "Signal"   addMemberRenderer nameColumn sizeStore True formatSizeInfoMember   treeViewAppendColumn sizeView nameColumn -  addStatColumn sizeView sizeStore (__ "Smallest") (formatSize . siMinSize)-  addStatColumn sizeView sizeStore (__ "Mean") (formatSize . siMeanSize)-  addStatColumn sizeView sizeStore (__ "Largest") (formatSize . siMaxSize)+  addStatColumn sizeView sizeStore (__ "Smallest") (Marquee.escape . formatSize . siMinSize)+  addStatColumn sizeView sizeStore (__ "Mean") (Marquee.escape . formatSize . siMeanSize)+  addStatColumn sizeView sizeStore (__ "Largest") (Marquee.escape . formatSize . siMaxSize)    return (sizeStore, sizeView)
Bustle/Stats.hs view
@@ -36,6 +36,7 @@ import Data.Ord (comparing)  import qualified Data.Map as Map+import qualified Data.Map.Strict import Data.Map (Map)  import Bustle.Types@@ -159,7 +160,7 @@       -> Map (SizeType, Maybe InterfaceName, MemberName) [Int]       -> Map (SizeType, Maybe InterfaceName, MemberName) [Int]     f dm = case sizeKeyRepr dm of-        Just key -> Map.insertWith' (++) key [deMessageSize dm]+        Just key -> Data.Map.Strict.insertWith (++) key [deMessageSize dm]         _        -> id      callDetails :: Message
− Bustle/Translation.hs
@@ -1,34 +0,0 @@-{-# LANGUAGE CPP #-}-module Bustle.Translation-    (-      initTranslation-    , __-    )-where--#ifdef USE_HGETTEXT-import Text.I18N.GetText-import System.Locale.SetLocale-import System.IO.Unsafe--import GetText_bustle-#endif--initTranslation :: IO ()-__ :: String -> String--#ifdef USE_HGETTEXT-initTranslation = do-    setLocale LC_ALL (Just "")-    domain <- getMessageCatalogDomain-    dir <- getMessageCatalogDir-    bindTextDomain domain (Just dir)-    textDomain (Just domain)-    return ()---- FIXME: I do not like this unsafePerformIO one little bit.-__ = unsafePerformIO . getText-#else-initTranslation = return ()-__ = id-#endif
Bustle/UI.hs view
@@ -1,6 +1,7 @@ {- Bustle.UI: displays charts of D-Bus activity Copyright © 2008–2011 Collabora Ltd.+Copyright © 2018      Will Thompson  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public@@ -46,6 +47,7 @@ import Bustle.UI.FilterDialog import Bustle.UI.OpenTwoDialog (setupOpenTwoDialog) import Bustle.UI.Recorder+import Bustle.UI.RecordAddressDialog (showRecordAddressDialog) import Bustle.UI.Util (displayError) import Bustle.StatisticsPane import Bustle.Translation (__)@@ -53,7 +55,9 @@  import qualified Control.Exception as C import System.Glib.GError (GError(..), failOnGError)-import System.Glib.Properties (objectSetPropertyMaybeString)+import System.Glib.Properties ( objectSetPropertyString+                              , objectSetPropertyMaybeString+                              )  import Graphics.UI.Gtk @@ -72,24 +76,26 @@   | SingleLog FilePath   | TwoLogs FilePath FilePath +-- Must be kept in sync with the names in the GtkBuilder file data Page =     InstructionsPage   | PleaseHoldPage   | CanvasPage   deriving-    (Enum)+    (Show)  data WindowInfo =     WindowInfo { wiWindow :: Window-               , wiHeaderBar :: Widget -- TODO+               , wiHeaderBar :: Widget -- TODO, GtkHeaderBar                , wiSave :: Button                , wiExport :: Button                , wiViewStatistics :: CheckMenuItem                , wiFilterNames :: MenuItem-               , wiNotebook :: Notebook-               , wiStatsBook :: Notebook+               , wiStack :: Stack+               , wiSidebarHeader :: Widget -- TODO, GtkHeaderBar+               , wiSidebarStack :: Stack                , wiStatsPane :: StatsPane-               , wiContentVPaned :: VPaned+               , wiContentPaned :: Paned                , wiCanvas :: Canvas (Detailed Message)                , wiDetailsView :: DetailsView @@ -98,8 +104,6 @@  data BConfig =     BConfig { debugEnabled :: Bool-            , methodIcon :: Maybe Pixbuf-            , signalIcon :: Maybe Pixbuf             }  data BState = BState { windows :: Int@@ -122,12 +126,7 @@     -- FIXME: get a real option parser     let debug = any isDebug args -    [method, signal] <- mapM loadPixbuf-        ["dfeet-method.png", "dfeet-signal.png"]-     let config = BConfig { debugEnabled = debug-                         , methodIcon = method-                         , signalIcon = signal                          }         initialState = BState { windows = 0                               , initialWindow = Nothing@@ -211,8 +210,9 @@           displayError Nothing (printf (__ "Could not read '%s'") f) (Just e)       Right () -> return () -startRecording :: B ()-startRecording = do+startRecording :: Either BusType String+               -> B ()+startRecording target = do     wi <- consumeInitialWindow      zt <- io $ getZonedTime@@ -226,7 +226,7 @@     let mwindow = Just (wiWindow wi)         progress = aChallengerAppears wi         finished = finishedRecording wi filename-    embedIO $ \r -> recorderRun filename mwindow progress+    embedIO $ \r -> recorderRun target filename mwindow progress                                 (\p -> makeCallback (finished p) r)  aChallengerAppears :: WindowInfo@@ -347,17 +347,19 @@   header <- getW castToWidget "header"    [openItem, openTwoItem] <- mapM (getW castToMenuItem) ["open", "openTwo"]-  [headerNew, headerSave, headerExport] <- mapM (getW castToButton) ["headerNew", "headerSave", "headerExport"]+  recordSessionItem <- getW castToMenuItem "recordSession"+  recordSystemItem <- getW castToMenuItem "recordSystem"+  recordAddressItem <- getW castToMenuItem "recordAddress"+  [headerSave, headerExport] <- mapM (getW castToButton) ["headerSave", "headerExport"]    viewStatistics <- getW castToCheckMenuItem "statistics"   filterNames <- getW castToMenuItem "filter"   aboutItem <- getW castToMenuItem "about" -  [newButton, openButton] <- mapM (getW castToButton) ["newButton", "openButton"]--  [nb, statsBook] <- mapM (getW castToNotebook)-      ["diagramOrNot", "statsBook"]-  contentVPaned <- getW castToVPaned "contentVPaned"+  stack <- getW castToStack "diagramOrNot"+  sidebarHeader <- getW castToWidget "sidebarHeader"+  sidebarStack <- getW castToStack "sidebarStack"+  contentPaned <- getW castToPaned "contentPaned"    -- Open two logs dialog   openTwoDialog <- embedIO $ \r ->@@ -369,14 +371,15 @@    -- File menu and related buttons   embedIO $ \r -> do-      let new = makeCallback startRecording r-      forM [headerNew, newButton] $ \button ->-          button `on` buttonActivated $ new--      let open = makeCallback openDialogue r-      onMenuItemActivate openItem open-      openButton `on` buttonActivated $ open+      onMenuItemActivate recordSessionItem $+          makeCallback (startRecording (Left BusTypeSession)) r+      onMenuItemActivate recordSystemItem $+          makeCallback (startRecording (Left BusTypeSystem)) r+      onMenuItemActivate recordAddressItem $+          showRecordAddressDialog window $ \address ->+              makeCallback (startRecording (Right address)) r +      onMenuItemActivate openItem $ makeCallback openDialogue r       onMenuItemActivate openTwoItem $ widgetShowAll openTwoDialog    -- TODO: really this wants to live in the application menu, but that entails binding GApplication,@@ -385,20 +388,12 @@   -- Similarly, the drop-down menus would look better as popovers. But here we are.   io $ onMenuItemActivate aboutItem $ showAboutDialog window -  m <- asks methodIcon-  s <- asks signalIcon-  statsPane <- io $ statsPaneNew builder m s+  statsPane <- io $ statsPaneNew builder -  details <- io $ detailsViewNew-  io $ do-      let top = detailsViewGetTop details-      panedPack2 contentVPaned top False False-      -- Hide the details by default; they'll be shown when the user selects a-      -- message.-      widgetHide top+  details <- io $ detailsViewNew builder    -- The stats start off hidden.-  io $ widgetHide statsBook+  io $ widgetHide sidebarStack    showBounds <- asks debugEnabled   canvas <- io $ canvasNew builder showBounds (updateDetailsView details)@@ -410,10 +405,11 @@                               , wiExport = headerExport                               , wiViewStatistics = viewStatistics                               , wiFilterNames = filterNames-                              , wiNotebook = nb-                              , wiStatsBook = statsBook+                              , wiStack = stack+                              , wiSidebarHeader = sidebarHeader+                              , wiSidebarStack = sidebarStack                               , wiStatsPane = statsPane-                              , wiContentVPaned = contentVPaned+                              , wiContentPaned = contentPaned                               , wiCanvas = canvas                               , wiDetailsView = details                               , wiLogDetails = logDetailsRef@@ -486,13 +482,14 @@     let (title, subtitle) = logWindowTitle logDetails     (wiWindow wi) `set` [ windowTitle := title ]     -- TODO: add to gtk2hs+    objectSetPropertyString "title" (wiHeaderBar wi) title     objectSetPropertyMaybeString "subtitle" (wiHeaderBar wi) subtitle  setPage :: MonadIO io         => WindowInfo         -> Page         -> io ()-setPage wi page = io $ notebookSetCurrentPage (wiNotebook wi) (fromEnum page)+setPage wi page = io $ stackSetVisibleChildName (wiStack wi) (show page)  displayLog :: WindowInfo            -> LogDetails@@ -505,7 +502,8 @@                        , wiViewStatistics = viewStatistics                        , wiFilterNames = filterNames                        , wiCanvas = canvas-                       , wiStatsBook = statsBook+                       , wiSidebarHeader = sidebarHeader+                       , wiSidebarStack = sidebarStack                        , wiStatsPane = statsPane                        })            logDetails@@ -534,8 +532,10 @@     viewStatistics `on` checkMenuItemToggled $ do         active <- checkMenuItemGetActive viewStatistics         if active-            then widgetShow statsBook-            else widgetHide statsBook+            then do widgetShow sidebarStack+                    widgetShow sidebarHeader+            else do widgetHide sidebarStack+                    widgetHide sidebarHeader      widgetSetSensitivity filterNames True     onMenuItemActivate filterNames $ do@@ -547,12 +547,6 @@         updateDisplayedLog wi rr'    return ()--loadPixbuf :: FilePath -> IO (Maybe Pixbuf)-loadPixbuf filename = do-  iconName <- getDataFileName $ "data/" ++ filename-  C.catch (fmap Just (pixbufNewFromFile iconName))-          (\(GError _ _ msg) -> warn (toString msg) >> return Nothing)  openDialogue :: B () openDialogue = embedIO $ \r -> do
Bustle/UI/DetailsView.hs view
@@ -29,85 +29,38 @@  import qualified DBus as D -import Bustle.Translation (__) import Bustle.Types import Bustle.Marquee import Bustle.VariantFormatter  data DetailsView =-    DetailsView { detailsTable :: Table-                , detailsTitle :: Label+    DetailsView { detailsGrid :: Grid+                , detailsType :: Stack                 , detailsPath :: Label                 , detailsMember :: Label                 , detailsBodyView :: TextView                 } -addTitle :: Table-         -> String-         -> Int-         -> IO ()-addTitle table title row = do-    label <- labelNew $ Just title-    miscSetAlignment label 0 0-    tableAttach table label 0 1 row (row + 1) [Fill] [Fill] 0 0--addValue :: Table-         -> Int-         -> IO Label-addValue table row = do-    label <- labelNew (Nothing :: Maybe String)-    miscSetAlignment label 0 0-    labelSetEllipsize label EllipsizeStart-    labelSetSelectable label True-    tableAttach table label 1 2 row (row + 1) [Expand, Fill] [Fill] 0 0-    return label--addField :: Table-         -> String-         -> Int-         -> IO Label-addField table title row = do-    addTitle table title row-    addValue table row--detailsViewNew :: IO DetailsView-detailsViewNew = do-    table <- tableNew 2 3 False-    table `set` [ tableRowSpacing := 6-                , tableColumnSpacing := 6-                ]--    title <- labelNew (Nothing :: Maybe String)-    miscSetAlignment title 0 0-    tableAttach table title 0 2 0 1 [Fill] [Fill] 0 0--    pathLabel <- addField table (__ "Path:") 1-    memberLabel <- addField table (__ "Member:") 2--    addTitle table (__ "Arguments:") 3--    view <- textViewNew-    textViewSetWrapMode view WrapWordChar-    textViewSetEditable view False--    sw <- scrolledWindowNew Nothing Nothing-    scrolledWindowSetPolicy sw PolicyAutomatic PolicyAutomatic-    containerAdd sw view--    tableAttachDefaults table sw 1 2 3 4+detailsViewNew :: Builder+               -> IO DetailsView+detailsViewNew builder = do+    grid        <- builderGetObject builder castToGrid "detailsGrid"+    type_       <- builderGetObject builder castToStack "detailsType"+    pathLabel   <- builderGetObject builder castToLabel "detailsPath"+    memberLabel <- builderGetObject builder castToLabel "detailsMember"+    view        <- builderGetObject builder castToTextView "detailsArguments" -    widgetShowAll table-    return $ DetailsView table title pathLabel memberLabel view+    return $ DetailsView grid type_ pathLabel memberLabel view -pickTitle :: Detailed Message -> Marquee-pickTitle (Detailed _ m _ _) = case m of-    MethodCall {} -> b (escape (__ "Method call"))-    MethodReturn {} -> b (escape (__ "Method return"))-    Error {} -> b (escape (__ "Error"))+pickType :: Detailed Message -> String+pickType (Detailed _ m _ _) = case m of+    MethodCall {} -> "methodCall"+    MethodReturn {} -> "methodReturn"+    Error {} -> "error"     Signal { signalDestination = d } ->-        b . escape $ case d of-            Nothing -> (__ "Signal")-            Just _  -> (__ "Directed signal")+        case d of+            Nothing -> "signal"+            Just _  -> "directedSignal"  getMemberMarkup :: Member -> String getMemberMarkup m =@@ -129,7 +82,7 @@     formatArgs = intercalate "\n" . map (format_Variant VariantStyleSignature)  detailsViewGetTop :: DetailsView -> Widget-detailsViewGetTop = toWidget . detailsTable+detailsViewGetTop = toWidget . detailsGrid  detailsViewUpdate :: DetailsView                   -> Detailed Message@@ -137,9 +90,9 @@ detailsViewUpdate d m = do     buf <- textViewGetBuffer $ detailsBodyView d     let member_ = getMember m-    labelSetMarkup (detailsTitle d) (toPangoMarkup $ pickTitle m)+    stackSetVisibleChildName (detailsType d) (pickType m)     labelSetText (detailsPath d) (maybe unknown (D.formatObjectPath . path) member_)     labelSetMarkup (detailsMember d) (maybe unknown getMemberMarkup member_)     textBufferSetText buf $ formatMessage m   where-    unknown = __ "<unknown>"+    unknown = ""
+ Bustle/UI/RecordAddressDialog.hs view
@@ -0,0 +1,59 @@+{-+Bustle.UI.RecordAddressDialog: a dialog to prompt the user to open two log files+Copyright © 2018 Will Thompson++This library is free software; you can redistribute it and/or+modify it under the terms of the GNU Lesser General Public+License as published by the Free Software Foundation; either+version 2.1 of the License, or (at your option) any later version.++This library is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public+License along with this library; if not, write to the Free Software+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA+-}+module Bustle.UI.RecordAddressDialog+  (+    showRecordAddressDialog+  )+where++import Control.Monad (when)++import Data.Text (Text)+import Graphics.UI.Gtk+import System.Glib.UTFString (stringLength)++import Paths_bustle++showRecordAddressDialog :: Window+                        -> (String -> IO ())+                        -> IO ()+showRecordAddressDialog parent callback = do+    builder <- builderNew+    builderAddFromFile builder =<< getDataFileName "data/RecordAddressDialog.ui"++    dialog <- builderGetObject builder castToDialog "recordAddressDialog"+    entry <- builderGetObject builder castToEntry "recordAddressEntry"+    record <- builderGetObject builder castToButton "recordAddressRecord"++    dialog `set` [ windowTransientFor := parent ]+    entry `on` editableChanged $ do+        address <- entryGetText entry+        -- TODO: validate with g_dbus_is_suppported_address() once+        -- https://gitlab.gnome.org/GNOME/glib/merge_requests/103 is in a+        -- release.+        widgetSetSensitive record (stringLength (address :: Text) /= 0)++    dialog `after` response $ \resp -> do+        when (resp == ResponseAccept) $ do+            address <- entryGetText entry+            callback address++        widgetDestroy dialog++    widgetShowAll dialog
Bustle/UI/Recorder.hs view
@@ -1,6 +1,7 @@ {- Bustle.UI.Recorder: dialogs for driving Bustle.Monitor Copyright © 2012 Collabora Ltd.+Copyright © 2018 Will Thompson  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public@@ -20,6 +21,7 @@   (     recorderChooseFile   , recorderRun+  , BusType(..)   ) where @@ -31,6 +33,8 @@ import Text.Printf  import qualified Control.Exception as C+import System.GIO.Enums (IOErrorEnum(IoErrorCancelled))+import System.Glib.GObject (quarkFromString) import System.Glib.GError import Graphics.UI.Gtk @@ -79,20 +83,22 @@                 i <- takeMVar n                 let j = i + (length pending)                 labelSetMarkup label $-                    (printf (__ "Logged <b>%u</b> messages…") j :: String)+                    (printf (__ "Logged <b>%u</b> messages&#8230;") j :: String)                 putMVar n j                  incoming rr'          return True -recorderRun :: FilePath+recorderRun :: Either BusType String+            -> FilePath             -> Maybe Window             -> RecorderIncomingCallback             -> RecorderFinishedCallback             -> IO ()-recorderRun filename mwindow incoming finished = C.handle newFailed $ do-    monitor <- monitorNew BusTypeSession filename+recorderRun target filename mwindow incoming finished = C.handle newFailed $ do+    -- TODO: I'm pretty sure the monitor is leaked+    monitor <- monitorNew target filename     dialog <- dialogNew      dialog `set` (map (windowTransientFor :=) (maybeToList mwindow))@@ -100,10 +106,9 @@                  , windowTitle := ""                  ] -     label <- labelNew (Nothing :: Maybe String)     labelSetMarkup label $-        (printf (__ "Logged <b>%u</b> messages…") (0 :: Int) :: String)+        (printf (__ "Logged <b>%u</b> messages&#8230;") (0 :: Int) :: String)     loaderStateRef <- newMVar Map.empty     pendingRef <- newMVar []     let updateLabel µs body = do@@ -119,7 +124,6 @@                         modifyMVar_ pendingRef $ \pending -> return (message:pending)                   | otherwise -> return () -    handlerId <- monitor `on` monitorMessageLogged $ updateLabel     n <- newMVar (0 :: Int)     processor <- processBatch pendingRef n label incoming     processorId <- timeoutAdd processor 200@@ -133,23 +137,42 @@     boxPackStart hbox label PackGrow 0     boxPackStart vbox hbox PackGrow 0 -    dialogAddButton dialog "gtk-media-stop" ResponseClose+    handlerId <- monitor `on` monitorMessageLogged $ updateLabel+    monitor `on` monitorStopped $ \domain code message -> do+        handleError domain code message -    dialog `after` response $ \_ -> do-        monitorStop monitor         signalDisconnect handlerId+         spinnerStop spinner-        timeoutRemove processorId+        widgetDestroy dialog+         -- Flush out any last messages from the queue.+        timeoutRemove processorId         processor-        widgetDestroy dialog+         hadOutput <- liftM (/= 0) (readMVar n)         finished hadOutput +    dialogAddButton dialog "gtk-media-stop" ResponseClose++    dialog `after` response $ \_ -> do+        monitorStop monitor+     widgetShowAll dialog   where-    newFailed (GError _ _ message) = do-        displayError mwindow (toString message) Nothing+    -- Filter out IoErrorCancelled. In theory one should use+    --   catchGErrorJust IoErrorCancelled computation (\_ -> return ())+    -- but IOErrorEnum does not have an instance for GError domain.+    newFailed (GError domain code message) = do+        finished False+        handleError domain code message++    handleError domain code message = do+        gIoErrorQuark <- quarkFromString "g-io-error-quark"+        let cancelled = fromEnum IoErrorCancelled+        when (not (domain == gIoErrorQuark && code == cancelled)) $ do+            displayError mwindow (toString message) Nothing+  recorderChooseFile :: FilePath                    -> Maybe Window
HACKING.md view
@@ -3,10 +3,10 @@  Grab the latest code from git: -    git clone git://anongit.freedesktop.org/bustle+    git clone https://gitlab.freedesktop.org/bustle/bustle.git -and get stuck in! Please submit patches, or links to git branches, as-bugs on <https://bugs.freedesktop.org/enter_bug.cgi?product=Bustle>.+and get stuck in! Please file bugs and merge requests at+<https://gitlab.freedesktop.org/bustle/bustle>.  In new code, try to follow <https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md>.@@ -17,8 +17,9 @@ ================  * Ideally, automate the steps below+* Write news in `NEWS.md` and `data/org.freedesktop.Bustle.appdata.xml.in`+* Update `po/messages.pot` * Update version number in `bustle.cabal`-* Write news in `NEWS.md`  ```sh # Tag release, build and sign the tarballs
LICENSE view
@@ -5,11 +5,7 @@ depends on, is covered by the GNU GPL version 3. Hence, Bustle binaries may be distributed under the GNU GPL version 3. -dfeet-method.png and dfeet-signal.png were, as their names suggest,-taken from D-Feet, and are licensed under the GNU GPL Version 2 or-(at your option) any later version.--The LGPL v2.1, GPL v2 and GPL v3 follow.+The LGPL v2.1 and GPL v3 follow.  --- @@ -520,348 +516,6 @@   Ty Coon, President of Vice  That's all there is to it!-------		    GNU GENERAL PUBLIC LICENSE-		       Version 2, June 1991-- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA- Everyone is permitted to copy and distribute verbatim copies- of this license document, but changing it is not allowed.--			    Preamble--  The licenses for most software are designed to take away your-freedom to share and change it.  By contrast, the GNU General Public-License is intended to guarantee your freedom to share and change free-software--to make sure the software is free for all its users.  This-General Public License applies to most of the Free Software-Foundation's software and to any other program whose authors commit to-using it.  (Some other Free Software Foundation software is covered by-the GNU Lesser General Public License instead.)  You can apply it to-your programs, too.--  When we speak of free software, we are referring to freedom, not-price.  Our General Public Licenses are designed to make sure that you-have the freedom to distribute copies of free software (and charge for-this service if you wish), that you receive source code or can get it-if you want it, that you can change the software or use pieces of it-in new free programs; and that you know you can do these things.--  To protect your rights, we need to make restrictions that forbid-anyone to deny you these rights or to ask you to surrender the rights.-These restrictions translate to certain responsibilities for you if you-distribute copies of the software, or if you modify it.--  For example, if you distribute copies of such a program, whether-gratis or for a fee, you must give the recipients all the rights that-you have.  You must make sure that they, too, receive or can get the-source code.  And you must show them these terms so they know their-rights.--  We protect your rights with two steps: (1) copyright the software, and-(2) offer you this license which gives you legal permission to copy,-distribute and/or modify the software.--  Also, for each author's protection and ours, we want to make certain-that everyone understands that there is no warranty for this free-software.  If the software is modified by someone else and passed on, we-want its recipients to know that what they have is not the original, so-that any problems introduced by others will not reflect on the original-authors' reputations.--  Finally, any free program is threatened constantly by software-patents.  We wish to avoid the danger that redistributors of a free-program will individually obtain patent licenses, in effect making the-program proprietary.  To prevent this, we have made it clear that any-patent must be licensed for everyone's free use or not licensed at all.--  The precise terms and conditions for copying, distribution and-modification follow.--		    GNU GENERAL PUBLIC LICENSE-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION--  0. This License applies to any program or other work which contains-a notice placed by the copyright holder saying it may be distributed-under the terms of this General Public License.  The "Program", below,-refers to any such program or work, and a "work based on the Program"-means either the Program or any derivative work under copyright law:-that is to say, a work containing the Program or a portion of it,-either verbatim or with modifications and/or translated into another-language.  (Hereinafter, translation is included without limitation in-the term "modification".)  Each licensee is addressed as "you".--Activities other than copying, distribution and modification are not-covered by this License; they are outside its scope.  The act of-running the Program is not restricted, and the output from the Program-is covered only if its contents constitute a work based on the-Program (independent of having been made by running the Program).-Whether that is true depends on what the Program does.--  1. You may copy and distribute verbatim copies of the Program's-source code as you receive it, in any medium, provided that you-conspicuously and appropriately publish on each copy an appropriate-copyright notice and disclaimer of warranty; keep intact all the-notices that refer to this License and to the absence of any warranty;-and give any other recipients of the Program a copy of this License-along with the Program.--You may charge a fee for the physical act of transferring a copy, and-you may at your option offer warranty protection in exchange for a fee.--  2. You may modify your copy or copies of the Program or any portion-of it, thus forming a work based on the Program, and copy and-distribute such modifications or work under the terms of Section 1-above, provided that you also meet all of these conditions:--    a) You must cause the modified files to carry prominent notices-    stating that you changed the files and the date of any change.--    b) You must cause any work that you distribute or publish, that in-    whole or in part contains or is derived from the Program or any-    part thereof, to be licensed as a whole at no charge to all third-    parties under the terms of this License.--    c) If the modified program normally reads commands interactively-    when run, you must cause it, when started running for such-    interactive use in the most ordinary way, to print or display an-    announcement including an appropriate copyright notice and a-    notice that there is no warranty (or else, saying that you provide-    a warranty) and that users may redistribute the program under-    these conditions, and telling the user how to view a copy of this-    License.  (Exception: if the Program itself is interactive but-    does not normally print such an announcement, your work based on-    the Program is not required to print an announcement.)--These requirements apply to the modified work as a whole.  If-identifiable sections of that work are not derived from the Program,-and can be reasonably considered independent and separate works in-themselves, then this License, and its terms, do not apply to those-sections when you distribute them as separate works.  But when you-distribute the same sections as part of a whole which is a work based-on the Program, the distribution of the whole must be on the terms of-this License, whose permissions for other licensees extend to the-entire whole, and thus to each and every part regardless of who wrote it.--Thus, it is not the intent of this section to claim rights or contest-your rights to work written entirely by you; rather, the intent is to-exercise the right to control the distribution of derivative or-collective works based on the Program.--In addition, mere aggregation of another work not based on the Program-with the Program (or with a work based on the Program) on a volume of-a storage or distribution medium does not bring the other work under-the scope of this License.--  3. You may copy and distribute the Program (or a work based on it,-under Section 2) in object code or executable form under the terms of-Sections 1 and 2 above provided that you also do one of the following:--    a) Accompany it with the complete corresponding machine-readable-    source code, which must be distributed under the terms of Sections-    1 and 2 above on a medium customarily used for software interchange; or,--    b) Accompany it with a written offer, valid for at least three-    years, to give any third party, for a charge no more than your-    cost of physically performing source distribution, a complete-    machine-readable copy of the corresponding source code, to be-    distributed under the terms of Sections 1 and 2 above on a medium-    customarily used for software interchange; or,--    c) Accompany it with the information you received as to the offer-    to distribute corresponding source code.  (This alternative is-    allowed only for noncommercial distribution and only if you-    received the program in object code or executable form with such-    an offer, in accord with Subsection b above.)--The source code for a work means the preferred form of the work for-making modifications to it.  For an executable work, complete source-code means all the source code for all modules it contains, plus any-associated interface definition files, plus the scripts used to-control compilation and installation of the executable.  However, as a-special exception, the source code distributed need not include-anything that is normally distributed (in either source or binary-form) with the major components (compiler, kernel, and so on) of the-operating system on which the executable runs, unless that component-itself accompanies the executable.--If distribution of executable or object code is made by offering-access to copy from a designated place, then offering equivalent-access to copy the source code from the same place counts as-distribution of the source code, even though third parties are not-compelled to copy the source along with the object code.--  4. You may not copy, modify, sublicense, or distribute the Program-except as expressly provided under this License.  Any attempt-otherwise to copy, modify, sublicense or distribute the Program is-void, and will automatically terminate your rights under this License.-However, parties who have received copies, or rights, from you under-this License will not have their licenses terminated so long as such-parties remain in full compliance.--  5. You are not required to accept this License, since you have not-signed it.  However, nothing else grants you permission to modify or-distribute the Program or its derivative works.  These actions are-prohibited by law if you do not accept this License.  Therefore, by-modifying or distributing the Program (or any work based on the-Program), you indicate your acceptance of this License to do so, and-all its terms and conditions for copying, distributing or modifying-the Program or works based on it.--  6. Each time you redistribute the Program (or any work based on the-Program), the recipient automatically receives a license from the-original licensor to copy, distribute or modify the Program subject to-these terms and conditions.  You may not impose any further-restrictions on the recipients' exercise of the rights granted herein.-You are not responsible for enforcing compliance by third parties to-this License.--  7. If, as a consequence of a court judgment or allegation of patent-infringement or for any other reason (not limited to patent issues),-conditions are imposed on you (whether by court order, agreement or-otherwise) that contradict the conditions of this License, they do not-excuse you from the conditions of this License.  If you cannot-distribute so as to satisfy simultaneously your obligations under this-License and any other pertinent obligations, then as a consequence you-may not distribute the Program at all.  For example, if a patent-license would not permit royalty-free redistribution of the Program by-all those who receive copies directly or indirectly through you, then-the only way you could satisfy both it and this License would be to-refrain entirely from distribution of the Program.--If any portion of this section is held invalid or unenforceable under-any particular circumstance, the balance of the section is intended to-apply and the section as a whole is intended to apply in other-circumstances.--It is not the purpose of this section to induce you to infringe any-patents or other property right claims or to contest validity of any-such claims; this section has the sole purpose of protecting the-integrity of the free software distribution system, which is-implemented by public license practices.  Many people have made-generous contributions to the wide range of software distributed-through that system in reliance on consistent application of that-system; it is up to the author/donor to decide if he or she is willing-to distribute software through any other system and a licensee cannot-impose that choice.--This section is intended to make thoroughly clear what is believed to-be a consequence of the rest of this License.--  8. If the distribution and/or use of the Program is restricted in-certain countries either by patents or by copyrighted interfaces, the-original copyright holder who places the Program under this License-may add an explicit geographical distribution limitation excluding-those countries, so that distribution is permitted only in or among-countries not thus excluded.  In such case, this License incorporates-the limitation as if written in the body of this License.--  9. The Free Software Foundation may publish revised and/or new versions-of the General Public License from time to time.  Such new versions will-be similar in spirit to the present version, but may differ in detail to-address new problems or concerns.--Each version is given a distinguishing version number.  If the Program-specifies a version number of this License which applies to it and "any-later version", you have the option of following the terms and conditions-either of that version or of any later version published by the Free-Software Foundation.  If the Program does not specify a version number of-this License, you may choose any version ever published by the Free Software-Foundation.--  10. If you wish to incorporate parts of the Program into other free-programs whose distribution conditions are different, write to the author-to ask for permission.  For software which is copyrighted by the Free-Software Foundation, write to the Free Software Foundation; we sometimes-make exceptions for this.  Our decision will be guided by the two goals-of preserving the free status of all derivatives of our free software and-of promoting the sharing and reuse of software generally.--			    NO WARRANTY--  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,-REPAIR OR CORRECTION.--  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE-POSSIBILITY OF SUCH DAMAGES.--		     END OF TERMS AND CONDITIONS--	    How to Apply These Terms to Your New Programs--  If you develop a new program, and you want it to be of the greatest-possible use to the public, the best way to achieve this is to make it-free software which everyone can redistribute and change under these terms.--  To do so, attach the following notices to the program.  It is safest-to attach them to the start of each source file to most effectively-convey the exclusion of warranty; and each file should have at least-the "copyright" line and a pointer to where the full notice is found.--    <one line to give the program's name and a brief idea of what it does.>-    Copyright (C) <year>  <name of author>--    This program is free software; you can redistribute it and/or modify-    it under the terms of the GNU General Public License as published by-    the Free Software Foundation; either version 2 of the License, or-    (at your option) any later version.--    This program is distributed in the hope that it will be useful,-    but WITHOUT ANY WARRANTY; without even the implied warranty of-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the-    GNU General Public License for more details.--    You should have received a copy of the GNU General Public License along-    with this program; if not, write to the Free Software Foundation, Inc.,-    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.--Also add information on how to contact you by electronic and paper mail.--If the program is interactive, make it output a short notice like this-when it starts in an interactive mode:--    Gnomovision version 69, Copyright (C) year name of author-    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.-    This is free software, and you are welcome to redistribute it-    under certain conditions; type `show c' for details.--The hypothetical commands `show w' and `show c' should show the appropriate-parts of the General Public License.  Of course, the commands you use may-be called something other than `show w' and `show c'; they could even be-mouse-clicks or menu items--whatever suits your program.--You should also get your employer (if you work as a programmer) or your-school, if any, to sign a "copyright disclaimer" for the program, if-necessary.  Here is a sample; alter the names:--  Yoyodyne, Inc., hereby disclaims all copyright interest in the program-  `Gnomovision' (which makes passes at compilers) written by James Hacker.--  <signature of Ty Coon>, 1 April 1989-  Ty Coon, President of Vice--This General Public License does not permit incorporating your program into-proprietary programs.  If your program is a subroutine library, you may-consider it more useful to permit linking proprietary applications with the-library.  If this is what you want to do, use the GNU Lesser General-Public License instead of this License.  --- 
Makefile view
@@ -1,4 +1,4 @@-CFLAGS = -g -O2 -Wall -Wunused+CFLAGS = -g -O2 -Wall -Wunused -Waddress DBUS_FLAGS = $(shell pkg-config --cflags --libs dbus-1) GIO_FLAGS := $(shell pkg-config --cflags --libs 'glib-2.0 >= 2.26' gio-2.0 gio-unix-2.0) PCAP_FLAGS := $(shell pcap-config --cflags pcap-config --libs)@@ -32,10 +32,10 @@ 	help2man --output=$@ --no-info --name='Generate D-Bus logs for bustle' $<  org.freedesktop.Bustle.desktop: data/org.freedesktop.Bustle.desktop.in-	LC_ALL=C intltool-merge -d -u po $< $@+	msgfmt --desktop -d po --template $< -o $@  org.freedesktop.Bustle.appdata.xml: data/org.freedesktop.Bustle.appdata.xml.in-	LC_ALL=C intltool-merge -x -u po $< $@+	msgfmt --xml -d po --template $< -o $@  # https://github.com/flathub/flathub/wiki/Review-Guidelines validate-metadata: org.freedesktop.Bustle.desktop org.freedesktop.Bustle.appdata.xml@@ -85,8 +85,6 @@  clean: 	rm -f $(BINARIES) $(MANPAGE) $(BUSTLE_PCAP_GENERATED_HEADERS) $(DESKTOP_FILE) $(APPDATA_FILE)-	if test -d ./$(TARBALL_DIR); then rm -r ./$(TARBALL_DIR); fi-	rm -f ./$(TARBALL)  # Icon cache stuff gtk_update_icon_cache = gtk-update-icon-cache -f -t $(DATADIR)/icons/hicolor@@ -109,6 +107,8 @@ # Maintainer stuff maintainer-update-messages-pot: 	find Bustle -name '*.hs' -print0 | xargs -0 hgettext -k __ -o po/messages.pot+	xgettext data/bustle.ui data/org.freedesktop.Bustle.desktop.in \+		data/org.freedesktop.Bustle.appdata.xml.in --join-existing -o po/messages.pot  maintainer-make-release: bustle.cabal dist/build/autogen/version.txt 	cabal test
NEWS.md view
@@ -1,3 +1,36 @@+Bustle 0.7.1 (2018-06-15)+-------------------------++* It's now possible to monitor the system bus (from the user interface+  and with the bustle-pcap command-line tool), with no need to+  reconfigure the system bus. It's also possible to monitor an arbitrary+  bus by address.+* Bustle now requires that a version of dbus-monitor new enough to+  support the --pcap argument is installed on the system. (This was+  added in DBus 1.9.10, released in February 2015.)+* To monitor the system bus, Bustle requires the pkexec command to be+  installed on the host system.+* Bustle now requires GLib 2.54 or newer.+* The canonical Git repository and issue tracker has moved to+  <https://gitlab.freedesktop.org/bustle/bustle>. All open tickets have+  been migrated.++Bustle 0.6.3 (2018-05-24)+-------------------------++* The statistics sidebar now a more modern look and feel, and uses text+  to distinguish signals, method calls, etc. rather than font colour,+  italic text, and abstract shapes.+* Sizes now use power-of-10 units, in common with the rest of the GNOME desktop.+* Padding and alignment is more consistent.+* A libpcap 0.8.0/0.8.1 incompatibility is now detected and handled+  specially. See [bug+  #100220](https://bugs.freedesktop.org/show_bug.cgi?id=100220#c7) for+  details. Distributions should apply downstream patches until until a+  new upstream release is made; users should [install Bustle from+  Flathub](https://flathub.org/apps/details/org.freedesktop.Bustle).+* Most importantly, the welcome page is more beautiful.+ Bustle 0.6.2 (2017-10-26) ------------------------- 
README.md view
@@ -31,15 +31,6 @@     bustle --count logfile.bustle     bustle --time logfile.bustle -If you want to log all system bus traffic, you need to edit-`/etc/dbus/system.conf` to enable eavesdropping, and then remove the include of-`/etc/dbus-1/system.conf.d` which seems to re-enable strictness. Then you can run-the stand-alone logger against the system bus:--    bustle-pcap --system system-log.bustle--Please remember to **undo these changes** when you're done.-  More information ================
Setup.hs view
@@ -3,7 +3,7 @@  import Distribution.PackageDescription import Distribution.Simple-import Distribution.Simple.BuildPaths ( autogenModulesDir )+import Distribution.Simple.BuildPaths ( autogenPackageModulesDir ) import Distribution.Simple.LocalBuildInfo import Distribution.Simple.Setup as S import Distribution.Simple.Utils@@ -42,9 +42,9 @@ writeGetTextConstantsFile pkg lbi flags = do     let verbosity = fromFlag (buildVerbosity flags) -    createDirectoryIfMissingVerbose verbosity True (autogenModulesDir lbi)+    createDirectoryIfMissingVerbose verbosity True (autogenPackageModulesDir lbi) -    let pathsModulePath = autogenModulesDir lbi+    let pathsModulePath = autogenPackageModulesDir lbi                       </> ModuleName.toFilePath (getTextConstantsModuleName pkg) <.> "hs"     rewriteFile pathsModulePath (generateModule pkg lbi) 
Test/Renderer.hs view
@@ -13,7 +13,7 @@ import Data.Monoid import Data.List import System.Exit (exitFailure)-import DBus (objectPath_, busName_)+import DBus (objectPath_, busName_, ReceivedMessage(ReceivedMethodReturn), firstSerial, methodReturn)  import Bustle.Types import Bustle.Renderer@@ -39,7 +39,8 @@ -- disconnect from the bus before the end of the log. This is a regression test -- for a bug I almost introduced. activeService = UniqueName ":1.1"-swaddle messages timestamps = map (\(e, ts) -> Detailed ts e Nothing)+dummyReceivedMessage = ReceivedMethodReturn firstSerial (methodReturn firstSerial)+swaddle messages timestamps = map (\(e, ts) -> Detailed ts e 0 dummyReceivedMessage)                                   (zip messages timestamps) sessionLogWithoutDisconnect =     [ NOCEvent $ Connected activeService
bustle.cabal view
@@ -1,8 +1,8 @@ Name:           bustle Category:       Network, Desktop-Version:        0.6.2+Version:        0.7.1 Cabal-Version:  >= 1.24-Tested-With:    GHC == 7.8.*, GHC == 7.10.*+Tested-With:    GHC == 8.2.2, GHC == 8.4.3 Synopsis:       Draw sequence diagrams of D-Bus traffic Description:    Draw sequence diagrams of D-Bus traffic License:        OtherLicense@@ -10,10 +10,9 @@ Author:         Will Thompson <will@willthompson.co.uk> Maintainer:     Will Thompson <will@willthompson.co.uk> Homepage:       https://www.freedesktop.org/wiki/Software/Bustle/-Data-files:     data/dfeet-method.png,-                data/dfeet-signal.png,-                data/bustle.ui,+Data-files:     data/bustle.ui,                 data/OpenTwoDialog.ui,+                data/RecordAddressDialog.ui,                 LICENSE Build-type:     Custom Extra-source-files:@@ -65,7 +64,7 @@  Source-Repository head   Type:           git-  Location:       git://anongit.freedesktop.org/bustle+  Location:       https://gitlab.freedesktop.org/bustle/bustle.git  Flag hgettext   Description:    Enable translations. Since there are no translations this is currently rather pointless.@@ -86,6 +85,7 @@                , Bustle.Loader                , Bustle.Loader.Pcap                , Bustle.Marquee+               , Bustle.Missing                , Bustle.Monitor                , Bustle.Noninteractive                , Bustle.Regions@@ -100,10 +100,12 @@                , Bustle.UI.DetailsView                , Bustle.UI.FilterDialog                , Bustle.UI.OpenTwoDialog+               , Bustle.UI.RecordAddressDialog                , Bustle.UI.Recorder                , Bustle.UI.Util                , Bustle.Util                , Bustle.VariantFormatter+               , Paths_bustle   default-language: Haskell2010   Ghc-options: -Wall                -fno-warn-unused-do-bind@@ -111,7 +113,7 @@     ghc-options: -threaded   C-sources: c-sources/pcap-monitor.c   cc-options: -fPIC -g-  pkgconfig-depends: glib-2.0 >= 2.44+  pkgconfig-depends: glib-2.0 >= 2.54   Build-Depends: base >= 4 && < 5                , bytestring                , cairo@@ -131,7 +133,12 @@   if flag(hgettext)     Build-Depends: hgettext >= 0.1.5                  , setlocale-    cpp-options: -DUSE_HGETTEXT+    -- Other-Modules: GetText_bustle+    hs-source-dirs: .+                  , src-hgettext+  else+    hs-source-dirs: .+                  , src-no-hgettext  Executable test-monitor   if flag(InteractiveTests)@@ -183,6 +190,8 @@     type: exitcode-stdio-1.0     main-is: Test/PcapCrash.hs     other-modules: Bustle.Loader.Pcap+                 , Bustle.Translation+                 , Bustle.Types     default-language: Haskell2010     Build-Depends: base                  , bytestring@@ -191,6 +200,15 @@                  , mtl                  , pcap                  , text+    if flag(hgettext)+        Build-Depends: hgettext >= 0.1.5+                     , setlocale+        -- Other-Modules: GetText_bustle+        hs-source-dirs: .+                      , src-hgettext+    else+        hs-source-dirs: .+                      , src-no-hgettext  Test-suite test-regions     type: exitcode-stdio-1.0@@ -203,7 +221,16 @@ Test-suite test-renderer     type: exitcode-stdio-1.0     main-is: Test/Renderer.hs-    other-modules: Bustle.Renderer+    other-modules: Bustle.Diagram+                 , Bustle.Marquee+                 , Bustle.Regions+                 , Bustle.Renderer+                 , Bustle.Translation+                 , Bustle.Types+                 , Bustle.Util+++     default-language: Haskell2010     Build-Depends: base                  , cairo@@ -218,3 +245,12 @@                  , test-framework                  , test-framework-hunit                  , HUnit+    if flag(hgettext)+        Build-Depends: hgettext >= 0.1.5+                     , setlocale+        -- Other-Modules: GetText_bustle+        hs-source-dirs: .+                      , src-hgettext+    else+        hs-source-dirs: .+                      , src-no-hgettext
c-sources/bustle-pcap.c view
@@ -1,6 +1,6 @@ /* bustle-pcap.c: utility to log D-Bus traffic to a pcap file.  *- * Copyright © 2011 Will Thompson <will@willthompson.co.uk>+ * Copyright © 2011–2018 Will Thompson <will@willthompson.co.uk>  *  * This program is free software; you can redistribute it and/or  * modify it under the terms of the GNU Lesser General Public@@ -31,18 +31,28 @@ static gboolean verbose = FALSE; static gboolean quiet = FALSE; static gboolean version = FALSE;+static gboolean help = FALSE;  static gboolean session_specified = FALSE; static gboolean system_specified = FALSE;+static GBusType bus_type = G_BUS_TYPE_NONE;+static gchar *address = NULL; static gchar **filenames = NULL; -static GOptionEntry entries[] = {+static GOptionEntry bus_entries[] = {     { "session", 'e', 0, G_OPTION_ARG_NONE, &session_specified,       "Monitor session bus (default)", NULL     },     { "system", 'y', 0, G_OPTION_ARG_NONE, &system_specified,       "Monitor system bus", NULL     },+    { "address", 'a', 0, G_OPTION_ARG_STRING, &address,+      "Monitor given D-Bus address", NULL+    },+    { NULL }+};++static GOptionEntry entries[] = {     { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,       "Print brief summaries of captured messages to stdout", NULL     },@@ -52,6 +62,9 @@     { "version", 'V', 0, G_OPTION_ARG_NONE, &version,       "Print version information and exit", NULL     },+    { "help", 'h', 0, G_OPTION_ARG_NONE, &help,+      "Print help and exit", NULL+    },     { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,       "The filename to log to", NULL     },@@ -62,21 +75,35 @@ parse_arguments (     int *argc,     char ***argv,-    GBusType *bus_type,     gchar **filename) {-  GOptionContext *context;-  gchar *usage;-  GError *error = NULL;+  g_autoptr(GOptionContext) context;+  GOptionGroup *g;+  g_autofree gchar *usage;+  g_autoptr(GError) error = NULL;   gboolean ret;   gint exit_status = -1;+  int buses_specified;    context = g_option_context_new ("FILENAME");+  /* We implement --help by hand because the default implementation only shows+   * the main group, and we want to show the --session/--system/--address group+   * too.+   */+  g_option_context_set_help_enabled (context, FALSE);   g_option_context_add_main_entries (context, entries, NULL);   g_option_context_set_summary (context, "Logs D-Bus traffic to FILENAME in a format suitable for bustle"); +  g = g_option_group_new ("bus",+                          "Bus Options:",+                          "Options specifying the bus to monitor",+                          NULL,+                          NULL);+  g_option_group_add_entries (g, bus_entries);+  g_option_context_add_group (context, g);+   ret = g_option_context_parse (context, argc, argv, &error);-  usage = g_option_context_get_help (context, TRUE, NULL);+  usage = g_option_context_get_help (context, FALSE, NULL);    if (!ret)     {@@ -87,31 +114,44 @@       goto out;     } -  if (version)+  buses_specified = !!session_specified + !!system_specified + !!address;++  if (help)     {+      g_print ("%s", usage);+      exit_status = 0;+      goto out;+    }+  else if (version)+    {       fprintf (stdout, "bustle-pcap " BUSTLE_VERSION "\n\n");-      fprintf (stdout, "Copyright 2011 Will Thompson <will@willthompson.co.uk>\n");+      fprintf (stdout, "Copyright © 2011–2018 Will Thompson <will@willthompson.co.uk>\n");       fprintf (stdout, "This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n");       fprintf (stdout, "Written by Will Thompson <will@willthompson.co.uk>\n");        exit_status = 0;       goto out;     }-  else if (session_specified && system_specified)+  else if (buses_specified > 1)     {-      fprintf (stderr, "You may only specify one of --session and --system\n");+      fprintf (stderr, "You may only specify one of --session, --system and --address\n");       fprintf (stderr, "%s", usage);        exit_status = 2;       goto out;     }+  else if (address != NULL)+    {+      bus_type = G_BUS_TYPE_NONE;+      /* and the caller will use the global variable for the address */+    }   else if (system_specified)     {-      *bus_type = G_BUS_TYPE_SYSTEM;+      bus_type = G_BUS_TYPE_SYSTEM;     }   else     {-      *bus_type = G_BUS_TYPE_SESSION;+      bus_type = G_BUS_TYPE_SESSION;     }    if (filenames == NULL ||@@ -128,10 +168,7 @@   *filename = g_strdup (filenames[0]);  out:-  g_free (usage);   g_strfreev (filenames);-  g_option_context_free (context);-  g_clear_error (&error);    if (exit_status > -1)     exit (exit_status);@@ -140,39 +177,69 @@ static void message_logged_cb (     BustlePcapMonitor *pcap,-    GDBusMessage *message,-    gboolean is_incoming,     glong sec,     glong usec,     guint8 *data,     guint len,     gpointer user_data) {-    g_print ("(%s) %s -> %s: %u %s\n",-        is_incoming ? "incoming" : "outgoing",+  g_autoptr(GError) error = NULL;+  g_autoptr(GDBusMessage) message = g_dbus_message_new_from_blob (+      data, len, G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING, &error);++  if (message == NULL)+    g_warning ("%s", error->message);+  else+    g_print ("%s -> %s: %d %s\n",         g_dbus_message_get_sender (message),         g_dbus_message_get_destination (message),         g_dbus_message_get_message_type (message),         g_dbus_message_get_member (message)); } +static void+stopped_cb (+    BustlePcapMonitor *pcap,+    guint domain,+    gint code,+    const gchar *message,+    gpointer user_data)+{+  GMainLoop *loop = user_data;++  if (!(domain == G_IO_ERROR && code == G_IO_ERROR_CANCELLED))+    fprintf (stderr, "Error: %s %d %s", g_quark_to_string (domain), code, message);++  g_main_loop_quit (loop);+}++static gboolean+sigint_cb (gpointer user_data)+{+  BustlePcapMonitor *pcap = user_data;++  bustle_pcap_monitor_stop (pcap);++  return G_SOURCE_CONTINUE;+}+ int main (     int argc,     char **argv) {   GMainLoop *loop;-  GBusType bus_type;   gchar *filename;   GError *error = NULL;   BustlePcapMonitor *pcap; -  parse_arguments (&argc, &argv, &bus_type, &filename);+  parse_arguments (&argc, &argv, &filename); -  pcap = bustle_pcap_monitor_new (bus_type, filename, &error);+  pcap = bustle_pcap_monitor_new (bus_type, address, filename, &error);   if (pcap == NULL)     {-      fprintf (stderr, "%s", error->message);+      fprintf (stderr, "%s %d %s",+          g_quark_to_string (error->domain), error->code, error->message);       g_clear_error (&error);       exit (1);     }@@ -182,11 +249,12 @@         G_CALLBACK (message_logged_cb), NULL);    loop = g_main_loop_new (NULL, FALSE);+  g_signal_connect (pcap, "stopped", G_CALLBACK (stopped_cb), loop);    if (!quiet)     g_printf ("Logging D-Bus traffic to '%s'...\n", filename); -  g_unix_signal_add (SIGINT, (GSourceFunc) g_main_loop_quit, loop);+  g_unix_signal_add (SIGINT, sigint_cb, pcap);    if (!quiet)     g_printf ("Hit Control-C to stop logging.\n");
c-sources/config.h view
@@ -19,7 +19,7 @@ #ifndef BUSTLE_CONFIG_H #define BUSTLE_CONFIG_H -#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_44-#define GLIB_VERSION_MAX_ALLOWED  GLIB_VERSION_2_44+#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_54+#define GLIB_VERSION_MAX_ALLOWED  GLIB_VERSION_2_54  #endif /* BUSTLE_CONFIG_H */
c-sources/pcap-monitor.c view
@@ -1,6 +1,7 @@ /*  * pcap-monitor.c - monitors a bus and dumps messages to a pcap file- * Copyright ©2011–2012 Collabora Ltd.+ * Copyright © 2011–2012 Collabora Ltd.+ * Copyright © 2018      Will Thompson  *  * This library is free software; you can redistribute it and/or  * modify it under the terms of the GNU Lesser General Public@@ -20,54 +21,92 @@ #include "config.h" #include "pcap-monitor.h" +#include <errno.h>+#include <signal.h> #include <string.h> #include <pcap/pcap.h>+#include <gio/gunixinputstream.h>  #ifndef DLT_DBUS # define DLT_DBUS 231 #endif +/*+ * Transitions:+ *+ * NEW      --[ initable_init() errors ]--> STOPPED+ * NEW      --[ initable_init()        ]--> STARTING+ *+ * STARTING --[ start_pcap()           ]--> RUNNING+ * STARTING --[ error                  ]--> STOPPING+ *+ * RUNNING  --[ user request/error     ]--> STOPPING+ *+ * STOPPING --[ all finished           ]--> STOPPED+ */+typedef enum {+    /* Nothing's happened yet */+    STATE_NEW, -typedef struct {-    struct timeval ts;-    GByteArray *blob;-} Message;+    /* We're waiting to read the pcap header from the subprocess */+    STATE_STARTING, -typedef struct {-    BustlePcapMonitor *self;-    GDBusMessage *dbus_message;-    gboolean is_incoming;-    Message message;-} IdleEmitData;+    /* We've read the pcap header, messages are flowing freely */+    STATE_RUNNING, -#define STOP ((Message *) 0x1)+    /* Error or user request is causing us to stop; we're waiting to consume+     * everything from the pipe and for the dbus-monitor subprocess to exit.+     */+    STATE_STOPPING, -typedef struct {-    pcap_dumper_t *dumper;-    GAsyncQueue *message_queue;-} ThreadData;+    /* We've stopped, whether by user decision or because of an error.+     * Everything has been torn down (except possibly a root-owned subprocess),+     * the output file has been fully written and closed, and no more signals+     * will be emitted.+     */+    STATE_STOPPED,+} BustlePcapMonitorState; +static const gchar * const STATES[] = {+    "NEW",+    "STARTING",+    "RUNNING",+    "STOPPING",+    "STOPPED",+};+ struct _BustlePcapMonitorPrivate {     GBusType bus_type;-    GDBusConnection *connection;-    GDBusCapabilityFlags caps;+    gchar *address;+    BustlePcapMonitorState state;+    GCancellable *cancellable;+    guint cancellable_cancelled_id; -    guint filter_id;+    /* input */+    GSubprocess *dbus_monitor;+    GSource *dbus_monitor_source;+    pcap_t *pcap_in; +    /* output */     gchar *filename;-    pcap_t *p;+    pcap_t *pcap_out;+    pcap_dumper_t *dumper; -    GThread *thread;-    ThreadData td;+    /* errors */+    GError *pcap_error;+    GError *subprocess_error;+    guint await_both_errors_id; };  enum {     PROP_BUS_TYPE = 1,+    PROP_ADDRESS,     PROP_FILENAME, };  enum {     SIG_MESSAGE_LOGGED,+    SIG_STOPPED,     N_SIGNALS }; @@ -87,7 +126,8 @@   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, BUSTLE_TYPE_PCAP_MONITOR,       BustlePcapMonitorPrivate);   self->priv->bus_type = G_BUS_TYPE_SESSION;-  self->priv->td.message_queue = g_async_queue_new ();+  self->priv->state = STATE_NEW;+  self->priv->cancellable = g_cancellable_new (); }  static void@@ -105,6 +145,9 @@       case PROP_BUS_TYPE:         g_value_set_enum (value, priv->bus_type);         break;+      case PROP_ADDRESS:+        g_value_set_string (value, priv->address);+        break;       case PROP_FILENAME:         g_value_set_string (value, priv->filename);         break;@@ -128,6 +171,9 @@       case PROP_BUS_TYPE:         priv->bus_type = g_value_get_enum (value);         break;+      case PROP_ADDRESS:+        priv->address = g_value_dup_string (value);+        break;       case PROP_FILENAME:         priv->filename = g_value_dup_string (value);         break;@@ -137,19 +183,40 @@ }  static void+close_dump (BustlePcapMonitor *self)+{+  BustlePcapMonitorPrivate *priv = self->priv;++  if (priv->dumper != NULL)+    pcap_dump_flush (priv->dumper);++  g_clear_pointer (&priv->dumper, pcap_dump_close);+  g_clear_pointer (&priv->pcap_out, pcap_close);+}++static void bustle_pcap_monitor_dispose (GObject *object) {   BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object);   BustlePcapMonitorPrivate *priv = self->priv;   GObjectClass *parent_class = bustle_pcap_monitor_parent_class; -  if (parent_class->dispose != NULL)-    parent_class->dispose (object);+  if (priv->cancellable_cancelled_id != 0)+    {+      g_assert (priv->cancellable != NULL);+      g_cancellable_disconnect (priv->cancellable, priv->cancellable_cancelled_id);+      priv->cancellable_cancelled_id = 0;+    } -  /* Make sure we're all closed up. */-  bustle_pcap_monitor_stop (self);+  g_clear_object (&priv->cancellable);+  g_clear_pointer (&priv->dbus_monitor_source, g_source_destroy);+  g_clear_pointer (&priv->pcap_in, pcap_close);+  g_clear_object (&priv->dbus_monitor); -  g_clear_object (&priv->connection);+  close_dump (self);++  if (parent_class->dispose != NULL)+    parent_class->dispose (object); }  static void@@ -159,14 +226,13 @@   BustlePcapMonitorPrivate *priv = self->priv;   GObjectClass *parent_class = bustle_pcap_monitor_parent_class; +  g_clear_pointer (&priv->address, g_free);+  g_clear_pointer (&priv->filename, g_free);+  g_clear_error (&priv->pcap_error);+  g_clear_error (&priv->subprocess_error);+   if (parent_class->finalize != NULL)     parent_class->finalize (object);--  g_free (priv->filename);-  priv->filename = NULL;--  g_async_queue_unref (priv->td.message_queue);-  priv->td.message_queue = NULL; }  static void@@ -184,11 +250,20 @@  #define THRICE(x) x, x, x -  param_spec = g_param_spec_enum (THRICE ("bus-type"),-      G_TYPE_BUS_TYPE, G_BUS_TYPE_SESSION,+  param_spec = g_param_spec_enum (+      "bus-type", "Bus type",+      "Which standard bus to monitor. If NONE, :address must be non-NULL.",+      G_TYPE_BUS_TYPE, G_BUS_TYPE_NONE,       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);   g_object_class_install_property (object_class, PROP_BUS_TYPE, param_spec); +  param_spec = g_param_spec_string (+      "address", "Address",+      "Address of bus to monitor. If non-NULL, :bus-type must be G_BUS_TYPE_NONE",+      NULL,+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);+  g_object_class_install_property (object_class, PROP_ADDRESS, param_spec);+   param_spec = g_param_spec_string (THRICE ("filename"), NULL,       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);   g_object_class_install_property (object_class, PROP_FILENAME, param_spec);@@ -196,14 +271,9 @@   /**    * BustlePcapMonitor::message-logged:    * @self: the monitor.-   * @message: the #GDBusMessage object logged.-   * @is_incoming: if %TRUE, @message has come in from the bus daemon; if-   *  %FALSE, this is a message we're in the process of sending. Note that this-   *  can be %TRUE for messages our process sends, because we eavesdrop all-   *  messages, including our own.    * @sec: seconds since 1970.    * @usec: microseconds! (These are not combined into a single %gint64 because-   *  my version of gtk2hs crashes when it encounters %G_TYPE_UINT64 in a+   *  gtk2hs as of 2018-01-08 crashes when it encounters %G_TYPE_UINT64 in a    *  #GValue.)    * @blob: an array of bytes containing the serialized message.    * @length: the size in bytes of @blob.@@ -211,160 +281,147 @@   signals[SIG_MESSAGE_LOGGED] = g_signal_new ("message-logged",       BUSTLE_TYPE_PCAP_MONITOR, G_SIGNAL_RUN_FIRST,       0, NULL, NULL,-      NULL, G_TYPE_NONE, 6,-      G_TYPE_DBUS_MESSAGE,-      G_TYPE_BOOLEAN,+      NULL, G_TYPE_NONE, 4,       G_TYPE_LONG,       G_TYPE_LONG,       G_TYPE_POINTER,       G_TYPE_UINT);-} -static gpointer-log_thread (gpointer data)-{-  ThreadData *td = data;-  Message *message;--  while (STOP != (message = g_async_queue_pop (td->message_queue)))-    {-      struct pcap_pkthdr hdr;-      hdr.ts = message->ts;--      hdr.caplen = message->blob->len;-      hdr.len = message->blob->len;--      /* The cast is necessary because libpcap is weird. */-      pcap_dump ((u_char *) td->dumper, &hdr, message->blob->data);-      g_byte_array_unref (message->blob);-      g_slice_free (Message, message);-    }--  return NULL;+  /**+   * BustlePcapMonitor::stopped:+   * @self: the monitor+   * @domain: domain of a #GError (as G_TYPE_UINT because there is no+   *          G_TYPE_UINT32)+   * @code: code of a #GError+   * @message: message of a #GError+   *+   * Emitted once when monitoring stops, whether triggered (asynchronously) by+   * calling bustle_pcap_monitor_stop(), in which case @domain will be+   * %G_IO_ERROR and @code will be %G_IO_ERROR_CANCELLED, or because an error+   * occurs.+   */+  signals[SIG_STOPPED] = g_signal_new ("stopped",+      BUSTLE_TYPE_PCAP_MONITOR, G_SIGNAL_RUN_FIRST,+      0, NULL, NULL,+      NULL, G_TYPE_NONE, 3,+      G_TYPE_UINT,+      G_TYPE_INT,+      G_TYPE_STRING); } -static gboolean-emit_me (gpointer data)+static void+handle_error (BustlePcapMonitor *self) {-  IdleEmitData *ied = data;-  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (ied->self);-  glong sec = ied->message.ts.tv_sec;-  glong usec = ied->message.ts.tv_usec;+  BustlePcapMonitorPrivate *priv = self->priv;+  g_autoptr(GError) error = NULL; -  g_signal_emit (self, signals[SIG_MESSAGE_LOGGED], 0,-      ied->dbus_message,-      ied->is_incoming,-      sec,-      usec,-      ied->message.blob->data,-      ied->message.blob->len);-  g_object_unref (self);-  g_object_unref (ied->dbus_message);-  g_byte_array_unref (ied->message.blob);-  g_slice_free (IdleEmitData, ied);-  return FALSE;-}+  g_return_if_fail (priv->pcap_error != NULL ||+                    priv->subprocess_error != NULL); -GDBusMessage *-filter (-    GDBusConnection *connection,-    GDBusMessage *message,-    gboolean is_incoming,-    gpointer user_data)-{-  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (user_data);-  const gchar *dest;-  gsize size;-  guchar *blob;-  IdleEmitData ied = { g_object_ref (self), g_object_ref (message), is_incoming };-  GError *error = NULL;+  if (priv->pcap_error != NULL)+    g_debug ("%s: pcap_error: %s", G_STRFUNC, priv->pcap_error->message); -  gettimeofday (&ied.message.ts, NULL);-  blob = g_dbus_message_to_blob (message, &size, self->priv->caps, &error);-  if (blob == NULL)+  if (priv->subprocess_error != NULL)+    g_debug ("%s: subprocess_error: %s", G_STRFUNC,+             priv->subprocess_error->message);++  if (priv->state == STATE_STOPPED)     {-      g_critical ("Couldn't marshal message: %s", error->message);-      g_return_val_if_reached (NULL);+      g_debug ("%s: already stopped", G_STRFUNC);+      return;     }-  if (size > G_MAXUINT)++  /* Check for pkexec errors. Signal these in preference to all others. */+  if (priv->subprocess_error != NULL &&+      priv->bus_type == G_BUS_TYPE_SYSTEM)     {-      g_critical ("Message is longer than " G_STRINGIFY (G_MAXUINT)-                  "(which is surprising because the specification says the "-                  "maximum length of a message is 2**27 and guint is always "-                  "at least 32 bits wide");-      g_return_val_if_reached (NULL);+      if (g_error_matches (priv->subprocess_error, G_SPAWN_EXIT_ERROR, 126))+        {+          /* dialog dismissed */+          g_set_error (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED,+                       "User dismissed polkit authorization dialog");+        }+      else if (g_error_matches (priv->subprocess_error, G_SPAWN_EXIT_ERROR, 127))+        {+          /* not authorized, authorization couldn't be obtained through+           * authentication, or an priv->subprocess_error occurred */+          g_set_error (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,+                       "Not authorized to monitor system bus");+        }     }-  ied.message.blob = g_byte_array_append (-      g_byte_array_sized_new ((guint) size),-      blob, (guint) size);-  g_byte_array_ref (ied.message.blob);-  g_async_queue_push (self->priv->td.message_queue,-      g_slice_dup (Message, &(ied.message))); -  dest = g_dbus_message_get_destination (message);--  /* The idle steals the remaining refs to self, message, and message_blob. */-  g_idle_add (emit_me, g_slice_dup (IdleEmitData, &ied));--  if (!is_incoming ||-      g_strcmp0 (dest, g_dbus_connection_get_unique_name (connection)) == 0)+  if (g_error_matches (priv->subprocess_error, G_SPAWN_EXIT_ERROR, 0))     {-      /* This message is either outgoing or actually for us, as opposed to-       * being eavesdropped; it should be allowed to escape from this handler.+      /* I believe clean exit only happens if the bus is shut down. This might+       * happen if you're using Bustle to monitor a test suite, or perhaps a+       * user session that you log out of. Let's consider this to be+       * cancellation.        */-      return message;+      g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED,+                           priv->subprocess_error->message);     }-  else++  if (error == NULL)     {-      /* Otherwise, we need to handle it within this function, or else GDBus-       * replies to other people's method calls and we all get really confused.+      /* If no pkexec errors, prefer potentially more informative errors from+       * libpcap, including the wonderful snaplen bug.        */-      g_clear_object (&message);-      return NULL;+      if (priv->pcap_error != NULL)+        {+          error = g_steal_pointer (&priv->pcap_error);+        }+      /* Otherwise, the "subprocess didn't work" error will have to do. */+      else+        {+          error = g_steal_pointer (&priv->subprocess_error);++          if (priv->state == STATE_STARTING)+            g_prefix_error (&error, "Failed to start dbus-monitor: ");+        }     }++  priv->state = STATE_STOPPED;+  close_dump (self);++  g_debug ("%s: emitting ::stopped(%s, %d, %s)", G_STRFUNC,+           g_quark_to_string (error->domain), error->code, error->message);+  g_signal_emit (self, signals[SIG_STOPPED], 0,+                 (guint) error->domain, error->code, error->message);++  g_clear_handle_id (&priv->await_both_errors_id, g_source_remove); }  static gboolean-match_everything (-    GDBusProxy *bus,-    gboolean with_eavesdrop,-    GError **error)+await_both_errors_cb (gpointer data) {-#define EAVESDROP "eavesdrop=true,"-  char *rules[] = {-      EAVESDROP "type='signal'",-      EAVESDROP "type='method_call'",-      EAVESDROP "type='method_return'",-      EAVESDROP "type='error'",-      NULL-  };-  const gsize offset = with_eavesdrop ? 0 : strlen (EAVESDROP);-  char **r;+  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (data);+  BustlePcapMonitorPrivate *priv = self->priv; -  for (r = rules; *r != NULL; r++)-    {-      const gchar *rule = *r + offset;-      GVariant *ret = g_dbus_proxy_call_sync (-          bus,-          "AddMatch",-          g_variant_new ("(s)", rule),-          G_DBUS_CALL_FLAGS_NONE,-          -1,-          NULL,-          error);+  handle_error (self); -      if (ret == NULL)-        {-          g_prefix_error (error, "Couldn't AddMatch(%s): ", *r);-          return FALSE;-        }-      else-        {-          g_variant_unref (ret);-        }-    }+  priv->await_both_errors_id = 0;+  return G_SOURCE_REMOVE;+} -  return TRUE;+/* Wait for the dbus-monitor subprocess to have exited, and for the pcap reader+ * to have finished. We expect the reader to finish promptly when the+ * subprocess does, but the subprocess may not die until it tries to read to a+ * closed pipe (if the user stops the recording). So we wait a couple of+ * seconds before pressing on.+ */+static void+await_both_errors (BustlePcapMonitor *self)+{+  BustlePcapMonitorPrivate *priv = self->priv;++  if (priv->state == STATE_STOPPED)+    return;+  else if (priv->subprocess_error != NULL && priv->pcap_error != NULL)+    handle_error (self);+  else if (priv->await_both_errors_id == 0)+    priv->await_both_errors_id =+      g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 2, await_both_errors_cb,+                                  g_object_ref (self), g_object_unref); }  static gboolean@@ -372,10 +429,10 @@     GDBusProxy *bus,     GError **error) {-  GVariant *ret;-  gchar **names;+  g_autoptr(GVariant) ret = NULL;+  gchar **names;  /* borrowed from 'ret' */ -  g_assert (G_IS_DBUS_PROXY (bus));+  g_return_val_if_fail (G_IS_DBUS_PROXY (bus), FALSE);    ret = g_dbus_proxy_call_sync (bus, "ListNames", NULL,       G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);@@ -394,189 +451,445 @@       if (!g_dbus_is_unique_name (name) &&           strcmp (name, "org.freedesktop.DBus") != 0)         {-          GVariant *owner = g_dbus_proxy_call_sync (bus, "GetNameOwner",-              g_variant_new ("(s)", name),-              G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);--          if (owner != NULL)-            g_variant_unref (owner);-          /* else they were too quick for us! */+          g_autoptr(GVariant) owner =+            g_dbus_proxy_call_sync (bus, "GetNameOwner",+                                    g_variant_new ("(s)", name),+                                    G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);+          /* Ignore returned value or error. These are just used by the UI to+           * fill in the initial owners of each well-known name. If we get an+           * error here, the owner disappeared between ListNames() and here;+           * but that means we'll have seen a NameOwnerChanged from which the+           * UI can (in theory) infer who the owner used to be.+           *+           * We cannot use G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED because we+           * do want the reply to be sent to us.+           */         }     } -  g_variant_unref (ret);   return TRUE; } -static gboolean-initable_init (-    GInitable *initable,+static GDBusConnection *+get_connection (+    BustlePcapMonitor *self,     GCancellable *cancellable,     GError **error) {-  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (initable);   BustlePcapMonitorPrivate *priv = self->priv;-  gchar *address;-  GDBusProxy *bus;+  g_autofree gchar *address_to_free = NULL;+  const gchar *address = priv->address; -  if (priv->bus_type == G_BUS_TYPE_NONE)+  if (priv->address != NULL)     {-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,-          "Logging things other than message busses is not supported");-      return FALSE;+      address = priv->address;     }+  else+    {+      address_to_free = g_dbus_address_get_for_bus_sync (priv->bus_type,+                                                         cancellable, error);+      if (address_to_free == NULL)+        {+          g_prefix_error (error, "Couldn't get bus address: ");+          return FALSE;+        } -  if (priv->filename == NULL)+      address = address_to_free;+    }++  return g_dbus_connection_new_for_address_sync (+      address,+      G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |+      G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,+      NULL, /* auth observer */+      cancellable,+      error);+}++static void+dump_names_thread_func (+    GTask *task,+    gpointer source_object,+    gpointer task_data,+    GCancellable *cancellable)+{+  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (source_object);+  g_autoptr(GDBusConnection) connection = NULL;+  g_autoptr(GDBusProxy) bus = NULL;+  g_autoptr(GError) error = NULL;++  connection = get_connection (self, cancellable, &error);+  if (connection != NULL)     {-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,-          "You must specify a filename");-      return FALSE;+      bus = g_dbus_proxy_new_sync (connection,+                                   G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |+                                   G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,+                                   NULL,+                                   "org.freedesktop.DBus",+                                   "/org/freedesktop/DBus",+                                   "org.freedesktop.DBus",+                                   cancellable,+                                   &error);     } -  priv->p = pcap_open_dead (DLT_DBUS, 1 << 27);-  if (priv->p == NULL)+  if (bus != NULL && list_all_names (bus, &error))+    g_task_return_boolean (task, TRUE);+  else+    g_task_return_error (task, g_steal_pointer (&error));++  g_assert (error == NULL);+  if (connection != NULL+      && !g_dbus_connection_close_sync (connection, cancellable, &error))+    g_warning ("%s: %s", G_STRFUNC, error->message);+}++static void+dump_names_cb (+    GObject *source_object,+    GAsyncResult *result,+    gpointer user_data)+{+  g_autoptr(GError) error = NULL;++  if (!g_task_propagate_boolean (G_TASK (result), &error))+    g_warning ("Failed to dump names: %s", error->message);+}++static void+dump_names_async (+    BustlePcapMonitor *self)+{+  BustlePcapMonitorPrivate *priv = self->priv;+  g_autoptr(GTask) task = g_task_new (self, priv->cancellable, dump_names_cb, NULL);++  g_task_run_in_thread (task, dump_names_thread_func);+}++static gboolean+start_pcap (+    BustlePcapMonitor *self,+    GError **error)+{+  BustlePcapMonitorPrivate *priv = self->priv;+  GInputStream *stdout_pipe = NULL;+  gint stdout_fd = -1;+  FILE *dbus_monitor_filep = NULL;+  char errbuf[PCAP_ERRBUF_SIZE] = {0};++  stdout_pipe = g_subprocess_get_stdout_pipe (priv->dbus_monitor);+  g_return_val_if_fail (stdout_pipe != NULL, FALSE);++  stdout_fd = g_unix_input_stream_get_fd (G_UNIX_INPUT_STREAM (stdout_pipe));+  g_return_val_if_fail (stdout_fd >= 0, FALSE);++  dbus_monitor_filep = fdopen (stdout_fd, "r");+  if (dbus_monitor_filep == NULL)     {-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,-          "pcap_open_dead failed. wtf");+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "fdopen");       return FALSE;     }+  /* fd is owned by the FILE * now */+  g_unix_input_stream_set_close_fd (G_UNIX_INPUT_STREAM (stdout_pipe), FALSE); -  priv->td.dumper = pcap_dump_open (priv->p, priv->filename);-  if (priv->td.dumper == NULL)+  /* This reads the 4-byte pcap header from the pipe, in a single blocking+   * fread(). It's safe to do this on the main thread, since we know the pipe+   * is readable. On short read, pcap_fopen_offline() fails immediately.+   */+  priv->pcap_in = pcap_fopen_offline (dbus_monitor_filep, errbuf);+  if (priv->pcap_in == NULL)     {       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,-          "Couldn't open target file %s", pcap_geterr (priv->p));+                   "Couldn't read messages from dbus-monitor: %s",+                   errbuf);++      /* Cause dbus-monitor to exit next time it tries to write a message */+      g_clear_pointer (&dbus_monitor_filep, fclose);++      /* And try to terminate it immediately. When spawning via pkexec we may+       * not be able to kill it.+       */+      g_subprocess_force_exit (priv->dbus_monitor);+       return FALSE;     } -  priv->thread = g_thread_try_new (NULL, log_thread, &priv->td, error);-  if (priv->thread == NULL)+  /* pcap_close() will call fclose() on the FILE * passed to+   * pcap_fopen_offline() */+  dump_names_async (self);+  priv->state = STATE_RUNNING;+  return TRUE;+}++static gboolean+read_one (+    BustlePcapMonitor *self,+    GError **error)+{+  BustlePcapMonitorPrivate *priv = self->priv;+  struct pcap_pkthdr *hdr;+  const guchar *blob;+  int ret;++  ret = pcap_next_ex (priv->pcap_in, &hdr, &blob);+  switch (ret)     {-      g_prefix_error (error, "Couldn't spawn logging thread: ");-      return FALSE;+      case 1:+        g_signal_emit (self, signals[SIG_MESSAGE_LOGGED], 0,+            hdr->ts.tv_sec, hdr->ts.tv_usec, blob, hdr->caplen);++        /* cast necessary because pcap_dump has a type matching the callback+         * argument to pcap_loop()+         * TODO don't block+         */+        pcap_dump ((u_char *) priv->dumper, hdr, blob);+        return TRUE;++      case -2:+        /* EOF; shouldn't happen since we waited for the FD to be readable */+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED,+            "EOF when reading from dbus-monitor");+        return FALSE;++      default:+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,+            "Error %i reading dbus-monitor stream: %s",+            ret, pcap_geterr (priv->pcap_in));+        return FALSE;     }+} -  address = g_dbus_address_get_for_bus_sync (priv->bus_type, NULL, error);-  if (address == NULL)++++static gboolean+dbus_monitor_readable (+    GObject *pollable_input_stream,+    gpointer user_data)+{+  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (user_data);+  BustlePcapMonitorPrivate *priv = self->priv;+  gboolean (*read_func) (BustlePcapMonitor *, GError **);++  g_return_val_if_fail (priv->pcap_error == NULL, FALSE);++  if (g_cancellable_set_error_if_cancelled (priv->cancellable, &priv->pcap_error))     {-      g_prefix_error (error, "Couldn't get %s bus address: ",-          priv->bus_type == G_BUS_TYPE_SESSION ? "session" : "system");+      await_both_errors (self);       return FALSE;     } -  if (*address == '\0')+  switch (priv->state)     {-      g_set_error (error,-          G_IO_ERROR,-          G_IO_ERROR_FAILED,-          "Failed to look up the %s bus address. %s",-          priv->bus_type == G_BUS_TYPE_SESSION ? "session" : "system",-          priv->bus_type == G_BUS_TYPE_SESSION-              ? "Is DBUS_SESSION_BUS_ADDRESS properly set?"-              : "");-      g_free (address);+    case STATE_STARTING:+      read_func = start_pcap;+      break;++    case STATE_RUNNING:+    case STATE_STOPPING: /* may have a few last messages to read */+      read_func = read_one;+      break;++    default:+      g_critical ("%s in unexpected state %d (%s)",+                  G_STRFUNC, priv->state, STATES[priv->state]);       return FALSE;     } -  priv->connection = g_dbus_connection_new_for_address_sync (address,-      G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |-      G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,-      NULL, /* auth observer */-      NULL, /* cancellable */-      error);-  g_free (address);-  if (priv->connection == NULL)+  if (!read_func (self, &priv->pcap_error))     {-      g_prefix_error (error, "Couldn't connect to %s bus: ",-          priv->bus_type == G_BUS_TYPE_SESSION ? "session" : "system");+      await_both_errors (self);       return FALSE;     } -  priv->caps = g_dbus_connection_get_capabilities (priv->connection);+  return TRUE;+} -  bus = g_dbus_proxy_new_sync (priv->connection,-      G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |-      G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,-      NULL,-      "org.freedesktop.DBus",-      "/org/freedesktop/DBus",-      "org.freedesktop.DBus",-      NULL,-      error);-  if (bus == NULL)+static void+wait_check_cb (+    GObject *source,+    GAsyncResult *result,+    gpointer user_data)+{+  g_autoptr(BustlePcapMonitor) self = BUSTLE_PCAP_MONITOR (user_data);+  BustlePcapMonitorPrivate *priv = self->priv;+  GSubprocess *dbus_monitor = G_SUBPROCESS (source);++  g_return_if_fail (priv->subprocess_error == NULL);++  if (g_subprocess_wait_check_finish (dbus_monitor, result,+                                      &priv->subprocess_error))     {-      g_prefix_error (error, "Couldn't construct bus proxy: ");-      return FALSE;+      g_set_error (&priv->subprocess_error,+                   G_SPAWN_EXIT_ERROR, 0,+                   "dbus-monitor exited cleanly");     } -  /* As of DBus 1.5.something you have to specify eavesdrop=true to be sure of-   * getting everything. (Specifically, you don't get directed signals unless-   * you specify it.)+  /* cases:+   * - G_SPAWN_ERROR / G_SPAWN_ERROR_FAILED / "Child process killed by signal N":+   *   dbus-monitor was killed, possibly by us calling+   *   g_subprocess_force_exit(), though this doesn't work for pkexec'd+   *   dbus-monitor+   * - G_SPAWN_EXIT_ERROR:+   *   - 0: bus itself went away (assuming pkexec/flatpak-spawn propagate+   *        errors correctly)+   *   - 1: anything else went wrong in dbus-monitor, including invalid+   *        arguments and broken pipe (when we close the read end)+   *   - 126: User dismissed polkit authentication dialog+   *   - 127: polkit auth failed+   *   - 128 + N: killed by signal N, propagated by flatpak-spawn --host    *-   * So first we try to add match rules with "eavesdrop=true" on them. If that-   * fails, we try again without that; if that also fails, we return the second error.+   * We just need to deal with 0, 126, 127 specially.    */-  if (!match_everything (bus, TRUE, NULL) &&-      !match_everything (bus, FALSE, error))-    return FALSE;+  await_both_errors (self);+} -  priv->filter_id = g_dbus_connection_add_filter (priv->connection, filter,-      g_object_ref (self), g_object_unref);+static void+cancellable_cancelled_cb (GCancellable *cancellable,+                          gpointer      user_data)+{+  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (user_data);+  BustlePcapMonitorPrivate *priv = self->priv; -  {-    /* FIXME: there's a race between listing all the names and binding to all-     * signals (and hence getting NameOwnerChanged). Old bustle-dbus-monitor had-     * it too.-     */-    gboolean ret = list_all_names (bus, error);-    g_object_unref (bus);+  /* Closes the stream; should cause dbus-monitor to quit in due course when it+   * tries to write to the other end of the pipe.+   */+  g_clear_pointer (&priv->pcap_in, pcap_close); -    return ret;-  }+  if (priv->dbus_monitor != NULL)+    {+      /* Try to make it stop sooner; this has no effect on a privileged+       * dbus-monitor.+       */+      g_subprocess_force_exit (priv->dbus_monitor);+    } } -/* FIXME: make this async? */-void-bustle_pcap_monitor_stop (-    BustlePcapMonitor *self)+static gboolean+initable_init (+    GInitable *initable,+    GCancellable *cancellable,+    GError **error) {+  BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (initable);   BustlePcapMonitorPrivate *priv = self->priv;+  gboolean in_flatpak = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);+  g_autoptr(GPtrArray) dbus_monitor_argv = g_ptr_array_sized_new (8);+  GInputStream *stdout_pipe = NULL; -  if (priv->filter_id != 0)+  if (in_flatpak)     {-      g_return_if_fail (priv->connection != NULL);-      g_dbus_connection_remove_filter (priv->connection, priv->filter_id);-      priv->filter_id = 0;+      g_ptr_array_add (dbus_monitor_argv, "flatpak-spawn");+      g_ptr_array_add (dbus_monitor_argv, "--host");     } -  if (priv->connection != NULL &&-      !g_dbus_connection_is_closed (priv->connection))+  if (priv->bus_type == G_BUS_TYPE_SYSTEM)+    g_ptr_array_add (dbus_monitor_argv, "pkexec");++  g_ptr_array_add (dbus_monitor_argv, "dbus-monitor");+  g_ptr_array_add (dbus_monitor_argv, "--pcap");++  switch (priv->bus_type)     {-      g_dbus_connection_close_sync (priv->connection, NULL, NULL);+      case G_BUS_TYPE_SESSION:+        g_return_val_if_fail (priv->address == NULL, FALSE);+        g_ptr_array_add (dbus_monitor_argv, "--session");+        break;++      case G_BUS_TYPE_SYSTEM:+        g_return_val_if_fail (priv->address == NULL, FALSE);+        g_ptr_array_add (dbus_monitor_argv, "--system");+        break;++      case G_BUS_TYPE_NONE:+        g_return_val_if_fail (priv->address != NULL, FALSE);+        g_ptr_array_add (dbus_monitor_argv, "--address");+        g_ptr_array_add (dbus_monitor_argv, priv->address);+        break;++      default:+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,+            "Can only log the session bus, system bus, or a given address");+        return FALSE;     } -  if (priv->thread != NULL)+  g_ptr_array_add (dbus_monitor_argv, NULL);++  if (priv->filename == NULL)     {-      g_return_if_fail (priv->td.message_queue != NULL);-      /* Wait for the writer thread to spit out all the messages, then close up. */-      g_async_queue_push (priv->td.message_queue, STOP);-      g_thread_join (priv->thread);-      priv->thread = NULL;+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,+          "You must specify a filename");+      return FALSE;     } -  if (priv->td.dumper != NULL)+  priv->cancellable_cancelled_id =+    g_cancellable_connect (priv->cancellable,+                           G_CALLBACK (cancellable_cancelled_cb),+                           self, NULL);++  priv->pcap_out = pcap_open_dead (DLT_DBUS, 1 << 27);+  if (priv->pcap_out == NULL)     {-      pcap_dump_close (priv->td.dumper);-      priv->td.dumper = NULL;+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,+          "pcap_open_dead failed. wtf");+      return FALSE;     } -  if (priv->p != NULL)+  priv->dumper = pcap_dump_open (priv->pcap_out, priv->filename);+  if (priv->dumper == NULL)     {-      pcap_close (priv->p);-      priv->p = NULL;+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,+          "Couldn't open target file %s", pcap_geterr (priv->pcap_out));+      return FALSE;     }++  priv->dbus_monitor = g_subprocess_newv (+      (const gchar * const *) dbus_monitor_argv->pdata,+      G_SUBPROCESS_FLAGS_STDOUT_PIPE, error);+  if (priv->dbus_monitor == NULL)+    {+      return FALSE;+    }++  stdout_pipe = g_subprocess_get_stdout_pipe (priv->dbus_monitor);+  g_return_val_if_fail (stdout_pipe != NULL, FALSE);+  g_return_val_if_fail (G_IS_POLLABLE_INPUT_STREAM (stdout_pipe), FALSE);+  g_return_val_if_fail (G_IS_UNIX_INPUT_STREAM (stdout_pipe), FALSE);++  priv->dbus_monitor_source = g_pollable_input_stream_create_source (+      G_POLLABLE_INPUT_STREAM (stdout_pipe), priv->cancellable);+  g_source_set_callback (priv->dbus_monitor_source,+      (GSourceFunc) dbus_monitor_readable, self, NULL);+  g_source_attach (priv->dbus_monitor_source, NULL);++  g_subprocess_wait_check_async (+      priv->dbus_monitor,+      priv->cancellable,+      wait_check_cb, g_object_ref (self));++  priv->state = STATE_STARTING;+  return TRUE; } +/* FIXME: instead of GInitable + syncronous stop, have+ * bustle_pcap_monitor_record_{async,finish} */+void+bustle_pcap_monitor_stop (+    BustlePcapMonitor *self)+{+  BustlePcapMonitorPrivate *priv = self->priv;++  if (priv->state == STATE_STOPPED ||+      priv->state == STATE_STOPPING ||+      priv->state == STATE_NEW)+    {+      g_debug ("%s: already in state %s", G_STRFUNC, STATES[priv->state]);+      return;+    }++  priv->state = STATE_STOPPING;+  g_cancellable_cancel (priv->cancellable);+}+ static void initable_iface_init (     gpointer g_class,@@ -590,12 +903,14 @@ BustlePcapMonitor * bustle_pcap_monitor_new (     GBusType bus_type,+    const gchar *address,     const gchar *filename,     GError **error) {   return g_initable_new (       BUSTLE_TYPE_PCAP_MONITOR, NULL, error,       "bus-type", bus_type,+      "address", address,       "filename", filename,       NULL); }
c-sources/pcap-monitor.h view
@@ -1,6 +1,7 @@ /*  * pcap-monitor.h - monitors a bus and dumps messages to a pcap file- * Copyright ©2011–2012 Collabora Ltd.+ * Copyright © 2011–2012 Collabora Ltd.+ * Copyright © 2018      Will Thompson  *  * This library is free software; you can redistribute it and/or  * modify it under the terms of the GNU Lesser General Public@@ -41,6 +42,7 @@  BustlePcapMonitor *bustle_pcap_monitor_new (     GBusType bus_type,+    const gchar *address,     const gchar *filename,     GError **error); void bustle_pcap_monitor_stop (@@ -61,5 +63,7 @@ #define BUSTLE_PCAP_MONITOR_GET_CLASS(obj) \   (G_TYPE_INSTANCE_GET_CLASS ((obj), BUSTLE_TYPE_PCAP_MONITOR, \                               BustlePcapMonitorClass))++G_DEFINE_AUTOPTR_CLEANUP_FUNC (BustlePcapMonitor, g_object_unref)  #endif /* BUSTLE_PCAP_MONITOR_H */
+ data/RecordAddressDialog.ui view
@@ -0,0 +1,111 @@+<?xml version="1.0" encoding="UTF-8"?>+<!-- Generated with glade 3.22.1 -->+<interface>+  <requires lib="gtk+" version="3.20"/>+  <object class="GtkDialog" id="recordAddressDialog">+    <property name="can_focus">False</property>+    <property name="title" translatable="yes">Record Address</property>+    <property name="resizable">False</property>+    <property name="modal">True</property>+    <property name="type_hint">dialog</property>+    <child>+      <placeholder/>+    </child>+    <child internal-child="vbox">+      <object class="GtkBox">+        <property name="can_focus">False</property>+        <property name="orientation">vertical</property>+        <property name="spacing">2</property>+        <child internal-child="action_area">+          <object class="GtkButtonBox">+            <property name="can_focus">False</property>+            <property name="layout_style">end</property>+            <child>+              <object class="GtkButton" id="recordAddressCancel">+                <property name="label">gtk-cancel</property>+                <property name="visible">True</property>+                <property name="can_focus">True</property>+                <property name="receives_default">True</property>+                <property name="use_stock">True</property>+              </object>+              <packing>+                <property name="expand">True</property>+                <property name="fill">True</property>+                <property name="position">0</property>+              </packing>+            </child>+            <child>+              <object class="GtkButton" id="recordAddressRecord">+                <property name="label" translatable="yes">_Record</property>+                <property name="visible">True</property>+                <property name="can_focus">True</property>+                <property name="can_default">True</property>+                <property name="has_default">True</property>+                <property name="receives_default">True</property>+                <property name="use_underline">True</property>+              </object>+              <packing>+                <property name="expand">True</property>+                <property name="fill">True</property>+                <property name="position">1</property>+              </packing>+            </child>+          </object>+          <packing>+            <property name="expand">False</property>+            <property name="fill">False</property>+            <property name="position">0</property>+          </packing>+        </child>+        <child>+          <object class="GtkBox">+            <property name="visible">True</property>+            <property name="can_focus">False</property>+            <property name="margin_left">6</property>+            <property name="margin_right">6</property>+            <property name="margin_top">6</property>+            <property name="margin_bottom">6</property>+            <property name="spacing">6</property>+            <child>+              <object class="GtkLabel">+                <property name="visible">True</property>+                <property name="can_focus">False</property>+                <property name="label" translatable="yes">Address:</property>+              </object>+              <packing>+                <property name="expand">False</property>+                <property name="fill">True</property>+                <property name="position">0</property>+              </packing>+            </child>+            <child>+              <object class="GtkEntry" id="recordAddressEntry">+                <property name="visible">True</property>+                <property name="can_focus">True</property>+                <property name="has_focus">True</property>+                <property name="activates_default">True</property>+                <property name="width_chars">60</property>+                <property name="placeholder_text" translatable="yes" comments="Just translate the &quot;e.g.&quot;; leave the rest untouched.">e.g. unix:abstract=/tmp/dbus-E5RlEB5Tzu,guid= b1c1921b62283b7b612b57305b20cc28</property>+                <property name="input_hints">GTK_INPUT_HINT_NO_SPELLCHECK | GTK_INPUT_HINT_NO_EMOJI | GTK_INPUT_HINT_NONE</property>+              </object>+              <packing>+                <property name="expand">True</property>+                <property name="fill">True</property>+                <property name="position">1</property>+              </packing>+            </child>+          </object>+          <packing>+            <property name="expand">False</property>+            <property name="fill">True</property>+            <property name="position">1</property>+          </packing>+        </child>+      </object>+    </child>+    <action-widgets>+      <action-widget response="-6">recordAddressCancel</action-widget>+      <action-widget response="-3">recordAddressRecord</action-widget>+    </action-widgets>+  </object>+</interface>
data/bustle.ui view
@@ -1,231 +1,151 @@ <?xml version="1.0" encoding="UTF-8"?>+<!-- Generated with glade 3.22.1 --> <interface>-  <!-- interface-requires gtk+ 3.0 -->+  <requires lib="gtk+" version="3.20"/>+  <object class="GtkMenu" id="filterStatsEtc">+    <property name="visible">True</property>+    <property name="can_focus">False</property>+    <property name="halign">end</property>+    <child>+      <object class="GtkMenuItem" id="filter">+        <property name="visible">True</property>+        <property name="sensitive">False</property>+        <property name="can_focus">False</property>+        <property name="label" translatable="yes">_Filter Visible Services…</property>+        <property name="use_underline">True</property>+        <accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>+      </object>+    </child>+    <child>+      <object class="GtkCheckMenuItem" id="statistics">+        <property name="visible">True</property>+        <property name="sensitive">False</property>+        <property name="can_focus">False</property>+        <property name="label" translatable="yes">_Statistics</property>+        <property name="use_underline">True</property>+        <accelerator key="F9" signal="activate"/>+      </object>+    </child>+    <child>+      <object class="GtkSeparatorMenuItem" id="separatormenuitem1">+        <property name="visible">True</property>+        <property name="can_focus">False</property>+      </object>+    </child>+    <child>+      <object class="GtkImageMenuItem" id="about">+        <property name="label">gtk-about</property>+        <property name="visible">True</property>+        <property name="can_focus">False</property>+        <property name="use_underline">True</property>+        <property name="use_stock">True</property>+      </object>+    </child>+  </object>+  <object class="GtkMenu" id="openMenu">+    <property name="visible">True</property>+    <property name="can_focus">False</property>+    <child>+      <object class="GtkImageMenuItem" id="open">+        <property name="label">gtk-open</property>+        <property name="visible">True</property>+        <property name="can_focus">False</property>+        <property name="use_underline">True</property>+        <property name="use_stock">True</property>+        <accelerator key="o" signal="activate" modifiers="GDK_CONTROL_MASK"/>+      </object>+    </child>+    <child>+      <object class="GtkMenuItem" id="openTwo">+        <property name="visible">True</property>+        <property name="can_focus">False</property>+        <property name="tooltip_text" translatable="yes">Display two logs—one for the session bus, one for the system bus—side by side.</property>+        <property name="label" translatable="yes">O_pen a Pair of Logs…</property>+        <property name="use_underline">True</property>+      </object>+    </child>+  </object>+  <object class="GtkMenu" id="recordMenu">+    <property name="visible">True</property>+    <property name="can_focus">False</property>+    <child>+      <object class="GtkMenuItem" id="recordSession">+        <property name="visible">True</property>+        <property name="can_focus">False</property>+        <property name="label" translatable="yes">Record S_ession Bus</property>+        <property name="use_underline">True</property>+        <accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>+      </object>+    </child>+    <child>+      <object class="GtkMenuItem" id="recordSystem">+        <property name="visible">True</property>+        <property name="can_focus">False</property>+        <property name="label" translatable="yes">Record S_ystem Bus</property>+        <property name="use_underline">True</property>+        <accelerator key="y" signal="activate" modifiers="GDK_CONTROL_MASK"/>+      </object>+    </child>+    <child>+      <object class="GtkMenuItem" id="recordAddress">+        <property name="visible">True</property>+        <property name="can_focus">False</property>+        <property name="label" translatable="yes">Record _Address…</property>+        <property name="use_underline">True</property>+        <accelerator key="a" signal="activate" modifiers="GDK_CONTROL_MASK"/>+      </object>+    </child>+  </object>   <object class="GtkWindow" id="diagramWindow">     <property name="can_focus">False</property>+    <property name="title" translatable="yes">Bustle</property>     <property name="default_width">900</property>     <property name="default_height">700</property>-    <property name="icon-name">org.freedesktop.Bustle</property>-    <property name="title" translatable="yes">Bustle</property>+    <property name="icon_name">org.freedesktop.Bustle</property>     <child type="titlebar">-        <object class="GtkHeaderBar" id="header">-          <property name="visible">True</property>-          <property name="show-close-button">True</property>--          <child>-            <object class="GtkMenuButton" id="headerOpen">-              <property name="visible">True</property>-              <property name="sensitive">True</property>-              <property name="tooltip_text" translatable="yes">Open an existing log</property>-              <property name="popup">openMenu</property>-              <style>-                <class name="image-button"/>-              </style>-              <child>-                <object class="GtkImage">-                  <property name="visible">True</property>-                  <property name="icon-name">document-open-symbolic</property>-                  <property name="icon-size">1</property>-                </object>-              </child>-            </object>-            <packing>-              <property name="pack-type">GTK_PACK_START</property>-            </packing>-          </child>--          <!-- TODO: media-record-symbolic -->-          <child>-            <object class="GtkButton" id="headerNew">-              <property name="visible">True</property>-              <property name="label" translatable="yes">Record</property>-              <property name="tooltip_text" translatable="yes">Record a new log</property>-            </object>-          </child>--          <child>-            <object class="GtkMenuButton">-              <property name="visible">True</property>-              <property name="popup">filterStatsEtc</property>-              <style>-                <class name="image-button"/>-              </style>-              <child>-                <object class="GtkImage">-                  <property name="visible">True</property>-                  <property name="icon-name">open-menu-symbolic</property>-                  <property name="icon-size">1</property>-                </object>-              </child>-            </object>-            <packing>-              <property name="pack-type">end</property>-            </packing>-          </child>--          <child>-            <object class="GtkButton" id="headerExport">-              <property name="visible">True</property>-              <property name="sensitive">False</property>-              <property name="tooltip_text" translatable="yes">Export as PDF</property>-              <style>-                <class name="image-button"/>-              </style>-              <child>-                <object class="GtkImage">-                  <property name="visible">True</property>-                  <property name="icon-name">document-send-symbolic</property>-                  <property name="icon-size">1</property>-                </object>-              </child>-            </object>-            <packing>-              <property name="pack-type">end</property>-            </packing>-          </child>--          <child>-            <object class="GtkButton" id="headerSave">-              <property name="visible">True</property>-              <property name="sensitive">False</property>-              <property name="tooltip_text" translatable="yes">Save</property>-              <style>-                <class name="image-button"/>-              </style>-              <child>-                <object class="GtkImage">-                  <property name="visible">True</property>-                  <property name="icon-name">document-save-symbolic</property>-                  <property name="icon-size">1</property>-                </object>-              </child>-            </object>-            <packing>-              <property name="pack-type">end</property>-            </packing>-          </child>-        </object>-    </child>-    <child>-      <object class="GtkVBox" id="box1">+      <object class="GtkPaned" id="headerPaned">         <property name="visible">True</property>-        <property name="can_focus">False</property>+        <property name="can_focus">True</property>+        <property name="position" bind-source="sidebarPaned" bind-property="position" bind-flags="bidirectional|sync-create"/>         <child>-          <object class="GtkNotebook" id="diagramOrNot">+          <object class="GtkHeaderBar" id="sidebarHeader">+            <property name="can_focus">False</property>+            <child type="title">+              <object class="GtkStackSwitcher">+                <property name="visible">True</property>+                <property name="can_focus">False</property>+                <property name="stack">sidebarStack</property>+              </object>+            </child>+          </object>+          <packing>+            <property name="resize">False</property>+            <property name="shrink">False</property>+          </packing>+        </child>+        <child>+          <object class="GtkHeaderBar" id="header">             <property name="visible">True</property>-            <property name="can_focus">True</property>-            <property name="show_tabs">False</property>-            <property name="show_border">False</property>+            <property name="can_focus">False</property>+            <property name="show_close_button">True</property>             <child>-              <object class="GtkAlignment" id="alignment1">+              <object class="GtkMenuButton" id="headerRecord">                 <property name="visible">True</property>-                <property name="can_focus">False</property>-                <property name="xscale">0</property>-                <property name="yscale">0</property>+                <property name="can_focus">True</property>+                <property name="receives_default">True</property>+                <property name="tooltip_text" translatable="yes">Record a new log</property>+                <property name="popup">recordMenu</property>                 <child>-                  <object class="GtkVBox" id="box2">+                  <object class="GtkBox">                     <property name="visible">True</property>                     <property name="can_focus">False</property>-                    <property name="spacing">12</property>                     <child>-                      <object class="GtkHButtonBox" id="buttonbox1">+                      <object class="GtkLabel">                         <property name="visible">True</property>                         <property name="can_focus">False</property>-                        <property name="spacing">12</property>-                        <property name="homogeneous">True</property>-                        <property name="layout_style">center</property>-                        <child>-                          <object class="GtkButton" id="newButton">-                            <property name="use_action_appearance">False</property>-                            <property name="visible">True</property>-                            <property name="can_focus">True</property>-                            <property name="receives_default">True</property>-                            <property name="use_action_appearance">False</property>-                            <child>-                              <object class="GtkVBox" id="box3">-                                <property name="visible">True</property>-                                <property name="can_focus">False</property>-                                <property name="spacing">12</property>-                                <child>-                                  <object class="GtkImage" id="image1">-                                    <property name="visible">True</property>-                                    <property name="can_focus">False</property>-                                    <property name="stock">gtk-new</property>-                                    <property name="icon-size">6</property>-                                  </object>-                                  <packing>-                                    <property name="expand">True</property>-                                    <property name="fill">True</property>-                                    <property name="position">0</property>-                                  </packing>-                                </child>-                                <child>-                                  <object class="GtkLabel" id="balahah">-                                    <property name="visible">True</property>-                                    <property name="can_focus">False</property>-                                    <property name="label" translatable="yes">Record a New Log</property>-                                  </object>-                                  <packing>-                                    <property name="expand">True</property>-                                    <property name="fill">True</property>-                                    <property name="position">1</property>-                                  </packing>-                                </child>-                              </object>-                            </child>-                          </object>-                          <packing>-                            <property name="expand">False</property>-                            <property name="fill">True</property>-                            <property name="position">0</property>-                          </packing>-                        </child>-                        <child>-                          <object class="GtkButton" id="openButton">-                            <property name="use_action_appearance">False</property>-                            <property name="visible">True</property>-                            <property name="can_focus">True</property>-                            <property name="receives_default">True</property>-                            <property name="use_action_appearance">False</property>-                            <child>-                              <object class="GtkVBox" id="box4">-                                <property name="visible">True</property>-                                <property name="can_focus">False</property>-                                <property name="spacing">12</property>-                                <child>-                                  <object class="GtkImage" id="image2">-                                    <property name="visible">True</property>-                                    <property name="can_focus">False</property>-                                    <property name="stock">gtk-open</property>-                                    <property name="icon-size">6</property>-                                  </object>-                                  <packing>-                                    <property name="expand">True</property>-                                    <property name="fill">True</property>-                                    <property name="position">0</property>-                                  </packing>-                                </child>-                                <child>-                                  <object class="GtkLabel" id="balahah1">-                                    <property name="visible">True</property>-                                    <property name="can_focus">False</property>-                                    <property name="label" translatable="yes">Open an Existing Log</property>-                                  </object>-                                  <packing>-                                    <property name="expand">True</property>-                                    <property name="fill">True</property>-                                    <property name="position">1</property>-                                  </packing>-                                </child>-                              </object>-                            </child>-                          </object>-                          <packing>-                            <property name="expand">False</property>-                            <property name="fill">True</property>-                            <property name="position">1</property>-                          </packing>-                        </child>+                        <property name="valign">baseline</property>+                        <property name="label" translatable="yes">_Record</property>+                        <property name="use_underline">True</property>                       </object>                       <packing>                         <property name="expand">False</property>@@ -234,60 +154,223 @@                       </packing>                     </child>                     <child>-                      <object class="GtkLabel" id="label4">+                      <object class="GtkImage">                         <property name="visible">True</property>                         <property name="can_focus">False</property>-                        <property name="label" translatable="yes">You may also use the &lt;i&gt;bustle-pcap&lt;/i&gt; command-line tool to record logs.</property>-                        <property name="use_markup">True</property>+                        <property name="valign">baseline</property>+                        <property name="icon_name">pan-down-symbolic</property>                       </object>                       <packing>                         <property name="expand">False</property>-                        <property name="fill">False</property>+                        <property name="fill">True</property>                         <property name="position">1</property>                       </packing>                     </child>                   </object>                 </child>+                <style>+                  <class name="text-button"/>+                  <class name="image-button"/>+                  <class name="suggested-action"/>+                </style>               </object>             </child>-            <child type="tab">-              <object class="GtkLabel" id="label1">+            <child>+              <object class="GtkMenuButton" id="headerOpen">                 <property name="visible">True</property>                 <property name="can_focus">False</property>-                <property name="label" translatable="yes">Instructions</property>+                <property name="receives_default">False</property>+                <property name="tooltip_text" translatable="yes">Open an existing log</property>+                <property name="popup">openMenu</property>+                <child>+                  <object class="GtkImage">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="icon_name">document-open-symbolic</property>+                    <property name="icon_size">1</property>+                  </object>+                </child>+                <style>+                  <class name="image-button"/>+                </style>               </object>               <packing>-                <property name="tab_fill">False</property>+                <property name="position">1</property>               </packing>             </child>             <child>-              <object class="GtkLabel" id="label5">+              <object class="GtkMenuButton">                 <property name="visible">True</property>                 <property name="can_focus">False</property>-                <property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;Waiting for D-Bus traffic; please hold…&lt;/b&gt;&lt;/big&gt;</property>-                <property name="use_markup">True</property>+                <property name="receives_default">False</property>+                <property name="popup">filterStatsEtc</property>+                <child>+                  <object class="GtkImage">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="icon_name">open-menu-symbolic</property>+                    <property name="icon_size">1</property>+                  </object>+                </child>+                <style>+                  <class name="image-button"/>+                </style>               </object>               <packing>-                <property name="position">1</property>+                <property name="pack_type">end</property>+                <property name="position">2</property>               </packing>             </child>-            <child type="tab">-              <object class="GtkLabel" id="label2">+            <child>+              <object class="GtkButton" id="headerExport">                 <property name="visible">True</property>+                <property name="sensitive">False</property>                 <property name="can_focus">False</property>-                <property name="label" translatable="yes">Please hold</property>+                <property name="receives_default">False</property>+                <property name="tooltip_text" translatable="yes">Export as PDF</property>+                <child>+                  <object class="GtkImage">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="icon_name">document-send-symbolic</property>+                    <property name="icon_size">1</property>+                  </object>+                </child>+                <style>+                  <class name="image-button"/>+                </style>               </object>               <packing>+                <property name="pack_type">end</property>+                <property name="position">3</property>+              </packing>+            </child>+            <child>+              <object class="GtkButton" id="headerSave">+                <property name="visible">True</property>+                <property name="sensitive">False</property>+                <property name="can_focus">False</property>+                <property name="receives_default">False</property>+                <property name="tooltip_text" translatable="yes">Save</property>+                <child>+                  <object class="GtkImage">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="icon_name">document-save-symbolic</property>+                    <property name="icon_size">1</property>+                  </object>+                </child>+                <style>+                  <class name="image-button"/>+                </style>+              </object>+              <packing>+                <property name="pack_type">end</property>+                <property name="position">4</property>+              </packing>+            </child>+          </object>+          <packing>+            <property name="resize">True</property>+            <property name="shrink">False</property>+          </packing>+        </child>+      </object>+    </child>+    <child>+      <object class="GtkBox" id="box1">+        <property name="visible">True</property>+        <property name="can_focus">False</property>+        <property name="orientation">vertical</property>+        <child>+          <object class="GtkStack" id="diagramOrNot">+            <property name="visible">True</property>+            <property name="can_focus">True</property>+            <child>+              <object class="GtkBox">+                <property name="visible">True</property>+                <property name="can_focus">False</property>+                <property name="valign">center</property>+                <property name="orientation">vertical</property>+                <property name="spacing">12</property>+                <child type="center">+                  <object class="GtkImage">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="pixel_size">256</property>+                    <property name="icon_name">org.freedesktop.Bustle-symbolic</property>+                    <style>+                      <class name="dim-label"/>+                    </style>+                  </object>+                  <packing>+                    <property name="expand">False</property>+                    <property name="fill">True</property>+                    <property name="position">2</property>+                  </packing>+                </child>+                <child>+                  <object class="GtkLabel">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="label" translatable="yes">Start recording D-Bus activity with the &lt;b&gt;Record&lt;/b&gt; button above+ You can also run &lt;i&gt;dbus-monitor --pcap&lt;/i&gt; from the command line</property>+                    <property name="use_markup">True</property>+                    <property name="justify">center</property>+                    <style>+                      <class name="dim-label"/>+                    </style>+                  </object>+                  <packing>+                    <property name="expand">False</property>+                    <property name="fill">True</property>+                    <property name="pack_type">end</property>+                    <property name="position">0</property>+                  </packing>+                </child>+                <child>+                  <object class="GtkLabel">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="label" translatable="yes">Welcome to Bustle</property>+                    <attributes>+                      <attribute name="weight" value="bold"/>+                      <attribute name="scale" value="2"/>+                    </attributes>+                    <style>+                      <class name="dim-label"/>+                    </style>+                  </object>+                  <packing>+                    <property name="expand">False</property>+                    <property name="fill">True</property>+                    <property name="pack_type">end</property>+                    <property name="position">1</property>+                  </packing>+                </child>+              </object>+              <packing>+                <property name="name">InstructionsPage</property>+              </packing>+            </child>+            <child>+              <object class="GtkLabel" id="label5">+                <property name="visible">True</property>+                <property name="can_focus">False</property>+                <property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;Waiting for D-Bus traffic; please hold…&lt;/b&gt;&lt;/big&gt;</property>+                <property name="use_markup">True</property>+              </object>+              <packing>+                <property name="name">PleaseHoldPage</property>                 <property name="position">1</property>-                <property name="tab_fill">False</property>               </packing>             </child>             <child>-              <object class="GtkHPaned" id="paned1">+              <object class="GtkPaned" id="sidebarPaned">                 <property name="visible">True</property>                 <property name="can_focus">True</property>                 <child>-                  <object class="GtkNotebook" id="statsBook">+                  <object class="GtkStack" id="sidebarStack">                     <property name="visible">True</property>                     <property name="can_focus">True</property>                     <child>@@ -295,21 +378,13 @@                         <property name="visible">True</property>                         <property name="can_focus">True</property>                         <property name="hscrollbar_policy">never</property>-                        <property name="vscrollbar_policy">automatic</property>                         <property name="shadow_type">in</property>                         <child>                           <placeholder/>                         </child>                       </object>-                    </child>-                    <child type="tab">-                      <object class="GtkLabel" id="label6">-                        <property name="visible">True</property>-                        <property name="can_focus">False</property>-                        <property name="label" translatable="yes">Message Frequencies</property>-                      </object>                       <packing>-                        <property name="tab_fill">False</property>+                        <property name="title" translatable="yes">Frequencies</property>                       </packing>                     </child>                     <child>@@ -317,69 +392,47 @@                         <property name="visible">True</property>                         <property name="can_focus">True</property>                         <property name="hscrollbar_policy">never</property>-                        <property name="vscrollbar_policy">automatic</property>                         <property name="shadow_type">in</property>                         <child>                           <placeholder/>                         </child>                       </object>                       <packing>+                        <property name="title" translatable="yes">Durations</property>                         <property name="position">1</property>                       </packing>                     </child>-                    <child type="tab">-                      <object class="GtkLabel" id="label7">-                        <property name="visible">True</property>-                        <property name="can_focus">False</property>-                        <property name="label" translatable="yes">Method Durations</property>-                      </object>-                      <packing>-                        <property name="position">1</property>-                        <property name="tab_fill">False</property>-                      </packing>-                    </child>                     <child>                       <object class="GtkScrolledWindow" id="sizeSW">                         <property name="visible">True</property>                         <property name="can_focus">True</property>                         <property name="hscrollbar_policy">never</property>-                        <property name="vscrollbar_policy">automatic</property>                         <property name="shadow_type">in</property>                         <child>                           <placeholder/>                         </child>                       </object>                       <packing>+                        <property name="title" translatable="yes">Sizes</property>                         <property name="position">2</property>                       </packing>                     </child>-                    <child type="tab">-                      <object class="GtkLabel" id="label8">-                        <property name="visible">True</property>-                        <property name="can_focus">False</property>-                        <property name="label" translatable="yes">Message Sizes</property>-                      </object>-                      <packing>-                        <property name="position">2</property>-                        <property name="tab_fill">False</property>-                      </packing>-                    </child>                   </object>                   <packing>                     <property name="resize">False</property>-                    <property name="shrink">True</property>+                    <property name="shrink">False</property>                   </packing>                 </child>                 <child>-                  <object class="GtkVPaned" id="contentVPaned">+                  <object class="GtkPaned" id="contentPaned">                     <property name="visible">True</property>                     <property name="can_focus">True</property>+                    <property name="orientation">vertical</property>                     <child>                       <object class="GtkScrolledWindow" id="scrolledwindow1">                         <property name="visible">True</property>                         <property name="can_focus">True</property>                         <property name="vscrollbar_policy">always</property>-                        <property name="hscrollbar_policy">automatic</property>                         <property name="shadow_type">in</property>                         <child>                           <object class="GtkLayout" id="diagramLayout">@@ -394,7 +447,211 @@                       </packing>                     </child>                     <child>-                      <placeholder/>+                      <object class="GtkGrid" id="detailsGrid">+                        <property name="can_focus">False</property>+                        <property name="margin_left">6</property>+                        <property name="margin_right">6</property>+                        <property name="margin_top">6</property>+                        <property name="margin_bottom">6</property>+                        <property name="row_spacing">6</property>+                        <property name="column_spacing">12</property>+                        <child>+                          <object class="GtkLabel">+                            <property name="visible">True</property>+                            <property name="can_focus">False</property>+                            <property name="hexpand">False</property>+                            <property name="label" translatable="yes">Type</property>+                            <property name="xalign">1</property>+                            <style>+                              <class name="dim-label"/>+                            </style>+                          </object>+                          <packing>+                            <property name="left_attach">0</property>+                            <property name="top_attach">0</property>+                          </packing>+                        </child>+                        <child>+                          <object class="GtkLabel">+                            <property name="visible">True</property>+                            <property name="can_focus">False</property>+                            <property name="hexpand">False</property>+                            <property name="label" translatable="yes">Path</property>+                            <property name="xalign">1</property>+                            <style>+                              <class name="dim-label"/>+                            </style>+                          </object>+                          <packing>+                            <property name="left_attach">0</property>+                            <property name="top_attach">1</property>+                          </packing>+                        </child>+                        <child>+                          <object class="GtkLabel" id="detailsPath">+                            <property name="name">detailsPath</property>+                            <property name="visible">True</property>+                            <property name="can_focus">False</property>+                            <property name="hexpand">True</property>+                            <property name="label">/place/holder</property>+                            <property name="selectable">True</property>+                            <property name="ellipsize">start</property>+                            <property name="xalign">0</property>+                          </object>+                          <packing>+                            <property name="left_attach">1</property>+                            <property name="top_attach">1</property>+                          </packing>+                        </child>+                        <child>+                          <object class="GtkLabel">+                            <property name="visible">True</property>+                            <property name="can_focus">False</property>+                            <property name="hexpand">False</property>+                            <property name="label" translatable="yes">Member</property>+                            <property name="xalign">1</property>+                            <style>+                              <class name="dim-label"/>+                            </style>+                          </object>+                          <packing>+                            <property name="left_attach">0</property>+                            <property name="top_attach">2</property>+                          </packing>+                        </child>+                        <child>+                          <object class="GtkLabel" id="detailsMember">+                            <property name="name">detailsMember</property>+                            <property name="visible">True</property>+                            <property name="can_focus">False</property>+                            <property name="hexpand">True</property>+                            <property name="label">com.example.Placeholder</property>+                            <property name="selectable">True</property>+                            <property name="ellipsize">start</property>+                            <property name="xalign">0</property>+                          </object>+                          <packing>+                            <property name="left_attach">1</property>+                            <property name="top_attach">2</property>+                          </packing>+                        </child>+                        <child>+                          <object class="GtkLabel">+                            <property name="visible">True</property>+                            <property name="can_focus">False</property>+                            <property name="hexpand">False</property>+                            <property name="label" translatable="yes">Arguments</property>+                            <property name="xalign">1</property>+                            <property name="yalign">0</property>+                            <style>+                              <class name="dim-label"/>+                            </style>+                          </object>+                          <packing>+                            <property name="left_attach">0</property>+                            <property name="top_attach">3</property>+                          </packing>+                        </child>+                        <child>+                          <object class="GtkScrolledWindow">+                            <property name="visible">True</property>+                            <property name="can_focus">True</property>+                            <property name="vexpand">True</property>+                            <property name="shadow_type">in</property>+                            <child>+                              <object class="GtkTextView" id="detailsArguments">+                                <property name="name">detailsArguments</property>+                                <property name="visible">True</property>+                                <property name="can_focus">True</property>+                                <property name="editable">False</property>+                                <property name="wrap_mode">char</property>+                              </object>+                            </child>+                          </object>+                          <packing>+                            <property name="left_attach">1</property>+                            <property name="top_attach">3</property>+                          </packing>+                        </child>+                        <child>+                          <object class="GtkStack" id="detailsType">+                            <property name="visible">True</property>+                            <property name="can_focus">False</property>+                            <child>+                              <object class="GtkLabel" id="detailsTypeMethodCall">+                                <property name="visible">True</property>+                                <property name="can_focus">False</property>+                                <property name="hexpand">True</property>+                                <property name="label" translatable="yes">Method call</property>+                                <property name="xalign">0</property>+                              </object>+                              <packing>+                                <property name="name">methodCall</property>+                              </packing>+                            </child>+                            <child>+                              <object class="GtkLabel" id="detailsTypeMethodReturn">+                                <property name="visible">True</property>+                                <property name="can_focus">False</property>+                                <property name="hexpand">True</property>+                                <property name="label" translatable="yes">Method return</property>+                                <property name="xalign">0</property>+                              </object>+                              <packing>+                                <property name="name">methodReturn</property>+                                <property name="position">1</property>+                              </packing>+                            </child>+                            <child>+                              <object class="GtkLabel" id="detailsTypeError">+                                <property name="visible">True</property>+                                <property name="can_focus">False</property>+                                <property name="hexpand">True</property>+                                <property name="label" translatable="yes">Error</property>+                                <property name="xalign">0</property>+                              </object>+                              <packing>+                                <property name="name">error</property>+                                <property name="position">2</property>+                              </packing>+                            </child>+                            <child>+                              <object class="GtkLabel" id="detailsTypeSignal">+                                <property name="visible">True</property>+                                <property name="can_focus">False</property>+                                <property name="hexpand">True</property>+                                <property name="label" translatable="yes">Signal</property>+                                <property name="xalign">0</property>+                              </object>+                              <packing>+                                <property name="name">signal</property>+                                <property name="position">3</property>+                              </packing>+                            </child>+                            <child>+                              <object class="GtkLabel" id="detailsTypeDirectedSignal">+                                <property name="visible">True</property>+                                <property name="can_focus">False</property>+                                <property name="hexpand">True</property>+                                <property name="label" translatable="yes">Directed signal</property>+                                <property name="xalign">0</property>+                              </object>+                              <packing>+                                <property name="name">directedSignal</property>+                                <property name="position">4</property>+                              </packing>+                            </child>+                          </object>+                          <packing>+                            <property name="left_attach">1</property>+                            <property name="top_attach">0</property>+                          </packing>+                        </child>+                      </object>+                      <packing>+                        <property name="resize">False</property>+                        <property name="shrink">False</property>+                      </packing>                     </child>                   </object>                   <packing>@@ -404,20 +661,10 @@                 </child>               </object>               <packing>+                <property name="name">CanvasPage</property>                 <property name="position">2</property>               </packing>             </child>-            <child type="tab">-              <object class="GtkLabel" id="label3">-                <property name="visible">True</property>-                <property name="can_focus">False</property>-                <property name="label" translatable="yes">Diagram</property>-              </object>-              <packing>-                <property name="position">2</property>-                <property name="tab_fill">False</property>-              </packing>-            </child>           </object>           <packing>             <property name="expand">True</property>@@ -428,75 +675,10 @@       </object>     </child>   </object>--                  <object class="GtkMenu" id="filterStatsEtc">-                    <property name="visible">True</property>-                    <property name="can_focus">False</property>-                    <property name="halign">end</property>-                    <child>-                      <object class="GtkMenuItem" id="filter">-                        <property name="use_action_appearance">False</property>-                        <property name="visible">True</property>-                        <property name="sensitive">False</property>-                        <property name="can_focus">False</property>-                        <property name="label" translatable="yes">_Filter Visible Services…</property>-                        <property name="use_underline">True</property>-                        <accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>-                      </object>-                    </child>-                    <child>-                      <object class="GtkCheckMenuItem" id="statistics">-                        <property name="use_action_appearance">False</property>-                        <property name="visible">True</property>-                        <property name="sensitive">False</property>-                        <property name="can_focus">False</property>-                        <property name="label" translatable="yes">_Statistics</property>-                        <property name="use_underline">True</property>-                        <accelerator key="F9" signal="activate"/>-                      </object>-                    </child>-                    <child>-                      <object class="GtkSeparatorMenuItem" id="separatormenuitem1">-                        <property name="use_action_appearance">False</property>-                        <property name="visible">True</property>-                        <property name="can_focus">False</property>-                      </object>-                    </child>-                    <child>-                      <object class="GtkImageMenuItem" id="about">-                        <property name="label">gtk-about</property>-                        <property name="use_action_appearance">False</property>-                        <property name="visible">True</property>-                        <property name="can_focus">False</property>-                        <property name="use_underline">True</property>-                        <property name="use_stock">True</property>-                      </object>-                    </child>-                  </object>--                  <object class="GtkMenu" id="openMenu">-                    <property name="visible">True</property>-                    <property name="can_focus">False</property>-                    <child>-                      <object class="GtkImageMenuItem" id="open">-                        <property name="label">gtk-open</property>-                        <property name="use_action_appearance">False</property>-                        <property name="visible">True</property>-                        <property name="can_focus">False</property>-                        <property name="use_underline">True</property>-                        <property name="use_stock">True</property>-                        <accelerator key="o" signal="activate" modifiers="GDK_CONTROL_MASK"/>-                      </object>-                    </child>-                    <child>-                      <object class="GtkMenuItem" id="openTwo">-                        <property name="use_action_appearance">False</property>-                        <property name="visible">True</property>-                        <property name="can_focus">False</property>-                        <property name="tooltip_text" translatable="yes">Display two logs—one for the session bus, one for the system bus—side by side.</property>-                        <property name="label" translatable="yes">O_pen a Pair of Logs…</property>-                        <property name="use_underline">True</property>-                      </object>-                    </child>-                  </object>+  <object class="GtkSizeGroup">+    <widgets>+      <widget name="sidebarHeader"/>+      <widget name="sidebarStack"/>+    </widgets>+  </object> </interface>
− data/dfeet-method.png

binary file changed (459 → absent bytes)

− data/dfeet-signal.png

binary file changed (400 → absent bytes)

data/org.freedesktop.Bustle.appdata.xml.in view
@@ -1,36 +1,48 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Copyright 2014 Philip Withnall <philip@tecnocode.co.uk> -->-<application>-	<id type="desktop">org.freedesktop.Bustle.desktop</id>+<component type="desktop-application">+	<id>org.freedesktop.Bustle.desktop</id> 	<metadata_license>CC-BY-SA-3.0</metadata_license>-	<project_license>LGPL-2.1+ and GPL-2.0+ and GPL-3.0</project_license>-	<_name>Bustle</_name>-	<_summary>Draw sequence diagrams of D-Bus activity</_summary>+	<name>Bustle</name>+	<summary>Draw sequence diagrams of D-Bus activity</summary> 	<description> 		<!-- Translators: These are the application description paragraphs in the AppData file. -->-		<_p>Bustle draws sequence diagrams of D-Bus activity.+		<p>Bustle draws sequence diagrams of D-Bus activity. 		    It shows signal emissions, method calls and their 		    corresponding returns, with time stamps for each individual 		    event and the duration of each method call. This can help 		    you check for unwanted D-Bus traffic, and pinpoint why your-		    D-Bus-based application isn't performing as well as you+		    D-Bus-based application is not performing as well as you 		    like. It also provides statistics like signal frequencies-		    and average method call times.</_p>+		    and average method call times.</p> 	</description>+	<project_license>LGPL-2.1+ AND GPL-3.0</project_license> 	<screenshots>-		<screenshot type="default">-			<!-- Translators: This should be the URI of a 2400×1350, pixel screenshot of Bustle in your language. If you can't take a screenshot, leave the string as the English screenshot. -->-			<_image type="source">https://wiki.freedesktop.org/www/Software/Bustle/bustle-0.5.2-1.png</_image>-			</screenshot>-		<screenshot>-			<!-- Translators: This should be the URI of a 2400×1350, pixel screenshot of Bustle in your language. If you can't take a screenshot, leave the string as the English screenshot. -->-			<_image type="source">https://wiki.freedesktop.org/www/Software/Bustle/bustle-0.5.2-2.png</_image>+		<screenshot width="1548" height="848" type="default">+			<image>https://gitlab.freedesktop.org/bustle/bustle/raw/master/data/appdata/bustle-diagram.png</image>+			<caption>Explore sequence diagrams of D-Bus activity</caption> 		</screenshot>+		<screenshot width="1548" height="848">+			<image>https://gitlab.freedesktop.org/bustle/bustle/raw/master/data/appdata/bustle-statistics.png</image>+			<caption>See statistics summarizing the log</caption>+		</screenshot>+		<screenshot width="1548" height="848">+			<image>https://gitlab.freedesktop.org/bustle/bustle/raw/master/data/appdata/bustle-welcome.png</image>+			<caption>Relax with this soothing greyscale welcome page</caption>+		</screenshot> 	</screenshots> 	<url type="homepage">https://www.freedesktop.org/wiki/Software/Bustle/</url>-	<updatecontact>will_at_willthompson.co.uk</updatecontact>+	<update_contact>will_at_willthompson.co.uk</update_contact> 	<translation type="gettext">bustle</translation> 	<provides> 		<id>bustle.desktop</id> 	</provides>-</application>+	<releases>+		<release date="2018-06-15" version="0.7.1">+			<description>+				<p>It's now possible to monitor the system bus (from the user interface and with the bustle-pcap command-line tool), with no need to reconfigure the system bus. It's also possible to monitor an arbitrary bus by address.</p>+				<p>Bustle now requires that dbus-monitor (≥ 1.9.10) and pkexec are installed on your system.</p>+			</description>+		</release>+	</releases>+</component>
data/org.freedesktop.Bustle.desktop.in view
@@ -1,10 +1,10 @@ [Desktop Entry]-_Name=Bustle-_Comment=Draw sequence diagrams of D-Bus activity+Name=Bustle+Comment=Draw sequence diagrams of D-Bus activity Exec=bustle Icon=org.freedesktop.Bustle Terminal=false Type=Application Categories=GTK;Development;Debugger;Profiling; StartupNotify=true-_Keywords=debug;profile;d-bus;dbus;sequence;monitor;+Keywords=debug;profile;d-bus;dbus;sequence;monitor;
po/messages.pot view
@@ -1,52 +1,41 @@-# Translation file-+# SOME DESCRIPTIVE TITLE.+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER+# This file is distributed under the same license as the PACKAGE package.+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.+#+#, fuzzy msgid "" msgstr ""- "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n"-"POT-Creation-Date: 2009-01-13 06:05-0800\n"+"POT-Creation-Date: 2018-06-15 09:25+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n"+"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: Bustle/StatisticsPane.hs:210-#: Bustle/StatisticsPane.hs:213+#: Bustle/StatisticsPane.hs:191 Bustle/StatisticsPane.hs:194 msgid "%.1f ms" msgstr "" -#: Bustle/UI.hs:467-msgid "%s - Bustle"-msgstr ""- #: Bustle/Noninteractive.hs:55 msgid "(no interface)" msgstr "" -#: Bustle/UI/DetailsView.hs:148-msgid "<unknown>"-msgstr ""--#: Bustle/UI/DetailsView.hs:87-msgid "Arguments:"-msgstr ""--#: Bustle/StatisticsPane.hs:228-msgid "B"-msgstr ""--#: Bustle/UI/AboutDialog.hs:47+#: Bustle/UI/AboutDialog.hs:46 data/bustle.ui:101+#: data/org.freedesktop.Bustle.desktop.in:3+#: data/org.freedesktop.Bustle.appdata.xml.in:6 msgid "Bustle" msgstr "" -#: Bustle/StatisticsPane.hs:211+#: Bustle/StatisticsPane.hs:192 msgid "Calls" msgstr "" -#: Bustle/UI.hs:294+#: Bustle/UI.hs:317 msgid "Close _Without Saving" msgstr "" @@ -58,100 +47,222 @@ msgid "Couldn't parse '%s': %s" msgstr "" -#: Bustle/UI/DetailsView.hs:110-msgid "Directed signal"+#: Bustle/UI.hs:282+msgid "Couldn't save log" msgstr "" -#: Bustle/UI/DetailsView.hs:106+#: Bustle/StatisticsPane.hs:224 data/bustle.ui:610 msgid "Error" msgstr "" -#: Bustle/StatisticsPane.hs:171-msgid "Frequency"+#: Bustle/UI.hs:285+msgid ""+"Error: <i>%s</i>\n"+"\n"+"You might want to manually recover the log from the temporary file at\n"+"<tt>%s</tt>" msgstr "" -#: Bustle/UI.hs:293-msgid "If you don't save, this log will be lost forever."+#: Bustle/StatisticsPane.hs:152+msgid "Frequency" msgstr "" -#: Bustle/StatisticsPane.hs:229-msgid "KB"+#: Bustle/UI.hs:316+msgid "If you don't save, this log will be lost forever." msgstr "" -#: Bustle/StatisticsPane.hs:264+#: Bustle/StatisticsPane.hs:231 msgid "Largest" msgstr "" -#: Bustle/StatisticsPane.hs:230-msgid "MB"+#: Bustle/UI/Recorder.hs:86 Bustle/UI/Recorder.hs:111+msgid "Logged <b>%u</b> messages&#8230;" msgstr "" -#: Bustle/StatisticsPane.hs:212-#: Bustle/StatisticsPane.hs:263+#: Bustle/StatisticsPane.hs:193 Bustle/StatisticsPane.hs:230 msgid "Mean" msgstr "" -#: Bustle/StatisticsPane.hs:249+#: Bustle/StatisticsPane.hs:137 Bustle/StatisticsPane.hs:215 data/bustle.ui:511 msgid "Member" msgstr "" -#: Bustle/UI/DetailsView.hs:85-msgid "Member:"-msgstr ""--#: Bustle/StatisticsPane.hs:200+#: Bustle/StatisticsPane.hs:144 Bustle/StatisticsPane.hs:181 msgid "Method" msgstr "" -#: Bustle/UI/DetailsView.hs:104+#: Bustle/StatisticsPane.hs:222 data/bustle.ui:585 msgid "Method call" msgstr "" -#: Bustle/UI/DetailsView.hs:105+#: Bustle/StatisticsPane.hs:223 data/bustle.ui:597 msgid "Method return" msgstr "" -#: Bustle/StatisticsPane.hs:156-msgid "Name"+#: Bustle/UI.hs:309+msgid "Save log '%s' before closing?" msgstr "" -#: Bustle/UI/DetailsView.hs:127-msgid "No message body information is available. Please capture a fresh log using a recent version of Bustle!"+#: Bustle/StatisticsPane.hs:145 Bustle/StatisticsPane.hs:225 data/bustle.ui:623+msgid "Signal" msgstr "" -#: Bustle/Loader.hs:64-msgid "Parse error %s"+#: Bustle/StatisticsPane.hs:229+msgid "Smallest" msgstr "" -#: Bustle/UI/DetailsView.hs:84-msgid "Path:"+#: Bustle/UI/AboutDialog.hs:48+msgid "Someone's favourite D-Bus profiler" msgstr "" -#: Bustle/UI.hs:286-msgid "Save log '%s' before closing?"+#: Bustle/StatisticsPane.hs:190+msgid "Total" msgstr "" -#: Bustle/UI/DetailsView.hs:109-msgid "Signal"+#: Bustle/UI/FilterDialog.hs:123+msgid ""+"Unticking a service hides its column in the diagram, and all messages it is "+"involved in. That is, all methods it calls or are called on it, the "+"corresponding returns, and all signals it emits will be hidden." msgstr "" -#: Bustle/StatisticsPane.hs:262-msgid "Smallest"+#: Bustle/Util.hs:53+msgid "Warning: " msgstr "" -#: Bustle/UI/AboutDialog.hs:49-msgid "Someone's favourite D-Bus profiler"+#: Bustle/Loader/Pcap.hs:276+msgid ""+"libpcap 1.8.0 and 1.8.1 are incompatible with Bustle. See https://bugs."+"freedesktop.org/show_bug.cgi?id=100220#c7 for details. Distributions should "+"apply downstream patches until until a new upstream release is made; users "+"should install Bustle from Flathub, which already includes the necessary "+"patches: https://flathub.org/apps/details/org.freedesktop.Bustle" msgstr "" -#: Bustle/StatisticsPane.hs:209-msgid "Total"+#: data/bustle.ui:14+msgid "_Filter Visible Services…" msgstr "" -#: Bustle/UI/FilterDialog.hs:105-msgid "Unticking a service hides its column in the diagram, and all messages it is involved in. That is, all methods it calls or are called on it, the corresponding returns, and all signals it emits will be hidden."+#: data/bustle.ui:24+msgid "_Statistics" msgstr "" -#: Bustle/Util.hs:53-msgid "Warning: "+#: data/bustle.ui:62+msgid ""+"Display two logs—one for the session bus, one for the system bus—side by "+"side." msgstr "" +#: data/bustle.ui:63+msgid "O_pen a Pair of Logs…"+msgstr ""++#: data/bustle.ui:75+msgid "Record S_ession Bus"+msgstr ""++#: data/bustle.ui:84+msgid "Record S_ystem Bus"+msgstr ""++#: data/bustle.ui:93+msgid "Record _Address…"+msgstr ""++#: data/bustle.ui:136+msgid "Record a new log"+msgstr ""++#: data/bustle.ui:147+msgid "_Record"+msgstr ""++#: data/bustle.ui:183+msgid "Open an existing log"+msgstr ""++#: data/bustle.ui:230+msgid "Export as PDF"+msgstr ""++#: data/bustle.ui:254+msgid "Save"+msgstr ""++#: data/bustle.ui:316+msgid ""+"Start recording D-Bus activity with the <b>Record</b> button above\n"+" You can also run <i>dbus-monitor --pcap</i> from the command line"+msgstr ""++#: data/bustle.ui:335+msgid "Welcome to Bustle"+msgstr ""++#: data/bustle.ui:360+msgid "<big><b>Waiting for D-Bus traffic; please hold…</b></big>"+msgstr ""++#: data/bustle.ui:387+msgid "Frequencies"+msgstr ""++#: data/bustle.ui:401+msgid "Durations"+msgstr ""++#: data/bustle.ui:416+msgid "Sizes"+msgstr ""++#: data/bustle.ui:463+msgid "Type"+msgstr ""++#: data/bustle.ui:479+msgid "Path"+msgstr ""++#: data/bustle.ui:543+msgid "Arguments"+msgstr ""++#: data/bustle.ui:636+msgid "Directed signal"+msgstr ""++#: data/org.freedesktop.Bustle.desktop.in:4+#: data/org.freedesktop.Bustle.appdata.xml.in:7+msgid "Draw sequence diagrams of D-Bus activity"+msgstr ""++#: data/org.freedesktop.Bustle.desktop.in:6+msgid "org.freedesktop.Bustle"+msgstr ""++#: data/org.freedesktop.Bustle.desktop.in:11+msgid "debug;profile;d-bus;dbus;sequence;monitor;"+msgstr ""++#. Translators: These are the application description paragraphs in the AppData file.+#: data/org.freedesktop.Bustle.appdata.xml.in:10+msgid ""+"Bustle draws sequence diagrams of D-Bus activity. It shows signal emissions, "+"method calls and their corresponding returns, with time stamps for each "+"individual event and the duration of each method call. This can help you "+"check for unwanted D-Bus traffic, and pinpoint why your D-Bus-based "+"application is not performing as well as you like. It also provides "+"statistics like signal frequencies and average method call times."+msgstr ""++#: data/org.freedesktop.Bustle.appdata.xml.in:23+msgid "Explore sequence diagrams of D-Bus activity"+msgstr ""++#: data/org.freedesktop.Bustle.appdata.xml.in:27+msgid "See statistics summarizing the log"+msgstr ""++#: data/org.freedesktop.Bustle.appdata.xml.in:31+msgid "Relax with this soothing greyscale welcome page"+msgstr ""
+ src-hgettext/Bustle/Translation.hs view
@@ -0,0 +1,25 @@+module Bustle.Translation+    (+      initTranslation+    , __+    )+where++import Text.I18N.GetText+import System.Locale.SetLocale+import System.IO.Unsafe++import GetText_bustle++initTranslation :: IO ()+initTranslation = do+    setLocale LC_ALL (Just "")+    domain <- getMessageCatalogDomain+    dir <- getMessageCatalogDir+    bindTextDomain domain (Just dir)+    textDomain (Just domain)+    return ()++__ :: String -> String+-- FIXME: I do not like this unsafePerformIO one little bit.+__ = unsafePerformIO . getText
+ src-no-hgettext/Bustle/Translation.hs view
@@ -0,0 +1,12 @@+module Bustle.Translation+    (+      initTranslation+    , __+    )+where++initTranslation :: IO ()+initTranslation = return ()++__ :: String -> String+__ = id