packages feed

movie-monad 0.0.0.0 → 0.0.1.0

raw patch · 4 files changed

+323/−127 lines, 4 filesdep +gi-gdkdep ~gi-gdkx11dep ~gi-xlib

Dependencies added: gi-gdk

Dependency ranges changed: gi-gdkx11, gi-xlib

Files

README.md view
@@ -6,7 +6,7 @@  ## Screenshots -<img src="http://i.imgur.com/QOMXSwo.jpg" width="600" alt="GUI" title= "GUI" />+![GUI showing Sintel from the Blender Foundation](https://i.imgur.com/SLse3s9.jpg)  ## Documentation @@ -18,23 +18,24 @@  ```bash # Install Git+# Install XQuartz (https://www.xquartz.org/) if using Mac OSX or macOS+# Install GTK+ 3.* (https://www.gtk.org/download/index.php) # Install Haskell # Install Haksell Stack # Install ExifTool git clone https://github.com/lettier/movie-monad.git cd movie-monad/ stack setup-stack install --dependencies-only-haskell-gi -o lib/gi-gdkx11/GdkX11.overrides -O lib/gi-gdkx11 GdkX11-3.0-haskell-gi -o lib/gi-xlib/xlib.overrides     -O lib/gi-xlib xlib-stack build stack install stack exec -- movie-monad+# Or just `movie-monad` if `stack path | grep local-bin-path` is in your `echo $PATH` ```  ## License  See [LICENSE](LICENSE).++## Copyright  (C) 2017 David Lettier   [lettier.com](http://www.lettier.com/)
movie-monad.cabal view
@@ -1,5 +1,5 @@ name:                 movie-monad-version:              0.0.0.0+version:              0.0.1.0 synopsis:             Plays videos using GStreamer and GTK+. description:          Desktop video player that uses GStreamer and GTK+. homepage:             https://github.com/lettier/movie-monad@@ -32,13 +32,14 @@                       , process == 1.4.*                       , MissingH == 1.4.*                       , haskell-gi-base == 0.20.*+                      , gi-xlib == 2.0.*                       , gi-gobject == 2.0.*-                      , gi-gst == 1.0.*-                      , gi-gtk == 3.0.*                       , gi-glib == 2.0.*-                      , gi-xlib == 0.1.6.*+                      , gi-gdkx11 == 3.0.*                       , gi-gstvideo == 1.0.*-                      , gi-gdkx11 == 0.3.*+                      , gi-gdk == 3.0.*+                      , gi-gst == 1.0.*+                      , gi-gtk == 3.0.*   ghc-options:        -threaded -with-rtsopts=-N -Wall   other-modules:      Paths_movie_monad   hs-source-dirs:     ./src/
src/Main.hs view
@@ -15,17 +15,20 @@ import System.Exit import Control.Monad import Control.Exception+import Text.Read+import Data.IORef import Data.Maybe import Data.Int import Data.Text-import Data.String.Utils import Data.GI.Base+import Data.GI.Base.Signals import Data.GI.Base.Properties import GI.GLib import GI.GObject import qualified GI.Gtk import GI.Gst import GI.GstVideo+import GI.Gdk import GI.GdkX11 import Paths_movie_monad @@ -50,99 +53,49 @@   onOffSwitch <- builderGetObject GI.Gtk.Switch builder "on-off-switch"   volumeButton <- builderGetObject GI.Gtk.VolumeButton builder "volume-button"   desiredVideoWidthComboBox <- builderGetObject GI.Gtk.ComboBoxText builder "desired-video-width-combo-box"+  fullscreenButton <- builderGetObject GI.Gtk.Button builder "fullscreen-button"   errorMessageDialog <- builderGetObject GI.Gtk.MessageDialog builder "error-message-dialog"   aboutButton <- builderGetObject GI.Gtk.Button builder "about-button"   aboutDialog <- builderGetObject GI.Gtk.AboutDialog builder "about-dialog"    playbin <- fromJust <$> GI.Gst.elementFactoryMake "playbin" (Just "MultimediaPlayer") -  _ <- GI.Gtk.onWidgetRealize drawingArea $ do-    gdkWindow <- fromJust <$> GI.Gtk.widgetGetWindow drawingArea-    x11Window <- GI.Gtk.unsafeCastTo GI.GdkX11.X11Window gdkWindow--    xid <- GI.GdkX11.x11WindowGetXid x11Window-    let xid' = fromIntegral xid :: CUIntPtr--    GI.GstVideo.videoOverlaySetWindowHandle (GstElement playbin) xid'--  _ <- GI.Gtk.onFileChooserButtonFileSet fileChooserButton $ do-    _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull--    filename <- fromJust <$> GI.Gtk.fileChooserGetFilename fileChooserButton-    let uri = "file://" ++ filename--    volume <- GI.Gtk.scaleButtonGetValue volumeButton-    Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume-    Data.GI.Base.Properties.setObjectPropertyString playbin "uri" (Just $ pack uri)--    desiredVideoWidth <- getDesiredVideoWidth desiredVideoWidthComboBox-    (success, width, height) <- getWindowSize desiredVideoWidth filename--    if success-      then do-        _ <- GI.Gst.elementSetState playbin GI.Gst.StatePlaying-        GI.Gtk.switchSetActive onOffSwitch True-        setWindowSize width height fileChooserButton drawingArea window-      else do-        _ <- GI.Gst.elementSetState playbin GI.Gst.StatePaused-        GI.Gtk.switchSetActive onOffSwitch False-        resetWindowSize desiredVideoWidth fileChooserButton drawingArea window-        _ <- GI.Gtk.onDialogResponse errorMessageDialog (\ _ -> GI.Gtk.widgetHide errorMessageDialog)-        void $ GI.Gtk.dialogRun errorMessageDialog--  _ <- GI.Gtk.onSwitchStateSet onOffSwitch $ \ switchOn -> do-    if switchOn-      then void $ GI.Gst.elementSetState playbin GI.Gst.StatePlaying-      else void $ GI.Gst.elementSetState playbin GI.Gst.StatePaused-    return switchOn--  _ <- GI.Gtk.onScaleButtonValueChanged volumeButton $-      \ volume -> void $ Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume+  isWindowFullScreenRef <- newIORef False -  seekScaleHandlerId <- GI.Gtk.onRangeValueChanged seekScale $ do-    (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime+  _ <- GI.Gtk.onWidgetRealize drawingArea $ onDrawingAreaRealize drawingArea playbin fullscreenButton -    when couldQueryDuration $ do-      percentage' <- GI.Gtk.rangeGetValue seekScale-      let percentage = percentage' / 100.0-      let position = fromIntegral (round ((fromIntegral duration :: Double) * percentage) :: Int) :: Int64-      void $ GI.Gst.elementSeekSimple playbin GI.Gst.FormatTime [ GI.Gst.SeekFlagsFlush ] position+  _ <- GI.Gtk.onFileChooserButtonFileSet fileChooserButton $+    onFileChooserButtonFileSet+      playbin+      fileChooserButton+      volumeButton+      isWindowFullScreenRef+      desiredVideoWidthComboBox+      onOffSwitch+      fullscreenButton+      drawingArea+      window+      errorMessageDialog -  _ <- GI.GLib.timeoutAddSeconds GI.GLib.PRIORITY_DEFAULT 1 $ do-    (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime-    (couldQueryPosition, position) <- GI.Gst.elementQueryPosition playbin GI.Gst.FormatTime+  _ <- GI.Gtk.onSwitchStateSet onOffSwitch (onSwitchStateSet playbin) -    let percentage =-          if couldQueryDuration && couldQueryPosition && duration > 0-            then 100.0 * (fromIntegral position / fromIntegral duration :: Double)-            else 0.0+  _ <- GI.Gtk.onScaleButtonValueChanged volumeButton (onScaleButtonValueChanged playbin) -    GI.GObject.signalHandlerBlock seekScale seekScaleHandlerId-    GI.Gtk.rangeSetValue seekScale percentage-    GI.GObject.signalHandlerUnblock seekScale seekScaleHandlerId+  seekScaleHandlerId <- GI.Gtk.onRangeValueChanged seekScale (onRangeValueChanged playbin seekScale) -    return True+  _ <- GI.GLib.timeoutAddSeconds GI.GLib.PRIORITY_DEFAULT 1 (updateSeekScale playbin seekScale seekScaleHandlerId) -  _ <- GI.Gtk.onComboBoxChanged desiredVideoWidthComboBox $ do-    filename' <- GI.Gtk.fileChooserGetFilename fileChooserButton-    let filename = fromMaybe "" filename'+  _ <- GI.Gtk.onComboBoxChanged desiredVideoWidthComboBox $+      onComboBoxChanged fileChooserButton desiredVideoWidthComboBox drawingArea window -    desiredVideoWidth <- getDesiredVideoWidth desiredVideoWidthComboBox-    (success, width, height) <- getWindowSize desiredVideoWidth filename+  _ <- GI.Gtk.onWidgetButtonReleaseEvent fullscreenButton+      (onFullscreenButtonRelease isWindowFullScreenRef desiredVideoWidthComboBox fileChooserButton window) -    if success-      then setWindowSize width height fileChooserButton drawingArea window-      else resetWindowSize desiredVideoWidth fileChooserButton drawingArea window+  _ <- GI.Gtk.onWidgetWindowStateEvent window (onWidgetWindowStateEvent isWindowFullScreenRef) -  _ <- GI.Gtk.onWidgetButtonReleaseEvent aboutButton $ \ _ -> do-    _ <- GI.Gtk.onDialogResponse aboutDialog (\ _ -> GI.Gtk.widgetHide aboutDialog)-    void $ GI.Gtk.dialogRun aboutDialog-    return True+  _ <- GI.Gtk.onWidgetButtonReleaseEvent aboutButton (onAboutButtonRelease aboutDialog) -  _ <- GI.Gtk.onWidgetDestroy window $ do-    _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull-    _ <- GI.Gst.objectUnref playbin-    GI.Gtk.mainQuit+  _ <- GI.Gtk.onWidgetDestroy window (onWindowDestroy playbin)    GI.Gtk.widgetShowAll window   GI.Gtk.main@@ -157,7 +110,210 @@   fromJust <$> GI.Gtk.builderGetObject builder (pack objectId) >>=     GI.Gtk.unsafeCastTo objectTypeClass -getVideoInfo :: Prelude.String -> Prelude.String -> IO (Bool, Prelude.String)+onDrawingAreaRealize ::+  GI.Gtk.Widget ->+  GI.Gst.Element ->+  GI.Gtk.Button ->+  GI.Gtk.WidgetRealizeCallback+onDrawingAreaRealize drawingArea playbin fullscreenButton = do+  gdkWindow <- fromJust <$> GI.Gtk.widgetGetWindow drawingArea+  x11Window <- GI.Gtk.unsafeCastTo GI.GdkX11.X11Window gdkWindow++  xid <- GI.GdkX11.x11WindowGetXid x11Window+  let xid' = fromIntegral xid :: CUIntPtr++  GI.GstVideo.videoOverlaySetWindowHandle (GstElement playbin) xid'++  GI.Gtk.widgetHide fullscreenButton++onFileChooserButtonFileSet ::+  GI.Gst.Element ->+  GI.Gtk.FileChooserButton ->+  GI.Gtk.VolumeButton ->+  IORef Bool ->+  GI.Gtk.ComboBoxText ->+  GI.Gtk.Switch ->+  GI.Gtk.Button ->+  GI.Gtk.Widget ->+  GI.Gtk.Window ->+  GI.Gtk.MessageDialog ->+  GI.Gtk.FileChooserButtonFileSetCallback+onFileChooserButtonFileSet+  playbin+  fileChooserButton+  volumeButton+  isWindowFullScreenRef+  desiredVideoWidthComboBox+  onOffSwitch+  fullscreenButton+  drawingArea+  window+  errorMessageDialog+  = do+  _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull++  filename <- fromJust <$> GI.Gtk.fileChooserGetFilename fileChooserButton++  setPlaybinUriAndVolume playbin filename volumeButton++  isWindowFullScreen <- readIORef isWindowFullScreenRef++  desiredVideoWidth <- getDesiredVideoWidth desiredVideoWidthComboBox+  maybeWindowSize <- getWindowSize desiredVideoWidth filename++  case maybeWindowSize of+    Nothing -> do+      _ <- GI.Gst.elementSetState playbin GI.Gst.StatePaused+      GI.Gtk.windowUnfullscreen window+      GI.Gtk.switchSetActive onOffSwitch False+      GI.Gtk.widgetHide fullscreenButton+      GI.Gtk.widgetShow desiredVideoWidthComboBox+      resetWindowSize desiredVideoWidth fileChooserButton drawingArea window+      _ <- GI.Gtk.onDialogResponse errorMessageDialog (\ _ -> GI.Gtk.widgetHide errorMessageDialog)+      void $ GI.Gtk.dialogRun errorMessageDialog+    Just (width, height) -> do+      _ <- GI.Gst.elementSetState playbin GI.Gst.StatePlaying+      GI.Gtk.switchSetActive onOffSwitch True+      GI.Gtk.widgetShow fullscreenButton+      unless isWindowFullScreen $ setWindowSize width height fileChooserButton drawingArea window++onSwitchStateSet ::+  GI.Gst.Element ->+  Bool ->+  IO Bool+onSwitchStateSet playbin switchOn = do+  if switchOn+    then void $ GI.Gst.elementSetState playbin GI.Gst.StatePlaying+    else void $ GI.Gst.elementSetState playbin GI.Gst.StatePaused+  return switchOn++onScaleButtonValueChanged ::+  GI.Gst.Element ->+  Double ->+  IO ()+onScaleButtonValueChanged playbin volume =+    void $ Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume++onRangeValueChanged ::+  GI.Gst.Element ->+  GI.Gtk.Scale ->+  IO ()+onRangeValueChanged playbin seekScale = do+  (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime++  when couldQueryDuration $ do+    percentage' <- GI.Gtk.rangeGetValue seekScale+    let percentage = percentage' / 100.0+    let position = fromIntegral (round ((fromIntegral duration :: Double) * percentage) :: Int) :: Int64+    void $ GI.Gst.elementSeekSimple playbin GI.Gst.FormatTime [ GI.Gst.SeekFlagsFlush ] position++updateSeekScale ::+  GI.Gst.Element ->+  GI.Gtk.Scale ->+  Data.GI.Base.Signals.SignalHandlerId ->+  IO Bool+updateSeekScale playbin seekScale seekScaleHandlerId = do+  (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime+  (couldQueryPosition, position) <- GI.Gst.elementQueryPosition playbin GI.Gst.FormatTime++  let percentage =+        if couldQueryDuration && couldQueryPosition && duration > 0+          then 100.0 * (fromIntegral position / fromIntegral duration :: Double)+          else 0.0++  GI.GObject.signalHandlerBlock seekScale seekScaleHandlerId+  GI.Gtk.rangeSetValue seekScale percentage+  GI.GObject.signalHandlerUnblock seekScale seekScaleHandlerId++  return True++onComboBoxChanged ::+  GI.Gtk.FileChooserButton ->+  GI.Gtk.ComboBoxText ->+  GI.Gtk.Widget ->+  GI.Gtk.Window ->+  IO ()+onComboBoxChanged+  fileChooserButton+  desiredVideoWidthComboBox+  drawingArea+  window+  = do+  filename' <- GI.Gtk.fileChooserGetFilename fileChooserButton+  let filename = fromMaybe "" filename'++  desiredVideoWidth <- getDesiredVideoWidth desiredVideoWidthComboBox+  maybeWindowSize <- getWindowSize desiredVideoWidth filename++  case maybeWindowSize of+    Nothing -> resetWindowSize desiredVideoWidth fileChooserButton drawingArea window+    Just (width, height) -> setWindowSize width height fileChooserButton drawingArea window++onFullscreenButtonRelease ::+  IORef Bool ->+  GI.Gtk.ComboBoxText ->+  GI.Gtk.FileChooserButton ->+  GI.Gtk.Window ->+  GI.Gdk.EventButton ->+  IO Bool+onFullscreenButtonRelease+  isWindowFullScreenRef+  desiredVideoWidthComboBox+  fileChooserButton+  window+  _+  = do+  isWindowFullScreen <- readIORef isWindowFullScreenRef+  if isWindowFullScreen+    then do+      GI.Gtk.widgetShow desiredVideoWidthComboBox+      GI.Gtk.widgetShow fileChooserButton+      void $ GI.Gtk.windowUnfullscreen window+    else do+      GI.Gtk.widgetHide desiredVideoWidthComboBox+      GI.Gtk.widgetHide fileChooserButton+      void $ GI.Gtk.windowFullscreen window+  return True++onWidgetWindowStateEvent ::+  IORef Bool ->+  GI.Gdk.EventWindowState ->+  IO Bool+onWidgetWindowStateEvent isWindowFullScreenRef eventWindowState = do+  windowStates <- GI.Gdk.getEventWindowStateNewWindowState eventWindowState+  let isWindowFullScreen = Prelude.foldl (\ acc x -> acc || GI.Gdk.WindowStateFullscreen == x) False windowStates+  writeIORef isWindowFullScreenRef isWindowFullScreen+  return True++onAboutButtonRelease ::+  GI.Gtk.AboutDialog ->+  GI.Gdk.EventButton ->+  IO Bool+onAboutButtonRelease aboutDialog _ = do+  _ <- GI.Gtk.onDialogResponse aboutDialog (\ _ -> GI.Gtk.widgetHide aboutDialog)+  _ <- GI.Gtk.dialogRun aboutDialog+  return True++onWindowDestroy ::+  GI.Gst.Element ->+  IO ()+onWindowDestroy playbin = do+  _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull+  _ <- GI.Gst.objectUnref playbin+  GI.Gtk.mainQuit++setPlaybinUriAndVolume ::+  GI.Gst.Element ->+  Prelude.String ->+  GI.Gtk.VolumeButton ->+  IO ()+setPlaybinUriAndVolume playbin filename volumeButton = do+  let uri = "file://" ++ filename+  volume <- GI.Gtk.scaleButtonGetValue volumeButton+  Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume+  Data.GI.Base.Properties.setObjectPropertyString playbin "uri" (Just $ pack uri)++getVideoInfo :: Prelude.String -> Prelude.String -> IO (Maybe Prelude.String) getVideoInfo flag filename = do   (code, out, _) <- catch (       readProcessWithExitCode@@ -165,40 +321,43 @@         [flag, "-s", "-S", filename]         ""     ) (\ (_ :: Control.Exception.IOException) -> return (ExitFailure 1, "", ""))-  return (code == System.Exit.ExitSuccess, out)+  if code == System.Exit.ExitSuccess+    then return (Just out)+    else return Nothing  isVideo :: Prelude.String -> IO Bool isVideo filename = do-  (success, out) <- getVideoInfo "-MIMEType" filename-  return (success && isInfixOf "video" (pack out))--getWindowSize :: Int -> Prelude.String -> IO (Bool, Int32, Int32)-getWindowSize desiredVideoWidth filename = do-  let defaultWidth = 800-  let defaultHeight = 600--  video <- isVideo filename--  if video-    then do-      (success, out) <- getVideoInfo "-ImageSize" filename-      if success && isInfixOf "x" (pack out)-        then do-          let (width''':height''':_) =-                Data.String.Utils.split "x" $ Data.String.Utils.strip out--          let width'' = read width''' :: Int-          let height'' = read height''' :: Int--          let ratio = fromIntegral height'' / fromIntegral width'' :: Double-          let width' = fromIntegral desiredVideoWidth :: Double-          let height' = width' * ratio-          let width = fromIntegral (round width' :: Int) :: Int32-          let height = fromIntegral (round height' :: Int) :: Int32+  maybeOut <- getVideoInfo "-MIMEType" filename+  case maybeOut of+    Nothing -> return False+    Just out -> return ("video" `isInfixOf` pack out) -          return (True, width, height)-        else return (False, defaultHeight, defaultWidth)-    else return (False, defaultHeight, defaultWidth)+getWindowSize :: Int -> Prelude.String -> IO (Maybe (Int32, Int32))+getWindowSize desiredVideoWidth filename =+  isVideo filename >>=+  getWidthHeightString >>=+  splitWidthHeightString >>=+  widthHeightToDouble >>=+  ratio >>=+  windowSize+  where+    getWidthHeightString :: Bool -> IO (Maybe Prelude.String)+    getWidthHeightString False = return Nothing+    getWidthHeightString True = getVideoInfo "-ImageSize" filename+    splitWidthHeightString :: Maybe Prelude.String -> IO (Maybe [Text])+    splitWidthHeightString Nothing = return Nothing+    splitWidthHeightString (Just string) = return (Just (Data.Text.splitOn "x" (pack string)))+    widthHeightToDouble :: Maybe [Text] -> IO (Maybe Double, Maybe Double)+    widthHeightToDouble (Just (x:y:_)) = return (readMaybe (unpack x) :: Maybe Double, readMaybe (unpack y) :: Maybe Double)+    widthHeightToDouble _ = return (Nothing, Nothing)+    ratio :: (Maybe Double, Maybe Double) -> IO (Maybe Double)+    ratio (Just width, Just height) =+      if width <= 0.0 then return Nothing else return (Just (height / width))+    ratio _ = return Nothing+    windowSize :: Maybe Double -> IO (Maybe (Int32, Int32))+    windowSize Nothing = return Nothing+    windowSize (Just ratio') =+      return (Just (fromIntegral desiredVideoWidth :: Int32, round ((fromIntegral desiredVideoWidth :: Double) *  ratio') :: Int32))  getDesiredVideoWidth :: GI.Gtk.ComboBoxText -> IO Int getDesiredVideoWidth = fmap (\ x -> read (Data.Text.unpack x) :: Int) . GI.Gtk.comboBoxTextGetActiveText@@ -229,4 +388,5 @@   IO () resetWindowSize width' fileChooserButton drawingArea window = do   let width = fromIntegral width' :: Int32+  GI.Gtk.widgetQueueDraw drawingArea   setWindowSize width 0 fileChooserButton drawingArea window
src/data/gui.glade view
@@ -2,11 +2,21 @@ <!-- Generated with glade 3.20.0 --> <interface>   <requires lib="gtk+" version="3.20"/>+  <object class="GtkImage" id="about-image">+    <property name="visible">True</property>+    <property name="can_focus">False</property>+    <property name="stock">gtk-dialog-question</property>+  </object>   <object class="GtkAdjustment" id="adjustment1">     <property name="upper">100</property>     <property name="step_increment">1</property>     <property name="page_increment">10</property>   </object>+  <object class="GtkImage" id="fullscreen-image">+    <property name="visible">True</property>+    <property name="can_focus">False</property>+    <property name="stock">gtk-fullscreen</property>+  </object>   <object class="GtkWindow" id="window">     <property name="can_focus">False</property>     <property name="title" translatable="yes">Movie Monad</property>@@ -22,13 +32,18 @@           <object class="GtkBox">             <property name="visible">True</property>             <property name="can_focus">False</property>+            <property name="hexpand">True</property>+            <property name="vexpand">True</property>             <property name="orientation">vertical</property>             <child>               <object class="GtkFileChooserButton" id="file-chooser-button">                 <property name="width_request">800</property>                 <property name="visible">True</property>+                <property name="app_paintable">True</property>                 <property name="can_focus">False</property>                 <property name="tooltip_text" translatable="yes">Select a video file.</property>+                <property name="valign">start</property>+                <property name="hexpand">True</property>                 <property name="create_folders">False</property>                 <property name="title" translatable="yes">Movie Monad</property>               </object>@@ -44,10 +59,12 @@                 <property name="sensitive">False</property>                 <property name="app_paintable">True</property>                 <property name="can_focus">False</property>+                <property name="hexpand">True</property>+                <property name="vexpand">True</property>               </object>               <packing>-                <property name="expand">False</property>-                <property name="fill">False</property>+                <property name="expand">True</property>+                <property name="fill">True</property>                 <property name="position">1</property>               </packing>             </child>@@ -55,8 +72,10 @@               <object class="GtkScale" id="seek-scale">                 <property name="visible">True</property>                 <property name="can_focus">True</property>+                <property name="valign">end</property>                 <property name="margin_left">5</property>                 <property name="margin_right">5</property>+                <property name="hexpand">True</property>                 <property name="adjustment">adjustment1</property>                 <property name="show_fill_level">True</property>                 <property name="fill_level">100</property>@@ -66,14 +85,14 @@                 <property name="value_pos">left</property>               </object>               <packing>-                <property name="expand">True</property>-                <property name="fill">True</property>+                <property name="expand">False</property>+                <property name="fill">False</property>                 <property name="position">2</property>               </packing>             </child>           </object>           <packing>-            <property name="expand">False</property>+            <property name="expand">True</property>             <property name="fill">True</property>             <property name="position">0</property>           </packing>@@ -82,7 +101,9 @@           <object class="GtkBox">             <property name="visible">True</property>             <property name="can_focus">False</property>+            <property name="valign">end</property>             <property name="margin_bottom">5</property>+            <property name="hexpand">True</property>             <child>               <object class="GtkSwitch" id="on-off-switch">                 <property name="visible">True</property>@@ -172,12 +193,11 @@             </child>             <child>               <object class="GtkButton" id="about-button">-                <property name="label">gtk-about</property>                 <property name="visible">True</property>                 <property name="can_focus">True</property>                 <property name="receives_default">True</property>                 <property name="margin_right">5</property>-                <property name="use_stock">True</property>+                <property name="image">about-image</property>                 <property name="always_show_image">True</property>               </object>               <packing>@@ -187,10 +207,24 @@                 <property name="position">3</property>               </packing>             </child>+            <child>+              <object class="GtkButton" id="fullscreen-button">+                <property name="can_focus">True</property>+                <property name="receives_default">True</property>+                <property name="tooltip_text" translatable="yes">Toggle on or off fullscreen.</property>+                <property name="image">fullscreen-image</property>+                <property name="always_show_image">True</property>+              </object>+              <packing>+                <property name="expand">False</property>+                <property name="fill">True</property>+                <property name="position">4</property>+              </packing>+            </child>           </object>           <packing>             <property name="expand">False</property>-            <property name="fill">True</property>+            <property name="fill">False</property>             <property name="position">1</property>           </packing>         </child>@@ -210,7 +244,7 @@     <property name="transient_for">window</property>     <property name="attached_to">window</property>     <property name="program_name">Movie Monad</property>-    <property name="version">0.0.0.0</property>+    <property name="version">0.0.1.0</property>     <property name="copyright" translatable="yes">(C) 2017 David Lettier</property>     <property name="website">https://github.com/lettier/movie-monad</property>     <property name="website_label" translatable="yes">Update</property>