/* * greeter.c * * Copyright © 2014-2016 Antergos Developers * * Includes Code Contributed By: * Copyright © 2016 Scott Balneaves * * Based on code from lightdm-webkit-greeter: * Copyright © 2010-2015 Robert Ancell * * This file is part of lightdm-webkit2-greeter. * * lightdm-webkit2-greeter is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * lightdm-webkit2-greeter is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * The following additional terms are in effect as per Section 7 of the license: * * The preservation of all legal notices and author attributions in * the material or in the Appropriate Legal Notices displayed * by works containing it is required. * * You should have received a copy of the GNU General Public License * along with lightdm-webkit2-greeter; If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "greeter-resources.h" /* Work-around CLion bug */ #ifndef CONFIG_DIR #include "../build/src/config.h" #include "../build/src/greeter-resources.h" #endif static GtkWidget *web_view; static GtkWidget *window; static WebKitSettings *webkit_settings; static GdkDisplay *default_display; static GResource *greeter_resources; /* Screensaver values */ static int timeout, interval, prefer_blanking, allow_exposures; static gint config_timeout; static gboolean debug_mode, heartbeat, heartbeat_exit; static void initialize_web_extensions_cb(WebKitWebContext *context, gpointer user_data) { webkit_web_context_set_web_extensions_directory(context, WEBEXT_DIR); } static void create_new_webkit_settings_object(void) { webkit_settings = webkit_settings_new_with_settings( "enable-developer-extras", TRUE, "javascript-can-open-windows-automatically", TRUE, "allow-file-access-from-file-urls", TRUE, "enable-write-console-messages-to-stdout", TRUE, #ifdef HAS_WEBKITGTK_2_14 "allow-universal-access-from-file-urls", TRUE, #endif NULL ); } static gboolean context_menu_cb(WebKitWebView *view, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer user_data) { /* Returning true without creating a custom context menu results in no context * menu being shown. Thus, we are returning the opposite of debug_mode to get * desired result (which is only show menu when debug_mode is enabled. */ return (! debug_mode); } static gboolean check_theme_heartbeat(void) { if (! heartbeat && ! heartbeat_exit) { /* Theme heartbeat not received. We assume that an error has occurred * which broke script execution. We will fallback to the simple theme * so the user won't be stuck with a broken login screen. */ g_warning("[ERROR] :: A problem was detected with the current theme. Falling back to simple theme..."); webkit_web_view_load_uri( WEBKIT_WEB_VIEW(web_view), g_strdup_printf("file://%ssimple/index.html", THEME_DIR) ); } heartbeat = FALSE; return heartbeat; } /** * Callback for Theme Heartbeat. */ static void theme_heartbeat_handler(void) { if (! heartbeat) { /* Setup g_timeout callback for theme heartbeat check */ g_timeout_add_seconds(8, (GSourceFunc) check_theme_heartbeat, NULL); heartbeat = TRUE; heartbeat_exit = FALSE; } } /** * Before starting the user's session, the web process will send a signal to exit the hearbeat * in order to prevent a race condition while the greeter is shutting down. */ static void theme_heartbeat_exit_handler(void) { heartbeat_exit = TRUE; } static void script_loaded_cb(GObject *object, GAsyncResult *result, gpointer user_data) { WebKitJavascriptResult *js_result; GError *error = NULL; js_result = webkit_web_view_run_javascript_from_gresource_finish( WEBKIT_WEB_VIEW(object), result, &error ); if (!js_result) { g_error_free(error); } else { webkit_javascript_result_unref(js_result); } } /** * Makes the greeter behave a bit more like a screensaver if it was launched as * a lock-screen by blanking the screen. */ static void lock_hint_enabled_handler(void) { Display *display = gdk_x11_display_get_xdisplay(default_display); config_timeout = (0 != config_timeout) ? config_timeout : 300; XGetScreenSaver(display, &timeout, &interval, &prefer_blanking, &allow_exposures); XForceScreenSaver(display, ScreenSaverActive); XSetScreenSaver(display, config_timeout, 0, PreferBlanking, DefaultExposures); } static void load_script(char *script) { webkit_web_view_run_javascript_from_gresource( WEBKIT_WEB_VIEW(web_view), script, NULL, (GAsyncReadyCallback) script_loaded_cb, NULL ); } static void greeter_loaded_handler(void) { load_script(GRESOURCE_PATH "/js/_vendor/moment-with-locales.min.js"); load_script(GRESOURCE_PATH "/js/Greeter.js"); load_script(GRESOURCE_PATH "/js/GreeterConfig.js"); load_script(GRESOURCE_PATH "/js/ThemeUtils.js"); load_script(GRESOURCE_PATH "/js/ThemeHeartbeat.js"); } /** * Message received callback. * * Receives messages from our web extension process and calls appropriate handlers. * * @param manager The user content manager instance that was created in #main. * @param message The message sent from web extension process. * @param user_data Data that is private to the current user. */ static void message_received_cb(WebKitUserContentManager *manager, WebKitJavascriptResult *message, gpointer user_data) { gchar *message_str; JSGlobalContextRef context; JSValueRef message_val; JSStringRef js_str_val; gsize message_str_length; context = webkit_javascript_result_get_global_context(message); message_val = webkit_javascript_result_get_value(message); if (JSValueIsString(context, message_val)) { js_str_val = JSValueToStringCopy(context, message_val, NULL); message_str_length = JSStringGetMaximumUTF8CStringSize(js_str_val); message_str = (gchar *)g_malloc (message_str_length); JSStringGetUTF8CString(js_str_val, message_str, message_str_length); JSStringRelease(js_str_val); } else { message_str = ""; printf("Error running javascript: unexpected return value"); } if (strcmp(message_str, "GreeterLoaded") == 0) { greeter_loaded_handler(); } else if (strcmp(message_str, "LockHint") == 0) { lock_hint_enabled_handler(); } else if (strcmp(message_str, "Heartbeat") == 0) { theme_heartbeat_handler(); } else if (strcmp(message_str, "Heartbeat::Exit") == 0) { theme_heartbeat_exit_handler(); } else { printf("UI PROCESS - message_received_cb(): no match!"); } g_free(message_str); } static void quit_cb(void) { gtk_widget_destroy(window); gtk_main_quit(); } static gchar * rtrim_comments(gchar *str) { gchar *ptr = NULL; ptr = strchr(str, '#'); if (NULL != ptr) { *ptr = '\0'; } return g_strstrip(str); } int main(int argc, char **argv) { GdkScreen *screen; GdkWindow *root_window; GdkRectangle geometry; GKeyFile *keyfile; gchar *theme; GError *err = NULL; GdkRGBA bg_color; WebKitUserContentManager *manager; WebKitWebContext *context; GtkCssProvider *css_provider; WebKitCookieManager *cookie_manager; /* Prevent memory from being swapped out, since we see unencrypted passwords. */ mlockall (MCL_CURRENT | MCL_FUTURE); /* https://goo.gl/vDFwFe */ g_setenv ("GDK_CORE_DEVICE_EVENTS", "1", TRUE); /* Initialize i18n */ bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); gtk_init(&argc, &argv); g_unix_signal_add(SIGTERM, (GSourceFunc) quit_cb, NULL); g_unix_signal_add(SIGINT, (GSourceFunc) quit_cb, NULL); g_unix_signal_add(SIGHUP, (GSourceFunc) quit_cb, NULL); /* BEGIN Greeter Config File */ keyfile = g_key_file_new(); g_key_file_load_from_file( keyfile, CONFIG_DIR "/lightdm-webkit2-greeter.conf", G_KEY_FILE_NONE, NULL ); /* TODO: Handle config values and fallbacks some other way, this is garbage! */ theme = g_key_file_get_string(keyfile, "greeter", "webkit_theme", &err); if ( NULL != err) { g_clear_error(&err); theme = g_key_file_get_string(keyfile, "greeter", "webkit-theme", &err); if ( NULL != err) { g_clear_error(&err); theme = "antergos"; } } theme = rtrim_comments(theme); config_timeout = g_key_file_get_integer(keyfile, "greeter", "screensaver_timeout", &err); if ( NULL != err) { g_clear_error(&err); config_timeout = g_key_file_get_integer(keyfile, "greeter", "screensaver-timeout", &err); if ( NULL != err) { g_error_free(err); config_timeout = 300; } } debug_mode = g_key_file_get_boolean(keyfile, "greeter", "debug_mode", NULL); if ( NULL != err) { g_clear_error(&err); debug_mode = FALSE; } /* END Greeter Config File */ /* Set default cursor */ root_window = gdk_get_default_root_window(); default_display = gdk_display_get_default(); gdk_window_set_cursor(root_window, gdk_cursor_new_for_display(default_display, GDK_LEFT_PTR)); /* Set the GTK theme to Adwaita */ g_object_set(gtk_settings_get_default(), "gtk-theme-name", "Adwaita", NULL); /* Setup the main window */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); screen = gtk_window_get_screen(GTK_WINDOW(window)); gtk_window_set_decorated(GTK_WINDOW(window), FALSE); #ifdef HAS_GTK_3_22 GdkMonitor *monitor = gdk_display_get_primary_monitor(default_display); gdk_monitor_get_geometry(monitor, &geometry); #else gdk_screen_get_monitor_geometry(screen, gdk_screen_get_primary_monitor(screen), &geometry); #endif gtk_window_set_default_size(GTK_WINDOW(window), geometry.width, geometry.height); gtk_window_move(GTK_WINDOW(window), geometry.x, geometry.y); /* Setup CSS provider. We use CSS to set the window background to black instead * of default white so the screen doesnt flash during startup. */ greeter_resources = greeter_resources_get_resource(); css_provider = gtk_css_provider_new(); g_resources_register(greeter_resources); gtk_css_provider_load_from_resource( GTK_CSS_PROVIDER(css_provider), "/com/antergos/lightdm-webkit2-greeter/css/style.css" ); gtk_style_context_add_provider_for_screen( screen, GTK_STYLE_PROVIDER(css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ); /* Register and connect handler that will set the web extensions directory * so webkit can find our extension. */ context = webkit_web_context_get_default(); g_signal_connect(WEBKIT_WEB_CONTEXT(context), "initialize-web-extensions", G_CALLBACK(initialize_web_extensions_cb), NULL); /* Set cookie policy */ cookie_manager = webkit_web_context_get_cookie_manager(context); webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS); /* Register and connect handler of any messages we send from our web extension process. */ manager = webkit_user_content_manager_new(); g_signal_connect(manager, "script-message-received::GreeterBridge", G_CALLBACK(message_received_cb), NULL); webkit_user_content_manager_register_script_message_handler(manager, "GreeterBridge"); /* Create the web_view */ web_view = webkit_web_view_new_with_user_content_manager(manager); /* Set the web_view's settings. */ create_new_webkit_settings_object(); webkit_web_view_set_settings(WEBKIT_WEB_VIEW(web_view), WEBKIT_SETTINGS(webkit_settings)); /* The default background color of the web_view is white which causes a flash effect when the greeter starts. * We make it black instead. This only applies when the theme hasn't set the body background via CSS. */ gdk_rgba_parse(&bg_color, "#000000"); webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(web_view), gdk_rgba_copy(&bg_color)); /* Maybe disable the context (right-click) menu. */ g_signal_connect(WEBKIT_WEB_VIEW(web_view), "context-menu", G_CALLBACK(context_menu_cb), NULL); /* There's no turning back now, let's go! */ gtk_container_add(GTK_CONTAINER(window), web_view); webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view), g_strdup_printf("file://%s/%s/index.html", THEME_DIR, theme)); gtk_widget_show_all(window); gtk_widget_set_can_focus(GTK_WIDGET(web_view), TRUE); gtk_widget_grab_focus(GTK_WIDGET(web_view)); g_debug("Entering Gtk loop..."); gtk_main(); g_debug("Exited Gtk loop."); return 0; }