예제 #1
0
    def __create_hostinfo(self):
        label_top = WINDOW_HEIGHT - MAIN_PADDING - HOSTINFO_LABEL_HEIGHT

        utils_gui.create_separator(container=self.container,
                                   x=0,
                                   y=label_top - MAIN_PADDING,
                                   w=SIDEBAR_WIDTH,
                                   h=1,
                                   orientation=Gtk.Orientation.HORIZONTAL)

        hostname_label = Gtk.Label()
        hostname_label.set_size_request(SIDEBAR_WIDTH, HOSTINFO_LABEL_HEIGHT)
        hostname_label.set_ellipsize(Pango.EllipsizeMode.END)
        hostname_label.set_justify(Gtk.Justification.CENTER)
        hostname_label.set_alignment(0.5, 0.5)
        hostname_label.set_use_markup(True)

        # FIXME: "big" and "small" are not good sizes, we need to be explicit
        hostname_label.set_markup(
            '<big>{0}</big>\n<small><a href="{3}" title="{4}">{1}</a> ({2})</small>'
            .format(
                utils.get_file_contents('/etc/puavo/hostname'),
                utils.get_file_contents('/etc/puavo-image/release'),
                utils.get_file_contents('/etc/puavo/hosttype'), '',
                utils.localize(STRINGS['sb_changelog_title'],
                               self.__settings.language)))

        hostname_label.connect('activate-link', self.__clicked_changelog)
        hostname_label.show()
        self.container.put(hostname_label, 0, label_top)
예제 #2
0
    def __create_buttons(self):
        utils_gui.create_separator(container=self.container,
                                   x=0,
                                   y=MAIN_PADDING + USER_AVATAR_SIZE +
                                   MAIN_PADDING,
                                   w=SIDEBAR_WIDTH,
                                   h=-1,
                                   orientation=Gtk.Orientation.HORIZONTAL)

        y = MAIN_PADDING + USER_AVATAR_SIZE + MAIN_PADDING * 2 + SEPARATOR_SIZE

        # Since Python won't let you modify arguments (no pass-by-reference),
        # each of these returns the next Y position. X coordinates are fixed.

        if not (self.__settings.is_guest or self.__settings.is_webkiosk):
            y = self.__create_button(y, SB_CHANGE_PASSWORD)

        y = self.__create_button(y, SB_SUPPORT)
        y = self.__create_button(y, SB_SYSTEM_SETTINGS)
        y = self.__create_separator(y)

        if not (self.__settings.is_guest or self.__settings.is_webkiosk):
            y = self.__create_button(y, SB_LOCK_SCREEN)

        if not (self.__settings.is_fatclient or self.__settings.is_webkiosk):
            y = self.__create_button(y, SB_SLEEP_MODE)

        y = self.__create_button(y, SB_LOGOUT)
        y = self.__create_separator(y)
        y = self.__create_button(y, SB_RESTART)

        if not self.__settings.is_webkiosk:
            y = self.__create_button(y, SB_SHUTDOWN)

        logging.info('Support page URL: "%s"', SB_SUPPORT['command']['args'])
예제 #3
0
    def __create_separator(self, y):
        padding = 20

        utils_gui.create_separator(container=self.container,
                                   x=padding,
                                   y=y + MAIN_PADDING,
                                   w=SIDEBAR_WIDTH - padding * 2,
                                   h=-1,
                                   orientation=Gtk.Orientation.HORIZONTAL)

        # the next available Y coordinate
        return y + MAIN_PADDING * 2 + SEPARATOR_SIZE
예제 #4
0
파일: main.py 프로젝트: basilstotz/puavo-os
    def __init__(self, settings):
        """This is where the magic happens."""

        start_time = time.clock()

        super().__init__()

        self.__settings = settings

        # Ensure the window is not visible until it's ready
        self.set_visible(False)

        self.set_type_hint(Gtk.WindowType.TOPLEVEL)

        # ----------------------------------------------------------------------
        # Set up a domain socket for show/hide messages from the panel
        # button shell extension. This is done early, because if it
        # fails, we simply exit. We can't do *anything* without it.
        # (The only other choice would be to always display the menu,
        # never allowing the user to hide it because without the socket
        # it cannot be reopened.)

        try:
            # Clean leftovers
            os.unlink(self.__settings.socket)
        except OSError:
            pass

        try:
            self.__socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
            self.__socket.bind(self.__settings.socket)
        except Exception as exception:
            # Oh dear...
            logging.error('Unable to create a domain socket for IPC!')
            logging.error('Reason: %s', str(exception))
            logging.error('Socket name: "%s"', self.__settings.socket)
            logging.error('This is a fatal error, stopping here.')

            syslog.syslog(syslog.LOG_CRIT,
                          'PuavoMenu IPC socket "%s" creation failed: %s',
                          self.__settings.socket, str(exception))

            syslog.syslog(syslog.LOG_CRIT,
                          'PuavoMenu stops here. Contact Opinsys support.')

            exit(1)

        # Start listening the socket. Use glib's watch functions, then
        # we don't need to use threads (which work too, but are harder
        # to clean up and we'll run into problems with multihtreaded
        # xlib programs).

        # https://developer.gnome.org/pygobject/stable/glib-functions.html#
        #   function-glib--io-add-watch
        GLib.io_add_watch(self.__socket, GLib.IO_IN, self.__socket_watcher)

        # ----------------------------------------------------------------------

        # Exit stuff. By default the program cannot be exited normally.
        self.__exit_permitted = False
        self.connect('delete-event', self.__try_exit)

        # Program, menu and category data
        self.menudata = None

        # Current category (index to menudata.category_index)
        self.current_category = -1

        # The current menu, if any (None if on category top-level)
        self.current_menu = None

        # The current menu/program buttons in the current
        # category and menu, if any
        self.__buttons = []

        # Storage for 48x48 -pixel program and menu icons. Maintained
        # separately from the menu data.
        self.__icons = iconcache.IconCache(48, 48 * 20)

        # Background image for top-level menus
        try:
            if self.__settings.dark_theme:
                image_name = 'folder_dark.png'
            else:
                image_name = 'folder.png'

            # WARNING: Hardcoded image size!
            self.__menu_background = \
                utils_gui.load_image_at_size(self.__settings.res_dir + image_name, 150, 110)
        except Exception as exception:
            logging.error("Can't load the menu background image: %s",
                          str(exception))
            self.__menu_background = None

        # ----------------------------------------------------------------------
        # Create the window elements

        # Set window style
        if self.__settings.prod_mode:
            self.set_skip_taskbar_hint(True)
            self.set_skip_pager_hint(True)
            self.set_deletable(False)  # no close button
            self.set_decorated(False)
        else:
            # makes developing slightly easier
            self.set_skip_taskbar_hint(False)
            self.set_skip_pager_hint(False)
            self.set_deletable(True)
            self.set_decorated(True)
            self.__exit_permitted = True

        # Don't mess with the real menu when running in development mode
        if self.__settings.prod_mode:
            self.set_title('PuavoMenuUniqueName')
        else:
            self.set_title('DevModePuavoMenu')

        self.set_resizable(False)
        self.set_size_request(WINDOW_WIDTH, WINDOW_HEIGHT)
        self.set_position(Gtk.WindowPosition.CENTER)

        # Top-level container for all widgets. This is needed, because
        # a window can contain only one child widget and we can have
        # dozens of them. Every widget has a fixed position and size,
        # because the menu isn't user-resizable.
        self.__main_container = Gtk.Fixed()
        self.__main_container.set_size_request(WINDOW_WIDTH, WINDOW_HEIGHT)

        if not self.__settings.prod_mode:
            # Create the devtools popup menu
            self.menu_signal = \
                self.connect('button-press-event', self.__devtools_menu)

        # ----------------------------------------------------------------------
        # Menus/programs list

        # TODO: Gtk.Notebook isn't the best choice for this

        # Category tabs
        self.__category_buttons = Gtk.Notebook()
        self.__category_buttons.set_size_request(CATEGORIES_WIDTH, -1)
        self.__category_buttons.connect('switch-page', self.__clicked_category)
        self.__main_container.put(self.__category_buttons, PROGRAMS_LEFT,
                                  MAIN_PADDING)

        # The back button
        self.__back_button = Gtk.Button()
        self.__back_button.set_label('<<')
        self.__back_button.connect('clicked', self.__clicked_back_button)
        self.__back_button.set_size_request(BACK_BUTTON_WIDTH, -1)
        self.__main_container.put(self.__back_button, BACK_BUTTON_X,
                                  BACK_BUTTON_Y)

        # The search box

        # filter unwanted characters from queries
        self.__translation_table = \
            dict.fromkeys(map(ord, "*^?{[]}/\\_+=\"\'#%&()'`@$<>|~"),
                          None)

        self.__search = Gtk.SearchEntry()
        self.__search.set_size_request(SEARCH_WIDTH, SEARCH_HEIGHT)
        self.__search.set_max_length(10)  # you won't need more than this
        self.__search_changed_signal = \
            self.__search.connect('changed', self.__do_search)
        self.__search_keypress_signal = \
            self.__search.connect('key-press-event', self.__search_keypress)
        self.__search.set_placeholder_text(
            utils.localize(STRINGS['search_placeholder'],
                           self.__settings.language))
        self.__main_container.put(
            self.__search,
            PROGRAMS_LEFT + PROGRAMS_WIDTH - SEARCH_WIDTH - MAIN_PADDING,
            MAIN_PADDING)

        # Menu label and description
        self.__menu_title = Gtk.Label()
        self.__menu_title.set_size_request(PROGRAMS_WIDTH, -1)
        self.__menu_title.set_ellipsize(Pango.EllipsizeMode.END)
        self.__menu_title.set_justify(Gtk.Justification.CENTER)
        self.__menu_title.set_alignment(0.0, 0.0)
        self.__menu_title.set_use_markup(True)
        self.__main_container.put(
            self.__menu_title,
            BACK_BUTTON_X + BACK_BUTTON_WIDTH + MAIN_PADDING,
            BACK_BUTTON_Y + 3)

        # The main programs list
        self.__programs_container = Gtk.ScrolledWindow()
        self.__programs_container.set_size_request(PROGRAMS_WIDTH,
                                                   PROGRAMS_HEIGHT)
        self.__programs_container.set_policy(Gtk.PolicyType.NEVER,
                                             Gtk.PolicyType.AUTOMATIC)
        self.__programs_container.set_shadow_type(Gtk.ShadowType.NONE)
        self.__programs_icons = Gtk.Fixed()
        self.__programs_container.add_with_viewport(self.__programs_icons)
        self.__main_container.put(self.__programs_container, PROGRAMS_LEFT,
                                  PROGRAMS_TOP)

        # Placeholder for empty categories, menus and search results
        self.__empty = Gtk.Label()
        self.__empty.set_size_request(PROGRAMS_WIDTH, PROGRAMS_HEIGHT)
        self.__empty.set_use_markup(True)
        self.__empty.set_justify(Gtk.Justification.CENTER)
        self.__empty.set_alignment(0.5, 0.5)
        self.__main_container.put(self.__empty, PROGRAMS_LEFT, PROGRAMS_TOP)

        # Faves list
        self.__faves_sep = Gtk.Separator(
            orientation=Gtk.Orientation.HORIZONTAL)
        self.__faves_sep.set_size_request(PROGRAMS_WIDTH, 1)
        self.__main_container.put(
            self.__faves_sep, PROGRAMS_LEFT,
            PROGRAMS_TOP + PROGRAMS_HEIGHT + MAIN_PADDING)

        self.__faves = faves.FavesList(self, self.__settings)
        self.__faves.set_size_request(PROGRAMS_WIDTH,
                                      PROGRAM_BUTTON_HEIGHT + 2)
        self.__main_container.put(self.__faves, PROGRAMS_LEFT, FAVES_TOP)

        # ----------------------------------------------------------------------
        # The sidebar: the user avatar, buttons, host infos

        utils_gui.create_separator(container=self.__main_container,
                                   x=SIDEBAR_LEFT - MAIN_PADDING,
                                   y=MAIN_PADDING,
                                   w=1,
                                   h=WINDOW_HEIGHT - (MAIN_PADDING * 2),
                                   orientation=Gtk.Orientation.VERTICAL)

        self.__sidebar = sidebar.Sidebar(self, self.__settings)

        self.__main_container.put(self.__sidebar.container, SIDEBAR_LEFT,
                                  SIDEBAR_TOP)

        # ----------------------------------------------------------------------
        # Setup GTK signal handlers

        # Listen for Esc keypresses for manually hiding the window
        self.__main_keypress_signal = \
            self.connect('key-press-event', self.__check_for_esc)

        self.__focus_signal = None

        if not self.__settings.autohide:
            # Keep the window on top of everything and show it
            self.set_visible(True)
            self.set_keep_above(True)
        else:
            # In auto-hide mode, hide the window when it loses focus
            self.enable_out_of_focus_hide()

        self.__search.connect('focus-out-event', self.__search_out)

        # ----------------------------------------------------------------------
        # UI done

        self.add(self.__main_container)
        self.__main_container.show()

        # DO NOT CALL self.show_all() HERE, the window has hidden elements
        # that are shown/hidden on demand. And we don't even have any
        # menu data yet to show.

        end_time = time.clock()
        utils.log_elapsed_time('Window init time', start_time, end_time)

        # ----------------------------------------------------------------------
        # Load menu data

        # Finally, load the menu data and show the UI
        self.load_menu_data()

        # This is a bad, bad situation that should never happen in production.
        # It will happen one day.
        if self.menudata is None or len(self.menudata.programs) == 0:
            if self.__settings.prod_mode:
                self.__show_empty_message(STRINGS['menu_no_data_at_all_prod'])
            else:
                self.__show_empty_message(STRINGS['menu_no_data_at_all_dev'])