mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 01:15:33 +00:00
This change allows to collect data for a diagnostic report. This can be done either from the command line through the new `km-diag-report` tool, or in `km-config` from the new `Support` tab that allows to generate the report and then copy it to the clipboard or save as a file. The diagnostic report contains installed browsers and their versions and installation methods as well as the default browser. It checks for browsers installed through apt (Ubuntu/Debian), snap, and flatpak. The change also has an implementation for pacman (Arch Linux) and dnf/yum (Fedora), although these have not been tested. Fixes: #7784
277 lines
11 KiB
Python
Executable file
277 lines
11 KiB
Python
Executable file
#!/usr/bin/python3
|
|
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
import gi
|
|
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
gi.require_version('Gdk', '3.0')
|
|
|
|
from gi.overrides.GLib import GError
|
|
from gi.repository import GdkPixbuf, Gtk
|
|
|
|
from keyman_config import _
|
|
from keyman_config.accelerators import bind_accelerator, init_accel
|
|
from keyman_config.convertico import checkandsaveico
|
|
from keyman_config.dbus_util import get_keyman_config_service
|
|
from keyman_config.downloadkeyboard import DownloadKmpWindow
|
|
from keyman_config.fcitx_util import is_fcitx_running
|
|
from keyman_config.get_kmp import (InstallLocation, get_keyboard_dir,
|
|
get_install_area_string, get_keyman_dir)
|
|
from keyman_config.ibus_util import IbusDaemon, verify_ibus_daemon
|
|
from keyman_config.install_kmp import process_keyboard_data
|
|
from keyman_config.install_window import InstallKmpWindow, find_keyman_image
|
|
from keyman_config.keyboard_layouts_model import create_kbd_layouts_model
|
|
from keyman_config.keyboard_layouts_widget import KeyboardLayoutsWidget
|
|
from keyman_config.list_installed_kmp import get_installed_kmp
|
|
from keyman_config.options_widget import OptionsWidget
|
|
from keyman_config.sentry_handling import SentryErrorHandling
|
|
from keyman_config.support_widget import SupportWidget
|
|
|
|
|
|
class ViewInstalledWindowBase(Gtk.Window):
|
|
def __init__(self):
|
|
self.accelerators = None
|
|
super().__init__(title=_("Keyman Configuration"))
|
|
init_accel(self)
|
|
self._config_service = get_keyman_config_service(self.refresh_installed_kmp)
|
|
|
|
def refresh_installed_kmp(self):
|
|
pass
|
|
|
|
def on_close_clicked(self, button):
|
|
logging.debug("Close application clicked")
|
|
self.close()
|
|
|
|
def on_refresh_clicked(self, button):
|
|
logging.debug("Refresh application clicked")
|
|
self.refresh_installed_kmp()
|
|
|
|
def on_download_clicked(self, button):
|
|
logging.debug("Download clicked")
|
|
downloadDlg = DownloadKmpWindow(self)
|
|
response = downloadDlg.run()
|
|
if response != Gtk.ResponseType.OK:
|
|
downloadDlg.destroy()
|
|
return
|
|
|
|
file = downloadDlg.downloadfile
|
|
language = downloadDlg.language
|
|
downloadDlg.destroy()
|
|
self.restart(self.install_file(file, language))
|
|
|
|
def on_installfile_clicked(self, button):
|
|
logging.debug("Install from file clicked")
|
|
dlg = Gtk.FileChooserDialog(
|
|
_("Choose a kmp file..."), self, Gtk.FileChooserAction.OPEN,
|
|
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
|
|
dlg.resize(640, 480)
|
|
filter_text = Gtk.FileFilter()
|
|
# i18n: file type in file selection dialog
|
|
filter_text.set_name(_("KMP files"))
|
|
filter_text.add_pattern("*.kmp")
|
|
dlg.add_filter(filter_text)
|
|
while True:
|
|
response = dlg.run()
|
|
if response != Gtk.ResponseType.OK:
|
|
dlg.destroy()
|
|
return
|
|
|
|
file = dlg.get_filename()
|
|
if file and os.path.isfile(file) and os.path.splitext(file)[1] == '.kmp':
|
|
dlg.destroy()
|
|
self.restart(self.install_file(file))
|
|
return
|
|
# Loop until we have a valid file or the user cancels the dialog
|
|
|
|
def install_file(self, kmpfile, language=None):
|
|
installDlg = InstallKmpWindow(kmpfile, viewkmp=self, language=language)
|
|
if installDlg.is_error:
|
|
return Gtk.ResponseType.CANCEL
|
|
result = installDlg.run()
|
|
installDlg.destroy()
|
|
return result
|
|
|
|
def restart(self, response=Gtk.ResponseType.OK):
|
|
if response != Gtk.ResponseType.CANCEL:
|
|
subprocess.Popen(sys.argv)
|
|
self.close()
|
|
|
|
def run(self):
|
|
self.resize(776, 424)
|
|
self.connect("destroy", Gtk.main_quit)
|
|
self.show_all()
|
|
|
|
if (not is_fcitx_running()) and (verify_ibus_daemon(False) != IbusDaemon.RUNNING):
|
|
dialog = Gtk.MessageDialog(
|
|
self, 0, Gtk.MessageType.WARNING,
|
|
Gtk.ButtonsType.YES_NO, _("ibus-daemon is not running. Try to start ibus-daemon now?"))
|
|
dialog.format_secondary_text(
|
|
_("If you just recently installed Keyman you might have to reboot or logout and login again."))
|
|
response = dialog.run()
|
|
dialog.destroy()
|
|
if response == Gtk.ResponseType.YES:
|
|
if verify_ibus_daemon(True) != IbusDaemon.RUNNING:
|
|
dialog = Gtk.MessageDialog(
|
|
self, 0, Gtk.MessageType.WARNING,
|
|
Gtk.ButtonsType.OK,
|
|
_("Unable to start ibus-daemon. Please reboot or logout and login again."))
|
|
dialog.run()
|
|
dialog.destroy()
|
|
self.close()
|
|
else:
|
|
self.close()
|
|
Gtk.main()
|
|
|
|
|
|
class ViewInstalledWindow(ViewInstalledWindowBase):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.sentry = SentryErrorHandling()
|
|
|
|
outmostVBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
outmostVBox.pack_start(self._add_stack_sidebar(), True, True, 0)
|
|
outmostVBox.pack_end(self._add_app_buttons(), False, True, 12)
|
|
self.add(outmostVBox)
|
|
|
|
def _add_stack_sidebar(self):
|
|
outerHbox = Gtk.HBox()
|
|
sidebar = Gtk.StackSidebar()
|
|
stack = Gtk.Stack()
|
|
|
|
outerHbox.pack_start(sidebar, False, False, 0)
|
|
outerHbox.pack_end(Gtk.Separator(), False, False, 0)
|
|
outerHbox.pack_end(stack, True, True, 0)
|
|
stack.set_hexpand(True)
|
|
stack.set_vexpand(True)
|
|
sidebar.set_stack(stack)
|
|
|
|
self.store = create_kbd_layouts_model()
|
|
self.refresh_installed_kmp()
|
|
stack.add_titled(KeyboardLayoutsWidget(self, self.store, self.restart), "KeyboardLayouts", _("Keyboard Layouts"))
|
|
stack.add_titled(OptionsWidget(self.sentry), "Options", _("Options"))
|
|
stack.add_titled(SupportWidget(), "Support", _("Support"))
|
|
return outerHbox
|
|
|
|
def _add_app_buttons(self):
|
|
bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.HORIZONTAL)
|
|
bbox_bottom.set_layout(Gtk.ButtonBoxStyle.START)
|
|
|
|
button = Gtk.Button.new_with_mnemonic(_("_Install keyboard..."))
|
|
button.set_tooltip_text(_("Install a keyboard from a file"))
|
|
button.connect("clicked", self.on_installfile_clicked)
|
|
bbox_bottom.add(button)
|
|
|
|
button = Gtk.Button.new_with_mnemonic(_("_Download keyboard..."))
|
|
button.set_tooltip_text(_("Download and install a keyboard from the Keyman website"))
|
|
button.connect("clicked", self.on_download_clicked)
|
|
bbox_bottom.add(button)
|
|
|
|
button = Gtk.Button.new_with_mnemonic(_("_Refresh"))
|
|
button.set_tooltip_text(_("Refresh keyboard list"))
|
|
button.connect("clicked", self.on_refresh_clicked)
|
|
bbox_bottom.add(button)
|
|
|
|
button = Gtk.Button.new_with_mnemonic(_("_Close"))
|
|
button.set_tooltip_text(_("Close window"))
|
|
button.connect("clicked", self.on_close_clicked)
|
|
bind_accelerator(self.accelerators, button, '<Control>q')
|
|
bind_accelerator(self.accelerators, button, '<Control>w')
|
|
bbox_bottom.add(button)
|
|
|
|
bbox_hbox = Gtk.HBox(spacing=12)
|
|
bbox_hbox.pack_start(bbox_bottom, True, True, 12)
|
|
return bbox_hbox
|
|
|
|
def _try_restore_icon_file(self, path, kmpdata):
|
|
if not os.access(path, os.X_OK | os.W_OK):
|
|
return ''
|
|
|
|
file_name = os.path.join(path, kmpdata['packageID'] + '.bmp')
|
|
if not os.path.isfile(file_name):
|
|
file_name = os.path.join(path, kmpdata['packageID'] + '.ico')
|
|
if not os.path.isfile(file_name):
|
|
file_name = os.path.join(path, kmpdata['keyboardID'] + '.bmp')
|
|
if not os.path.isfile(file_name):
|
|
file_name = os.path.join(path, kmpdata['keyboardID'] + '.ico')
|
|
if not os.path.isfile(file_name):
|
|
return ''
|
|
checkandsaveico(file_name)
|
|
root, ext = os.path.splitext(file_name)
|
|
return root + '.bmp.png'
|
|
|
|
def _addlistitems(self, installed_kmp, store, install_area):
|
|
bmppng = ".bmp.png" # Icon file extension
|
|
|
|
for kmp in sorted(installed_kmp):
|
|
kmpdata = installed_kmp[kmp]
|
|
path = get_keyboard_dir(install_area, kmpdata['packageID'])
|
|
|
|
welcome_file = os.path.join(path, "welcome.htm")
|
|
options_file = os.path.join(path, "options.htm")
|
|
icofile_name = os.path.join(path, kmpdata['packageID'] + bmppng)
|
|
|
|
if not os.path.isfile(welcome_file):
|
|
welcome_file = None
|
|
if not os.path.isfile(options_file):
|
|
options_file = None
|
|
if not os.path.isfile(icofile_name):
|
|
icofile_name = os.path.join(path, kmpdata['keyboardID'] + bmppng)
|
|
if not os.path.isfile(icofile_name):
|
|
icofile_name = self._try_restore_icon_file(path, kmpdata)
|
|
if icofile_name == '':
|
|
icofile_name = find_keyman_image('icon_kmp.png')
|
|
|
|
try:
|
|
icofile = GdkPixbuf.Pixbuf.new_from_file_at_size(icofile_name, 16, 16)
|
|
except GError:
|
|
_, value, _ = sys.exc_info()
|
|
logging.info(f"Error reading icon file {icofile_name}: {value.message}")
|
|
icofile = None
|
|
|
|
store.append([
|
|
icofile,
|
|
kmpdata['name'],
|
|
kmpdata['kmpversion'],
|
|
kmpdata['packageID'],
|
|
install_area,
|
|
get_install_area_string(install_area),
|
|
path.replace(os.path.expanduser('~'), '~'),
|
|
welcome_file,
|
|
options_file])
|
|
|
|
def _try_restore_package_json(self, packageId, area):
|
|
packageDir = get_keyman_dir(area)
|
|
if not os.access(packageDir, os.X_OK | os.W_OK):
|
|
# we don't have write access to packageDir, so we can't restore the .json file
|
|
return False
|
|
return process_keyboard_data(packageId, os.path.join(packageDir, packageId))
|
|
|
|
def _add_keyboards_from_area(self, area):
|
|
kmps = get_installed_kmp(area)
|
|
for kmp in sorted(kmps):
|
|
kmpdata = kmps[kmp]
|
|
if not kmpdata['has_kbjson'] and not self._try_restore_package_json(kmpdata['packageID'], area):
|
|
self.incomplete_kmp.append(kmpdata)
|
|
self._addlistitems(kmps, self.store, area)
|
|
|
|
def refresh_installed_kmp(self):
|
|
logging.debug("Refreshing listview")
|
|
self.store.clear()
|
|
self.incomplete_kmp = []
|
|
self._add_keyboards_from_area(InstallLocation.User)
|
|
self._add_keyboards_from_area(InstallLocation.Shared)
|
|
self._add_keyboards_from_area(InstallLocation.OS)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
w = ViewInstalledWindow()
|
|
w.connect("destroy", Gtk.main_quit)
|
|
w.resize(576, 324)
|
|
w.show_all()
|
|
Gtk.main()
|