Esempio n. 1
0
    def on_save_file(self, default_filename):
        """ Opens a native file dialog and returns the chosen path of the file to be saved
            The dialog does not allow overwriting existing files - to get this behaviour
            while using native dialogs the QFileDialog has to be reopened on overwrite.
            A bit weird but enables visual consistency with the other file choose dialogs
            :param default_filename: The default filename to be used in the Qt file dialog
            :type default_filename: str/unicode
            :returns: The designated key file path
            :rtype: str/unicode
        """
        def_path = os.path.join(os.getenv("HOME"), default_filename)

        while True:
            save_path = QFileDialog.getSaveFileName(
                self,
                _('Please create a new file'),
                def_path,
                options=QFileDialog.DontConfirmOverwrite)

            save_path = self.encode_qt_output(save_path[0]) if isinstance(
                save_path, tuple) else self.encode_qt_output(save_path)
            self.buttons.button(QDialogButtonBox.Ok).setText(
                _('Create'))  # qt keeps changing this..

            if os.path.exists(save_path):
                show_alert(
                    self,
                    _('File already exists:\n{filename}\n\n'
                      '<b>Please create a new file!</b>').format(
                          filename=save_path))
                def_path = os.path.join(os.path.basename(save_path),
                                        default_filename)
            else:
                return save_path
Esempio n. 2
0
    def run(self):
        """ Listens on workers stdout and executes callbacks when answers arrive """
        while True:
            try:
                buf = self.worker.stdout.readline()  # blocks
                if buf:  # check if worker output pipe closed
                    response = json.loads(buf.strip(), encoding='utf-8')
                else:
                    return
                assert('type' in response and 'msg' in response)
                assert(self.success_callback is not None and self.error_callback is not None)  # there should be somebody waiting for an answer!
                # valid response received
                if response['type'] == 'error':
                    QApplication.postEvent(self.parent, WorkerEvent(self.error_callback, response['msg']))
                else:
                    QApplication.postEvent(self.parent, WorkerEvent(self.success_callback, response['msg']))
                # reset callbacks
                self.success_callback, self.error_callback = None, None

            except ValueError:
                # worker didn't return json -> probably crashed, show everything printed to stdout
                buf += self.worker.stdout.read()
                QApplication.postEvent(self.parent, WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True),
                                                                response=_('Error in communication:\n{error}').format(error=_(buf))))
                return

            except (IOError, AssertionError) as communication_error:
                QApplication.postEvent(self.parent, WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True),
                                                                response=_('Error in communication:\n{error}').format(error=format_exception(communication_error))))
                return
Esempio n. 3
0
    def __init__(self, parent):
        """ :param parent: The parent widget to be passed to modal dialogs
            :type parent: :class:`PyQt4.QtGui.QWidget`
            :raises: SudoException
        """
        super(WorkerMonitor, self).__init__()
        self.parent = parent
        self.success_callback, self.error_callback = None, None
        self.modify_sudoers = False
        self.worker = None
        self._spawn_worker()

        if self.modify_sudoers:  # adding user/program to /etc/sudoers.d/ requested
            self.execute({'type': 'request', 'msg': 'authorize'}, None, None)
            response = json.loads(self.worker.stdout.readline().strip(),
                                  encoding='utf-8')  # blocks
            if response['type'] == 'error':
                show_alert(self.parent, response['msg'])
            else:
                message = _(
                    'Permanent `sudo` authorization for\n'
                    '{program}\n'
                    'has been successfully added for user `{username}` to \n'
                    '/etc/sudoers.d/lucky-luks\n').format(
                        program=os.path.abspath(sys.argv[0]),
                        username=os.getenv("USER"))
                show_info(self.parent, message, _('Success'))
Esempio n. 4
0
    def __init__(self, parent):
        """ :param parent: The parent widget to be passed to modal dialogs
            :type parent: :class:`PyQt4.QtGui.QWidget`
            :raises: SudoException
        """
        super(WorkerMonitor, self).__init__()
        self.parent = parent
        self.success_callback, self.error_callback = None, None
        self.modify_sudoers = False
        self.worker = None
        self._spawn_worker()

        if self.modify_sudoers:  # adding user/program to /etc/sudoers.d/ requested
            self.execute({'type': 'request', 'msg': 'authorize'}, None, None)
            response = json.loads(self.worker.stdout.readline().strip(), encoding='utf-8')  # blocks
            if response['type'] == 'error':
                show_alert(self.parent, response['msg'])
            else:
                message = _('Permanent `sudo` authorization for\n'
                            '{program}\n'
                            'has been successfully added for user `{username}` to \n'
                            '/etc/sudoers.d/lucky-luks\n').format(
                    program=os.path.abspath(sys.argv[0]),
                    username=os.getenv("USER"))
                show_info(self.parent, message, _('Success'))
Esempio n. 5
0
 def display_create_failed(self, errormessage, stop_timer=False):
     """ Triggered when an error happend during the create process
         :param errormessage: errormessage to be shown
         :type errormessage: str
         :param stop_timer: stop a progress indicator?
         :type stop_timer: bool
     """
     if stop_timer:
         self.set_progress_done(self.create_timer)
     show_alert(self, errormessage)
     self.display_create_done()
Esempio n. 6
0
 def on_initialized(self, message, error=False):
     """ Callback after worker send current state of container
         :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed)
         :type message: str
         :param critical: Error during initialization (default=False)
         :type critical: bool
     """
     if error:
         show_alert(self, message, critical=True)
     else:
         self.is_unlocked = (True if message == 'unlocked' else False)
         self.enable_ui()
Esempio n. 7
0
 def toggle_container_status(self):
     """ Unlock or close container """
     if self.is_unlocked:
         self.do_close_container()
     else:
         try:
             UnlockContainerDialog(self, self.worker, self.luks_device_name, self.encrypted_container, self.key_file, self.mount_point).communicate()
             self.is_unlocked = True
         except UserInputError as uie:
             show_alert(self, format_exception(uie))
             self.is_unlocked = False
         self.refresh()
Esempio n. 8
0
 def on_initialized(self, message, error=False):
     """ Callback after worker send current state of container
         :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed)
         :type message: str
         :param critical: Error during initialization (default=False)
         :type critical: bool
     """
     if error:
         show_alert(self, message, critical=True)
     else:
         self.is_unlocked = (True if message == 'unlocked' else False)
         self.enable_ui()
Esempio n. 9
0
 def on_switchpage_event(self, index):
     """ Event handler for tab switch: change text on OK button (Unlock/Create) """
     new_ok_label = _('Unlock')
     if index == 1:  # create
         if self.create_filesystem_type.currentText() == '':
             show_alert(
                 self,
                 _('No tools to format the filesystem found\n'
                   'Please install, eg for Debian/Ubuntu\n'
                   '`apt-get install e2fslibs ntfs-3g`'))
         new_ok_label = _('Create')
     self.buttons.button(QDialogButtonBox.Ok).setText(new_ok_label)
Esempio n. 10
0
 def toggle_container_status(self):
     """ Unlock or close container """
     if self.is_unlocked:
         self.do_close_container()
     else:
         try:
             UnlockContainerDialog(self, self.worker, self.luks_device_name,
                                   self.encrypted_container, self.key_file,
                                   self.mount_point).communicate()
             self.is_unlocked = True
         except UserInputError as uie:
             show_alert(self, format_exception(uie))
             self.is_unlocked = False
         self.refresh()
Esempio n. 11
0
 def execute(self, command, success_callback, error_callback):
     """ Writes command to workers stdin and sets callbacks for listener thread
         :param command: The function to be done by the worker is in command[`msg`] the arguments are passed as named properties command[`device_name`] etc.
         :type command: dict
         :param success_callback: The function to be called if the worker finished successfully
         :type success_callback: function
         :param error_callback: The function to be called if the worker returns an error
         :type error_callback: function
     """
     try:
         assert ('type' in command
                 and 'msg' in command)  # valid command obj?
         assert (
             self.success_callback is None and self.error_callback is None
         )  # channel clear? (no qeue neccessary for the backend process)
         self.success_callback = success_callback
         self.error_callback = error_callback
         self.worker.stdin.write(json.dumps(command) + '\n')
         self.worker.stdin.flush()
     except (IOError, AssertionError) as communication_error:
         QApplication.postEvent(
             self.parent,
             WorkerEvent(
                 callback=lambda msg: show_alert(
                     self.parent, msg, critical=True),
                 response=_('Error in communication:\n{error}').format(
                     error=format_exception(communication_error))))
Esempio n. 12
0
 def on_container_closed(self, message, error, shutdown):
     """ Callback after worker closed container
         :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed)
         :type message: str
         :param error: Error during closing of container
         :type error: bool
         :param shutdown: Quit application after container successfully closed?
         :type shutdown: bool
     """
     if error:
         show_alert(self, message)
     else:
         self.is_unlocked = False
     if not error and shutdown:  # automatic shutdown only if container successfully closed
         QApplication.instance().quit()
     else:
         self.enable_ui()
Esempio n. 13
0
    def run(self):
        """ Listens on workers stdout and executes callbacks when answers arrive """
        while True:
            try:
                buf = self.worker.stdout.readline()  # blocks
                if buf:  # check if worker output pipe closed
                    response = json.loads(buf.strip(), encoding='utf-8')
                else:
                    return
                assert ('type' in response and 'msg' in response)
                assert (self.success_callback is not None
                        and self.error_callback is not None
                        )  # there should be somebody waiting for an answer!
                # valid response received
                if response['type'] == 'error':
                    QApplication.postEvent(
                        self.parent,
                        WorkerEvent(self.error_callback, response['msg']))
                else:
                    QApplication.postEvent(
                        self.parent,
                        WorkerEvent(self.success_callback, response['msg']))
                # reset callbacks
                self.success_callback, self.error_callback = None, None

            except ValueError:
                # worker didn't return json -> probably crashed, show everything printed to stdout
                buf += self.worker.stdout.read()
                QApplication.postEvent(
                    self.parent,
                    WorkerEvent(
                        callback=lambda msg: show_alert(
                            self.parent, msg, critical=True),
                        response=_('Error in communication:\n{error}').format(
                            error=_(buf))))
                return

            except (IOError, AssertionError) as communication_error:
                QApplication.postEvent(
                    self.parent,
                    WorkerEvent(
                        callback=lambda msg: show_alert(
                            self.parent, msg, critical=True),
                        response=_('Error in communication:\n{error}').format(
                            error=format_exception(communication_error))))
                return
Esempio n. 14
0
 def on_container_closed(self, message, error, shutdown):
     """ Callback after worker closed container
         :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed)
         :type message: str
         :param error: Error during closing of container
         :type error: bool
         :param shutdown: Quit application after container successfully closed?
         :type shutdown: bool
     """
     if error:
         show_alert(self, message)
     else:
         self.is_unlocked = False
     if not error and shutdown:  # automatic shutdown only if container successfully closed
         QApplication.instance().quit()
     else:
         self.enable_ui()
Esempio n. 15
0
    def on_accepted(self):
        """ Event handler for response:
            Start unlock or create action
        """
        try:
            if self.tab_pane.currentIndex() == 1:

                self.on_create_container()

            else:
                UnlockContainerDialog(
                    self, self.worker, self.get_luks_device_name(),
                    self.get_encrypted_container(), self.get_keyfile(),
                    self.get_mount_point()).communicate()  # blocks

                # optionally create startmenu entry
                self.show_create_startmenu_entry()
                # all good, now switch to main window
                self.accept()

        except UserInputError as error:
            show_alert(self, format_exception(error))
Esempio n. 16
0
 def execute(self, command, success_callback, error_callback):
     """ Writes command to workers stdin and sets callbacks for listener thread
         :param command: The function to be done by the worker is in command[`msg`] the arguments are passed as named properties command[`device_name`] etc.
         :type command: dict
         :param success_callback: The function to be called if the worker finished successfully
         :type success_callback: function
         :param error_callback: The function to be called if the worker returns an error
         :type error_callback: function
     """
     try:
         assert('type' in command and 'msg' in command)  # valid command obj?
         assert(self.success_callback is None and self.error_callback is None)  # channel clear? (no qeue neccessary for the backend process)
         self.success_callback = success_callback
         self.error_callback = error_callback
         self.worker.stdin.write(json.dumps(command) + '\n')
         self.worker.stdin.flush()
     except (IOError, AssertionError) as communication_error:
         QApplication.postEvent(self.parent, WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True),
                                                         response=_('Error in communication:\n{error}').format(error=format_exception(communication_error))))
Esempio n. 17
0
    def create_startmenu_entry(self):
        """ Creates a startmenu entry that lets the user skip the setup dialog and go directly to the main UI
            Includes a workaround for the safety net some desktop environments create around the startupmenu
        """
        import random
        import string
        # command to be saved in shortcut: calling the script with the arguments entered in the dialog
        # put all arguments in single quotes and escape those in the strings (shell escape ' -> '\'')
        cmd = os.path.abspath(sys.argv[0])
        cmd += " -c '" + self.get_encrypted_container().replace("'",
                                                                "'\\'''") + "'"
        cmd += " -n '" + self.get_luks_device_name().replace("'",
                                                             "'\\'''") + "'"
        if self.get_mount_point() is not None:
            cmd += " -m '" + self.get_mount_point().replace("'",
                                                            "'\\'''") + "'"
        if self.get_keyfile() is not None:
            cmd += " -k '" + self.get_keyfile().replace("'", "'\\'''") + "'"

        # create .desktop-file
        filename = _('luckyLUKS') + '-' + ''.join(
            i for i in self.get_luks_device_name() if i not in ' \/:*?<>|'
        )  # xdg-desktop-menu has problems with some special chars
        if is_installed('xdg-desktop-menu'
                        ):  # create in tmp and add freedesktop menu entry
            # some desktop menus dont delete the .desktop files if a user removes items from the menu but keep track of those files instead
            # those items wont be readded later, the random part of the filename works around this behaviour
            desktop_file_path = os.path.join(
                '/tmp', filename + '-' +
                ''.join(random.choice(string.ascii_letters)
                        for i in range(4)) + '.desktop')
        else:  # or create in users home dir
            desktop_file_path = os.path.join(os.path.expanduser('~'),
                                             filename + '.desktop')

        desktop_file = codecs.open(desktop_file_path, 'w', 'utf-8')

        entry_name = _('Unlock {device_name}').format(
            device_name=self.get_luks_device_name())
        desktop_file.write("[Desktop Entry]\n")
        desktop_file.write("Name=" + entry_name + "\n")
        desktop_file.write("Comment=" + self.get_luks_device_name() + " " +
                           _('Encrypted Container Tool') + "\n")
        desktop_file.write("GenericName=" + _('Encrypted Container') + "\n")
        desktop_file.write("Categories=Utility;\n")
        desktop_file.write("Exec=" + cmd + "\n")
        desktop_file.write("Icon=dialog-password\n")
        desktop_file.write("NoDisplay=false\n")
        desktop_file.write("StartupNotify=false\n")
        desktop_file.write("Terminal=0\n")
        desktop_file.write("TerminalOptions=\n")
        desktop_file.write("Type=Application\n\n")
        desktop_file.close()

        os.chmod(desktop_file_path,
                 0o700)  # some distros need the xbit to trust the desktop file

        if is_installed('xdg-desktop-menu'):
            # safest way to ensure updates: explicit uninstall followed by installing a new desktop file with different random part
            import glob
            for desktopfile in glob.glob(
                    os.path.expanduser('~') + '/.local/share/applications/' +
                    filename + '-*.desktop'):
                with open(os.devnull) as DEVNULL:
                    subprocess.call(
                        ['xdg-desktop-menu', 'uninstall', desktopfile],
                        stdout=DEVNULL,
                        stderr=subprocess.STDOUT)
            try:
                subprocess.check_output([
                    'xdg-desktop-menu', 'install', '--novendor',
                    desktop_file_path
                ],
                                        stderr=subprocess.STDOUT,
                                        universal_newlines=True)
                os.remove(desktop_file_path)  # remove from tmp
                show_info(
                    self,
                    _('<b>` {name} `</b>\nadded to start menu').format(
                        name=entry_name), _('Success'))
            except subprocess.CalledProcessError as cpe:
                home_dir_path = os.path.join(
                    os.path.expanduser('~'),
                    os.path.basename(desktop_file_path))
                # move to homedir instead
                from shutil import move
                move(desktop_file_path, home_dir_path)
                show_alert(self, cpe.output)
                show_info(
                    self,
                    _('Adding to start menu not possible,\nplease place your shortcut manually.\n\nDesktop file saved to\n{location}'
                      ).format(location=home_dir_path))
        else:
            show_info(
                self,
                _('Adding to start menu not possible,\nplease place your shortcut manually.\n\nDesktop file saved to\n{location}'
                  ).format(location=desktop_file_path))
Esempio n. 18
0
    def __init__(self, device_name=None, container_path=None, key_file=None, mount_point=None):
        """ Command line arguments checks are done here to be able to display a graphical dialog with error messages .
            If no arguments were supplied on the command line a setup dialog will be shown.
            All commands will be executed from a separate worker process with administrator privileges that gets initialized here.
            :param device_name: The device mapper name
            :type device_name: str/unicode or None
            :param container_path: The path of the container file
            :type container_path: str/unicode or None
            :param key_file: The path of an optional key file
            :type key_file: str/unicode or None
            :param mount_point: The path of an optional mount point
            :type mount_point: str/unicode or None
        """
        super(MainWindow, self).__init__()

        self.luks_device_name = device_name
        self.encrypted_container = container_path
        self.key_file = key_file
        self.mount_point = mount_point

        self.worker = None
        self.is_waiting_for_worker = False
        self.is_unlocked = False
        self.is_initialized = False
        self.has_tray = QSystemTrayIcon.isSystemTrayAvailable()

        # L10n: program name - translatable for startmenu titlebar etc
        self.setWindowTitle(_('luckyLUKS'))
        self.setWindowIcon(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)))

        # check if cryptsetup and sudo are installed
        not_installed_msg = _('{program_name} executable not found!\nPlease install, eg for Debian/Ubuntu\n`apt-get install {program_name}`')
        if not utils.is_installed('cryptsetup'):
            show_alert(self, not_installed_msg.format(program_name='cryptsetup'), critical=True)
        if not utils.is_installed('sudo'):
            show_alert(self, not_installed_msg.format(program_name='sudo'), critical=True)
        # quick sanity checks before asking for passwd
        if os.getuid() == 0:
            show_alert(self, _('Graphical programs should not be run as root!\nPlease call as normal user.'), critical=True)
        if self.encrypted_container and not os.path.exists(self.encrypted_container):
            show_alert(self, _('Container file not accessible\nor path does not exist:\n\n{file_path}').format(file_path=self.encrypted_container), critical=True)

        # only either encrypted_container or luks_device_name supplied
        if bool(self.encrypted_container) != bool(self.luks_device_name):
            show_alert(self, _('Invalid arguments:\n'
                               'Please call without any arguments\n'
                               'or supply both container and name.\n\n'
                               '<b>{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]</b>\n\n'
                               'CONTAINER = Path of the encrypted container file\n'
                               'NAME = A (unique) name to identify the unlocked container\n'
                               'Optional: MOUNTPOINT = where to mount the encrypted filesystem\n\n'
                               'If automatic mounting is configured on your system,\n'
                               'explicitly setting a mountpoint is not required\n\n'
                               'For more information, visit\n'
                               '<a href="{project_url}">{project_url}</a>'
                               ).format(executable=os.path.basename(sys.argv[0]),
                                        project_url=PROJECT_URL), critical=True)

        # spawn worker process with root privileges
        try:
            self.worker = utils.WorkerMonitor(self)
            # start communication thread
            self.worker.start()
        except utils.SudoException as se:
            show_alert(self, format_exception(se), critical=True)
            return

        # if no arguments supplied, display dialog to gather this information
        if self.encrypted_container is None and self.luks_device_name is None:

            from luckyLUKS.setupUI import SetupDialog
            sd = SetupDialog(self)

            if sd.exec_() == QDialog.Accepted:
                self.luks_device_name = sd.get_luks_device_name()
                self.encrypted_container = sd.get_encrypted_container()
                self.mount_point = sd.get_mount_point()
                self.key_file = sd.get_keyfile()

                self.is_unlocked = True  # all checks in setup dialog -> skip initializing state
            else:
                # user closed dialog -> quit program
                # and check if a keyfile create thread has to be stopped
                # the worker process terminates itself when its parent dies
                if hasattr(sd, 'create_thread') and sd.create_thread.isRunning():
                    sd.create_thread.terminate()
                QApplication.instance().quit()
                return

        # center window on desktop
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

        # widget content
        main_grid = QGridLayout()
        main_grid.setSpacing(10)
        icon = QLabel()
        icon.setPixmap(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)).pixmap(32))
        main_grid.addWidget(icon, 0, 0)
        main_grid.addWidget(QLabel('<b>' + _('Handle encrypted container') + '</b>\n'), 0, 1, alignment=Qt.AlignCenter)

        main_grid.addWidget(QLabel(_('Name:')), 1, 0)
        main_grid.addWidget(QLabel('<b>{dev_name}</b>'.format(dev_name=self.luks_device_name)), 1, 1, alignment=Qt.AlignCenter)

        main_grid.addWidget(QLabel(_('File:')), 2, 0)
        main_grid.addWidget(QLabel(self.encrypted_container), 2, 1, alignment=Qt.AlignCenter)

        if self.key_file is not None:
            main_grid.addWidget(QLabel(_('Key:')), 3, 0)
            main_grid.addWidget(QLabel(self.key_file), 3, 1, alignment=Qt.AlignCenter)

        if self.mount_point is not None:
            main_grid.addWidget(QLabel(_('Mount:')), 4, 0)
            main_grid.addWidget(QLabel(self.mount_point), 4, 1, alignment=Qt.AlignCenter)

        main_grid.addWidget(QLabel(_('Status:')), 5, 0)
        self.label_status = QLabel('')
        main_grid.addWidget(self.label_status, 5, 1, alignment=Qt.AlignCenter)

        self.button_toggle_status = QPushButton('')
        self.button_toggle_status.setMinimumHeight(34)
        self.button_toggle_status.clicked.connect(self.toggle_container_status)
        main_grid.setRowMinimumHeight(6, 10)
        main_grid.addWidget(self.button_toggle_status, 7, 1)

        widget = QWidget()
        widget.setLayout(main_grid)
        widget.setContentsMargins(10, 10, 10, 10)
        self.setCentralWidget(widget)

        # tray popup menu
        if self.has_tray:
            tray_popup = QMenu(self)
            tray_popup.addAction(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)), self.luks_device_name).setEnabled(False)
            tray_popup.addSeparator()
            self.tray_toggle_action = QAction(QApplication.style().standardIcon(QStyle. SP_DesktopIcon), _('Hide'), self)
            self.tray_toggle_action.triggered.connect(self.toggle_main_window)
            tray_popup.addAction(self.tray_toggle_action)
            quit_action = QAction(QApplication.style().standardIcon(QStyle.SP_MessageBoxCritical), _('Quit'), self)
            quit_action.triggered.connect(self.tray_quit)
            tray_popup.addAction(quit_action)
            # systray
            self.tray = QSystemTrayIcon(self)
            self.tray.setIcon(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)))
            self.tray.setContextMenu(tray_popup)
            self.tray.activated.connect(self.toggle_main_window)
            self.tray.show()

        self.init_status()
Esempio n. 19
0
    def __init__(self,
                 device_name=None,
                 container_path=None,
                 key_file=None,
                 mount_point=None):
        """ Command line arguments checks are done here to be able to display a graphical dialog with error messages .
            If no arguments were supplied on the command line a setup dialog will be shown.
            All commands will be executed from a separate worker process with administrator privileges that gets initialized here.
            :param device_name: The device mapper name
            :type device_name: str/unicode or None
            :param container_path: The path of the container file
            :type container_path: str/unicode or None
            :param key_file: The path of an optional key file
            :type key_file: str/unicode or None
            :param mount_point: The path of an optional mount point
            :type mount_point: str/unicode or None
        """
        super(MainWindow, self).__init__()

        self.luks_device_name = device_name
        self.encrypted_container = container_path
        self.key_file = key_file
        self.mount_point = mount_point

        self.worker = None
        self.is_waiting_for_worker = False
        self.is_unlocked = False
        self.is_initialized = False
        self.has_tray = QSystemTrayIcon.isSystemTrayAvailable()

        # L10n: program name - translatable for startmenu titlebar etc
        self.setWindowTitle(_('luckyLUKS'))
        self.setWindowIcon(
            QIcon.fromTheme(
                'dialog-password',
                QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)))

        # check if cryptsetup and sudo are installed
        not_installed_msg = _(
            '{program_name} executable not found!\nPlease install, eg for Debian/Ubuntu\n`apt-get install {program_name}`'
        )
        if not utils.is_installed('cryptsetup'):
            show_alert(self,
                       not_installed_msg.format(program_name='cryptsetup'),
                       critical=True)
        if not utils.is_installed('sudo'):
            show_alert(self,
                       not_installed_msg.format(program_name='sudo'),
                       critical=True)
        # quick sanity checks before asking for passwd
        if os.getuid() == 0:
            show_alert(
                self,
                _('Graphical programs should not be run as root!\nPlease call as normal user.'
                  ),
                critical=True)
        if self.encrypted_container and not os.path.exists(
                self.encrypted_container):
            show_alert(
                self,
                _('Container file not accessible\nor path does not exist:\n\n{file_path}'
                  ).format(file_path=self.encrypted_container),
                critical=True)

        # only either encrypted_container or luks_device_name supplied
        if bool(self.encrypted_container) != bool(self.luks_device_name):
            show_alert(
                self,
                _('Invalid arguments:\n'
                  'Please call without any arguments\n'
                  'or supply both container and name.\n\n'
                  '<b>{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]</b>\n\n'
                  'CONTAINER = Path of the encrypted container file\n'
                  'NAME = A (unique) name to identify the unlocked container\n'
                  'Optional: MOUNTPOINT = where to mount the encrypted filesystem\n\n'
                  'If automatic mounting is configured on your system,\n'
                  'explicitly setting a mountpoint is not required\n\n'
                  'For more information, visit\n'
                  '<a href="{project_url}">{project_url}</a>').format(
                      executable=os.path.basename(sys.argv[0]),
                      project_url=PROJECT_URL),
                critical=True)

        # spawn worker process with root privileges
        try:
            self.worker = utils.WorkerMonitor(self)
            # start communication thread
            self.worker.start()
        except utils.SudoException as se:
            show_alert(self, format_exception(se), critical=True)
            return

        # if no arguments supplied, display dialog to gather this information
        if self.encrypted_container is None and self.luks_device_name is None:

            from luckyLUKS.setupUI import SetupDialog
            sd = SetupDialog(self)

            if sd.exec_() == QDialog.Accepted:
                self.luks_device_name = sd.get_luks_device_name()
                self.encrypted_container = sd.get_encrypted_container()
                self.mount_point = sd.get_mount_point()
                self.key_file = sd.get_keyfile()

                self.is_unlocked = True  # all checks in setup dialog -> skip initializing state
            else:
                # user closed dialog -> quit program
                # and check if a keyfile create thread has to be stopped
                # the worker process terminates itself when its parent dies
                if hasattr(sd,
                           'create_thread') and sd.create_thread.isRunning():
                    sd.create_thread.terminate()
                QApplication.instance().quit()
                return

        # center window on desktop
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

        # widget content
        main_grid = QGridLayout()
        main_grid.setSpacing(10)
        icon = QLabel()
        icon.setPixmap(
            QIcon.fromTheme(
                'dialog-password',
                QApplication.style().standardIcon(
                    QStyle.SP_DriveHDIcon)).pixmap(32))
        main_grid.addWidget(icon, 0, 0)
        main_grid.addWidget(QLabel('<b>' + _('Handle encrypted container') +
                                   '</b>\n'),
                            0,
                            1,
                            alignment=Qt.AlignCenter)

        main_grid.addWidget(QLabel(_('Name:')), 1, 0)
        main_grid.addWidget(QLabel(
            '<b>{dev_name}</b>'.format(dev_name=self.luks_device_name)),
                            1,
                            1,
                            alignment=Qt.AlignCenter)

        main_grid.addWidget(QLabel(_('File:')), 2, 0)
        main_grid.addWidget(QLabel(self.encrypted_container),
                            2,
                            1,
                            alignment=Qt.AlignCenter)

        if self.key_file is not None:
            main_grid.addWidget(QLabel(_('Key:')), 3, 0)
            main_grid.addWidget(QLabel(self.key_file),
                                3,
                                1,
                                alignment=Qt.AlignCenter)

        if self.mount_point is not None:
            main_grid.addWidget(QLabel(_('Mount:')), 4, 0)
            main_grid.addWidget(QLabel(self.mount_point),
                                4,
                                1,
                                alignment=Qt.AlignCenter)

        main_grid.addWidget(QLabel(_('Status:')), 5, 0)
        self.label_status = QLabel('')
        main_grid.addWidget(self.label_status, 5, 1, alignment=Qt.AlignCenter)

        self.button_toggle_status = QPushButton('')
        self.button_toggle_status.setMinimumHeight(34)
        self.button_toggle_status.clicked.connect(self.toggle_container_status)
        main_grid.setRowMinimumHeight(6, 10)
        main_grid.addWidget(self.button_toggle_status, 7, 1)

        widget = QWidget()
        widget.setLayout(main_grid)
        widget.setContentsMargins(10, 10, 10, 10)
        self.setCentralWidget(widget)

        # tray popup menu
        if self.has_tray:
            tray_popup = QMenu(self)
            tray_popup.addAction(
                QIcon.fromTheme(
                    'dialog-password',
                    QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)),
                self.luks_device_name).setEnabled(False)
            tray_popup.addSeparator()
            self.tray_toggle_action = QAction(
                QApplication.style().standardIcon(QStyle.SP_DesktopIcon),
                _('Hide'), self)
            self.tray_toggle_action.triggered.connect(self.toggle_main_window)
            tray_popup.addAction(self.tray_toggle_action)
            quit_action = QAction(
                QApplication.style().standardIcon(
                    QStyle.SP_MessageBoxCritical), _('Quit'), self)
            quit_action.triggered.connect(self.tray_quit)
            tray_popup.addAction(quit_action)
            # systray
            self.tray = QSystemTrayIcon(self)
            self.tray.setIcon(
                QIcon.fromTheme(
                    'dialog-password',
                    QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)))
            self.tray.setContextMenu(tray_popup)
            self.tray.activated.connect(self.toggle_main_window)
            self.tray.show()

        self.init_status()