SUSI Linux app provides access to SUSI on Linux distributions on desktop as well as hardware devices like Raspberry Pi. The settings for SUSI Linux are controlled with the use of a config.json file. You may edit the file manually, but to provide safe configurations, we have a config generator script. You may run the script to configure settings like TTS Engine, STT Engine, authentication, choice about the hotword engine etc. Generally, it is easier to configure application settings through a GUI. Thus, we added a GUI for it using PyGTK and Glade.
Glade is a GUI designer for GNOME based Linux systems. I wrote a blog about how to create user interfaces in Glade and access it from Python code in SUSI Linux. Now, for creating UI for Configuration screen, we need to choose an ideal layout. Glade provides various choices like BoxLayout, GridLayout, FlowBox, ListBox , Notebook etc. Since, we need to display only basic settings options, we select the BoxLayout for this purpose.
BoxLayout as the name suggests, forms a box like arrangement for widgets. You can arrange the widgets in either Landscape or Horizontal Layout. We select Application Window as a top-level container and add a BoxLayout container in it. Now, in each box of the BoxLayout, we need to add the widgets like ComboBox and Switch for user’s choice and a Label. This can be done by using a horizontal BoxLayout with corresponding widgets. After arranging the UI in above described manner, we have a GUI like below.
If you see the current window in the preview now, you will find that the ComboBox do not have any items. We need to define items in the ComboBox using a GTKListStore. You may refer to this video tutorial to see how this can be done.
Now, when we see the preview, our GUI is fully functional. We have options for Speech Recognition Service, Text to Speech Service in ComboBox. Other simple settings are available as switches.
Now, we need to add functionality to our UI. We want our code to be modular and structured, therefore, we declare a ConfigurationWindow class. Though the ideal way to handle such cases is inheriting from the Gtk.Window class, but reading the documentation of PyGTK+ 3, I could not find a way to do this for windows created through Glade. Thus, we will use composition for storing the window object. We add window and other widgets present in the UI as properties of ConfigurationWindow class like this.
class ConfigurationWindow: def __init__(self) -> None: super().__init__() builder = Gtk.Builder() builder.add_from_file(os.path.join("glade_files/configure.glade")) self.window = builder.get_object("configuration_window") self.stt_combobox = builder.get_object("stt_combobox") self.tts_combobox = builder.get_object("tts_combobox") self.auth_switch = builder.get_object("auth_switch") self.snowboy_switch = builder.get_object("snowboy_switch") self.wake_button_switch = builder.get_object("wake_button_switch")
Now, we need to connect the Signals from our configuration window to the Handler. We declare the Handler as a nested class in the ConfigurationWindow class because its scope of usage is inside the ConfigurationWindow object. Then you may connect signals to an object of the Handler class.
Since we may need to modify the state of the widgets, we hold a reference of the parent ConfigurationWindow object in the Handler and pass the self as a parameter to the Handler. You may read more about using the handlers in my previous blog.
In the Handler, we connect to the config.json file and change the parameters of the the file based on the user inputs on the GUI. We handle it for the Text to Speech selection comboBox in the following manner. We also declare two addition Dialogs for handling the input of credentials by the users for the Watson and Bing services.
def on_stt_combobox_changed(self, combo: Gtk.ComboBox): selection = combo.get_active() if selection == 0: config['default_stt'] = 'google' elif selection == 1: credential_dialog = WatsonCredentialsDialog(self.config_window.window) response = credential_dialog.run() if response == Gtk.ResponseType.OK: username = credential_dialog.username_field.get_text() password = credential_dialog.password_field.get_text() config['default_stt'] = 'watson' config['watson_stt_config']['username'] = username config['watson_stt_config']['password'] = password else: self.config_window.init_stt_combobox() credential_dialog.destroy() elif selection == 2: credential_dialog = BingCredentialDialog(self.config_window.window) response = credential_dialog.run() if response == Gtk.ResponseType.OK: api_key = credential_dialog.api_key_field.get_text() config['default_stt'] = 'bing' config['bing_speech_api_key']['username'] = api_key else: self.config_window.init_stt_combobox() credential_dialog.destroy()
Now, we declare two more methods to show and exit the Window.
def show_window(self): self.window.show_all() Gtk.main() def exit_window(self): self.window.destroy() Gtk.main_quit()
Now, we may use the ConfigurationWindow class object anywhere from our code. This modularized approach is better when you need to manage multiple windows as you can just declare the Window of a particular type and show it whenever need in your code.
- Glade usage Youtube tutorial: https://www.youtube.com/watch?v=vOGK3TveDDk
- Creating GUI using PyGTK for SUSI Linux: https://blog.fossasia.org/making-gui-for-susi-linux-with-pygtk/
- PyGObject Documentation: http://pygobject.readthedocs.io/en/latest/getting_started.html