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 +6/−5
- movie-monad.cabal +6/−5
- src/Main.hs +268/−108
- src/data/gui.glade +43/−9
README.md view
@@ -6,7 +6,7 @@ ## Screenshots -<img src="http://i.imgur.com/QOMXSwo.jpg" width="600" alt="GUI" title= "GUI" />+ ## 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>