Saving Window State

Quite often, we want the window state to persist between sessions. If the user resizes or maximizes the window, they might expect to find it in the same state the next time they open the app. GTK does not provide this functionality out of the box, but luckily it is not too hard to manually implement it. We basically want two integers (height & width) and a boolean (is_maximized) to persist. We already know how to do this by using Settings.

Filename: listings/saving_window_state/1/org.gtk.example.gschema.xml

<?xml version="1.0" encoding="utf-8"?>
  <schema id="org.gtk.example" path="/org/gtk/example/">
    <key name="window-width" type="i">
      <summary>Default window width</summary>
    <key name="window-height" type="i">
      <summary>Default window height</summary>
    <key name="is-maximized" type="b">
      <summary>Default window maximized behaviour</summary>

Since we do not care about intermediate state, we only load the window state when the window is constructed and save it when we close the window. That can be done by creating a custom window. First, we create one and add methods for getting and setting the window state.

Filename: listings/saving_window_state/1/custom_window/

mod imp;

use glib::Object;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::Application;
use gtk::{gio, glib};

glib::wrapper! {
    pub struct Window(ObjectSubclass<imp::Window>)
        @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
        @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
                    gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;

impl Window {
    pub fn new(app: &Application) -> Self {
        // Create new window
        Object::new(&[("application", app)]).expect("Failed to create `Window`.")

    pub fn save_window_size(&self) -> Result<(), glib::BoolError> {
        // Get `settings` from `imp::Window`
        let settings = &imp::Window::from_instance(self).settings;

        // Get the size of the window
        let size = self.default_size();

        // Set the window state in `settings`
        settings.set_int("window-width", size.0)?;
        settings.set_int("window-height", size.1)?;
        settings.set_boolean("is-maximized", self.is_maximized())?;


    fn load_window_size(&self) {
        // Get `settings` from `imp::Window`
        let settings = &imp::Window::from_instance(self).settings;

        // Get the window state from `settings`
        let width ="window-width");
        let height ="window-height");
        let is_maximized = settings.boolean("is-maximized");

        // Set the size of the window
        self.set_default_size(width, height);

        // If the window was maximized when it was closed, maximize it again
        if is_maximized {

The implementation struct holds the settings. We also override the constructed and close_request methods, where we load or save the window state.

Filename: listings/saving_window_state/1/custom_window/

use gio::Settings;
use glib::signal::Inhibit;
use gtk::{gio, glib};
use gtk::{subclass::prelude::*, ApplicationWindow};

pub struct Window {
    pub settings: Settings,

impl ObjectSubclass for Window {
    const NAME: &'static str = "MyGtkAppWindow";
    type Type = super::Window;
    type ParentType = ApplicationWindow;

    fn new() -> Self {
        Self {
            settings: Settings::new("org.gtk.example"),
impl ObjectImpl for Window {
    fn constructed(&self, obj: &Self::Type) {
        // Load latest window state
impl WidgetImpl for Window {}
impl WindowImpl for Window {
    // Save window state right before the window will be closed
    fn close_request(&self, obj: &Self::Type) -> Inhibit {
        if let Err(err) = obj.save_window_size() {
            log::error!("Failed to save window state, {}", &err);
        // Do not inhibit the default handler
impl ApplicationWindowImpl for Window {}

That is it! Now our window retains its state between app sessions.

Please note how we handle a failure in saving into the settings. We do not want to panic for recoverable errors. We might also not want to present all problems at the GUI. In our case we could not even do this, because the window will be immediately closed after the error occurs. Logging is the standard way of handling a situation like this. For that, we need to add the log crate and one of its front-ends, such as pretty_env_logger, to our dependencies.

Filename: listings/Cargo.toml

log = "0.4"
pretty_env_logger = "0.4"

We then have to initialize pretty_env_logger by calling init in main.

Filename: listings/saving_window_state/1/

mod custom_window;

use custom_window::Window;
use gtk::prelude::*;
use gtk::{Application, Button};

fn main() {
    // Initialize logger

    // Create a new application
    let app = Application::builder()

    // Connect to "activate" signal of `app`

    // Run the application;

fn build_ui(app: &Application) {
    // Create a window
    let window = Window::new(app);

    // Create a button
    let button = Button::builder()
        .label("Press me!")

    // Connect to "clicked" signal of `button`
    button.connect_clicked(move |button| {
        // Set the label to "Hello World!" after the button has been clicked on
        button.set_label("Hello World!");

    // Add button

We can now modify the log level by setting the RUST_LOG environment variable as can be seen here