diff --git a/build-aux/meson/post_install.py b/build-aux/meson/post_install.py deleted file mode 100644 index 6291b88..0000000 --- a/build-aux/meson/post_install.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess - -prefix = os.environ.get('MESON_INSTALL_PREFIX', '/usr/local') -datadir = os.path.join(prefix, 'share') -schemadir = os.path.join(os.environ['MESON_INSTALL_PREFIX'], 'share', 'glib-2.0', 'schemas') - -# Packaging tools define DESTDIR and this isn't needed for them -if 'DESTDIR' not in os.environ: - print('Updating icon cache...') - icon_cache_dir = os.path.join(datadir, 'icons', 'hicolor') - if not os.path.exists(icon_cache_dir): - os.makedirs(icon_cache_dir) - subprocess.call(['gtk-update-icon-cache', '-qtf', icon_cache_dir]) - - print('Updating desktop database...') - desktop_database_dir = os.path.join(datadir, 'applications') - if not os.path.exists(desktop_database_dir): - os.makedirs(desktop_database_dir) - subprocess.call(['update-desktop-database', '-q', desktop_database_dir]) - - print('Compiling gsettings schemas...') - subprocess.call(['glib-compile-schemas', schemadir]) diff --git a/data/gresource.xml b/data/gresource.xml index dcb0d81..f54825f 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -1,6 +1,7 @@ - + + metainfo.xml style.css diff --git a/data/metainfo.xml b/data/metainfo.xml index 63b5818..78866da 100644 --- a/data/metainfo.xml +++ b/data/metainfo.xml @@ -5,14 +5,14 @@ CC-BY-SA-4.0 GPL-3.0-or-later - Butler for Home Assistant - Control your smart home + Butler + Access your Home Assistant dashboard Cassidy James Blaede Cassidy James Blaede -

Hybrid native + web app for Home Assistant. Butler wraps your Home Assistant dashboard up in a native UI, integrating better with your OS. Native features include:

+

Hybrid native + web companion app for Home Assistant. Butler wraps your dashboard up in a native UI, integrating better with your OS. Native features include:

  • Icon in your App Grid, Applications Menu, Dash, Dock, etc.
  • Native header bar
  • @@ -20,6 +20,7 @@
  • Two-finger swipe and mouse button support to go back/forward between views
  • Cross-desktop light/dark style support (if supported by your Lovelace theme)
+

Butler is designed to make getting at your Home Assistant dashboard easier for kiosks, your laptop/desktop, or your Linux phone. It does not support companion app features from Android and iOS like location services, notifications, or exposing device sensors.

Other features include:

  • Pinch-to-zoom
  • @@ -55,6 +56,22 @@ + + +

    Small improvements

    +
      +
    • Improved about window
    • +
    • Open links in default browser
    • +
    • Automatically prepend http:// to custom server if omitted
    • +
    • Start preparing for translations
    • +
    +
    + + Better onboarding + Open links in default browser + Automatically prepend http:// to custom server if omitted + +

    Improved accessibility and fullscreen experience

    diff --git a/meson.build b/meson.build index 5c64d71..98163cb 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,8 @@ project( 'com.cassidyjames.butler', 'vala', 'c', - version: '1.0.1' + version: '1.0.2', + meson_version: '>=0.58', ) gnome = import('gnome') @@ -35,7 +36,7 @@ executable( dependencies: [ dependency('glib-2.0'), dependency('gtk4'), - dependency('libadwaita-1'), + dependency('libadwaita-1', version: '>=1.4.2'), dependency('webkitgtk-6.0'), meson.get_compiler('vala').find_library('posix'), ], @@ -43,5 +44,3 @@ executable( ) subdir('data') - -meson.add_install_script('build-aux' / 'meson'/ 'post_install.py') \ No newline at end of file diff --git a/src/App.vala b/src/App.vala index 38050a0..dda9699 100644 --- a/src/App.vala +++ b/src/App.vala @@ -4,17 +4,10 @@ */ public class Butler.App : Adw.Application { - public const string NAME = "Butler"; - public const string DEVELOPER = "Cassidy James Blaede"; - public const string EMAIL = "c@ssidyjam.es"; - public const string URL = "https://cassidyjames.com"; - public static GLib.Settings settings; public App () { - Object ( - application_id: APP_ID - ); + Object ( application_id: APP_ID ); } public static App _instance = null; diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 95f0fc7..3d6f52b 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -4,9 +4,12 @@ */ public class Butler.MainWindow : Adw.ApplicationWindow { + public Adw.AboutWindow about_window; + public Adw.Banner demo_banner; public Adw.Toast fullscreen_toast; public Adw.ToastOverlay toast_overlay; public Gtk.Revealer header_revealer; + public Gtk.Revealer home_revealer; private const GLib.ActionEntry[] ACTION_ENTRIES = { { "toggle_fullscreen", toggle_fullscreen }, @@ -21,9 +24,7 @@ public class Butler.MainWindow : Adw.ApplicationWindow { Object ( application: application, height_request: 180, - icon_name: APP_ID, resizable: true, - title: App.NAME, width_request: 300 ); add_action_entries (ACTION_ENTRIES, this); @@ -33,6 +34,37 @@ public class Butler.MainWindow : Adw.ApplicationWindow { maximized = App.settings.get_boolean ("window-maximized"); fullscreened = App.settings.get_boolean ("window-fullscreened"); + about_window = new Adw.AboutWindow.from_appdata ( + "/com/cassidyjames/butler/metainfo.xml", VERSION + ) { + transient_for = this, + hide_on_close = true, + comments = _("Companion app to access your Home Assistant dashboard"), + + /// The translator credits. Please translate this with your name(s). + translator_credits = _("translator-credits"), + }; + about_window.copyright = "© 2020–%i %s".printf ( + new DateTime.now_local ().get_year (), + about_window.developer_name + ); + about_window.add_link (_("About Home Assistant"), "https://www.home-assistant.io/"); + about_window.add_link (_("Home Assistant Privacy Policy"), "https://www.home-assistant.io/privacy/"); + + // Set MainWindow properties from the AppData already fetched and parsed + // by the AboutWindow construction + icon_name = about_window.application_icon; + title = about_window.application_name; + + var home_button = new Gtk.Button.from_icon_name ("go-home-symbolic") { + tooltip_text = _("Go Home") + }; + + home_revealer = new Gtk.Revealer () { + child = home_button, + transition_type = Gtk.RevealerTransitionType.SLIDE_RIGHT + }; + var site_menu = new Menu (); site_menu.append (_("_Log Out…"), "win.log_out"); @@ -40,7 +72,7 @@ public class Butler.MainWindow : Adw.ApplicationWindow { // TODO: How do I add shortcuts to the menu? app_menu.append (_("_Fullscreen"), "win.toggle_fullscreen"); app_menu.append (_("Change _Server…"), "win.set_server"); - app_menu.append (_("_About %s").printf (App.NAME), "win.about"); + app_menu.append (_("_About %s").printf (title), "win.about"); var menu = new Menu (); menu.append_section (null, site_menu); @@ -53,6 +85,7 @@ public class Butler.MainWindow : Adw.ApplicationWindow { }; var header = new Adw.HeaderBar (); + header.pack_start (home_revealer); header.pack_end (menu_button); header_revealer = new Gtk.Revealer () { @@ -60,7 +93,12 @@ public class Butler.MainWindow : Adw.ApplicationWindow { reveal_child = !fullscreened }; - fullscreen_toast = new Adw.Toast ("Press Ctrl F or F11 to toggle fullscreen") { + demo_banner = new Adw.Banner (_("Browsing Home Assistant Demo")) { + action_name = "win.set_server", + button_label = _("Set _Server…") + }; + + fullscreen_toast = new Adw.Toast (_("Press Ctrl F or F11 to toggle fullscreen")) { action_name = "win.toggle_fullscreen", button_label = _("Exit _Fullscreen") }; @@ -76,7 +114,7 @@ public class Butler.MainWindow : Adw.ApplicationWindow { } var status_page = new Adw.StatusPage () { - title = _("%s for Home Assistant").printf (App.NAME), + title = title, description = _("Loading the dashboard…"), icon_name = APP_ID }; @@ -99,6 +137,7 @@ public class Butler.MainWindow : Adw.ApplicationWindow { }; grid.attach (header_revealer, 0, 0); grid.attach (toast_overlay, 0, 1); + grid.attach (demo_banner, 0, 2); set_content (grid); @@ -107,6 +146,10 @@ public class Butler.MainWindow : Adw.ApplicationWindow { set_default_size (window_width, window_height); + home_button.clicked.connect (() => { + web_view.load_uri (server); + }); + close_request.connect (() => { save_window_state (); return Gdk.EVENT_PROPAGATE; @@ -148,7 +191,20 @@ public class Butler.MainWindow : Adw.ApplicationWindow { if (web_view.is_loading) { // TODO: Add a loading progress bar or spinner somewhere? } else { - App.settings.set_string ("current-url", web_view.uri); + string default_server = App.settings.get_default_value ("server").get_string (); + string server = App.settings.get_string ("server"); + string current_url = web_view.uri; + + App.settings.set_string ("current-url", current_url); + + if (current_url.has_prefix (default_server)) { + demo_banner.revealed = true; + } else if (current_url.has_prefix (server)) { + demo_banner.revealed = false; + } else { + demo_banner.revealed = false; + home_revealer.set_reveal_child (true); + } } } @@ -220,14 +276,14 @@ public class Butler.MainWindow : Adw.ApplicationWindow { var server_dialog = new Adw.MessageDialog ( this, - "Set Server URL", - "Enter the full URL including protocol (e.g. http://) and any custom port (e.g. :8123)" + _("Set Server URL"), + _("Enter the full URL including any custom port") ) { body_use_markup = true, default_response = "save", extra_child = server_entry, }; - server_dialog.add_response ("close", "_Cancel"); + server_dialog.add_response ("close", _("_Cancel")); server_dialog.add_response ("demo", _("_Reset to Demo")); server_dialog.set_response_appearance ("demo", Adw.ResponseAppearance.DESTRUCTIVE); @@ -245,6 +301,10 @@ public class Butler.MainWindow : Adw.ApplicationWindow { new_server = default_server; } + if (!new_server.contains ("://")) { + new_server = "http://" + new_server; + } + if (new_server != current_server) { // FIXME: There's currently no validation of this App.settings.set_string ("server", new_server); @@ -262,13 +322,13 @@ public class Butler.MainWindow : Adw.ApplicationWindow { var log_out_dialog = new Adw.MessageDialog ( this, - "Log out of Home Assistant?", - "You will need to re-enter your username and password for %s to log back in.".printf (server) + _("Log out of Home Assistant?"), + _("You will need to re-enter your username and password for %s to log back in.").printf (server) ) { body_use_markup = true, default_response = "log_out" }; - log_out_dialog.add_response ("close", "_Stay Logged In"); + log_out_dialog.add_response ("close", _("_Stay Logged In")); log_out_dialog.add_response ("log_out", _("_Log Out")); log_out_dialog.set_response_appearance ("log_out", Adw.ResponseAppearance.DESTRUCTIVE); @@ -282,33 +342,6 @@ public class Butler.MainWindow : Adw.ApplicationWindow { } private void on_about_activate () { - var about_window = new Adw.AboutWindow () { - transient_for = this, - - application_icon = APP_ID, - application_name = _("%s for Home Assistant").printf (App.NAME), - developer_name = App.DEVELOPER, - version = VERSION, - - comments = _("Butler is a hybrid native + web app for your Home Assistant dashboard"), - - website = App.URL, - issue_url = "https://github.com/cassidyjames/butler/issues", - - // Credits - developers = { "%s <%s>".printf (App.DEVELOPER, App.EMAIL) }, - designers = { "%s %s".printf (App.DEVELOPER, App.URL) }, - - /// The translator credits. Please translate this with your name(s). - translator_credits = _("translator-credits"), - - // Legal - copyright = "Copyright © 2020–2024 %s".printf (App.DEVELOPER), - license_type = Gtk.License.GPL_3_0, - }; - about_window.add_link (_("About Home Assistant"), "https://www.home-assistant.io/"); - about_window.add_link (_("Home Assistant Privacy Policy"), "https://www.home-assistant.io/privacy/"); - about_window.present (); } } diff --git a/src/Widgets/WebView.vala b/src/Widgets/WebView.vala index 0a6c83d..7c5fe90 100644 --- a/src/Widgets/WebView.vala +++ b/src/Widgets/WebView.vala @@ -9,8 +9,7 @@ public class Butler.WebView : WebKit.WebView { public WebView () { Object ( hexpand: true, - vexpand: true, - user_content_manager: new WebKit.UserContentManager () + vexpand: true ); } @@ -30,28 +29,25 @@ public class Butler.WebView : WebKit.WebView { hardware_acceleration_policy = WebKit.HardwareAccelerationPolicy.ALWAYS }; - var custom_css = new WebKit.UserStyleSheet ( - """ - header, - .header { - app-region: drag; - -webkit-app-region: drag; - } - """, - WebKit.UserContentInjectedFrames.TOP_FRAME, - WebKit.UserStyleLevel.AUTHOR, - null, - null - ); - user_content_manager.add_style_sheet (custom_css); - settings = webkit_settings; - web_context = new WebKit.WebContext (); context_menu.connect (() => { return !is_terminal; }); + decide_policy.connect ((decision, type) => { + if (type == WebKit.PolicyDecisionType.NEW_WINDOW_ACTION) { + try { + new Gtk.UriLauncher ( + ((WebKit.NavigationPolicyDecision)decision). + navigation_action.get_request ().get_uri () + ).launch.begin (null, null); + } catch (Error e) { + critical ("Unable to open externally"); + } + } + }); + var back_click_gesture = new Gtk.GestureClick () { button = 8 };