prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Backtrace when run without gtk installed
  If you try to bring up the graphical interface without gtk installed, you get a backtrace:
  ```
  Traceback (most recent call last):
    File "/usr/bin/solaar", line 57, in <module>
      solaar.gtk.main()
    File "/usr/lib/python3.7/site-packages/solaar/gtk.py", line 90, in main
      gi.require_version('Gtk', '3.0')
    File "/usr/lib64/python3.7/site-packages/gi/__init__.py", line 127, in require_version
      raise ValueError('Namespace %s not available' % namespace)
  ValueError: Namespace Gtk not available
  ```
  It does appear that there is code to handle missing modules, but gtk doesn't get imported directly so it wouldn't trigger.  Maybe something like this?
  ```
  diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py
  index f728420..20683af 100644
  --- a/lib/solaar/gtk.py
  +++ b/lib/solaar/gtk.py
  @@ -87,8 +87,11 @@ def main():
          if not args: return
          if args.action: return _cli.run(args.action, args.hidraw_path)
  
  -       gi = _require('gi', 'python-gi')
  -       gi.require_version('Gtk', '3.0')
  +       try:
  +               gi.require_version('Gtk', '3.0')
  +       except ValueError:
  +               import sys
  +               sys.exit("%s: Gtk (version 3) must be installed in order to run the graphical interface." % (NAME))
          _require('gi.repository.Gtk', 'gir1.2-gtk-3.0')
  
          try:
  ```
  Can send a PR if desired, but I don't know if that's the right approach.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `lib/solaar/gtk.py`
  Content:
  ```
  1 #!/usr/bin/env python3
  2 # -*- python-mode -*-
  3 # -*- coding: UTF-8 -*-
  4 
  5 ## Copyright (C) 2012-2013  Daniel Pavel
  6 ##
  7 ## This program is free software; you can redistribute it and/or modify
  8 ## it under the terms of the GNU General Public License as published by
  9 ## the Free Software Foundation; either version 2 of the License, or
  10 ## (at your option) any later version.
  11 ##
  12 ## This program is distributed in the hope that it will be useful,
  13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 ## GNU General Public License for more details.
  16 ##
  17 ## You should have received a copy of the GNU General Public License along
  18 ## with this program; if not, write to the Free Software Foundation, Inc.,
  19 ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20 
  21 from __future__ import absolute_import, division, print_function, unicode_literals
  22 
  23 import importlib
  24 
  25 
  26 from solaar import __version__, NAME
  27 import solaar.i18n as _i18n
  28 import solaar.cli as _cli
  29 
  30 #
  31 #
  32 #
  33 
  34 def _require(module, os_package):
  35 	try:
  36 		return importlib.import_module(module)
  37 	except ImportError:
  38 		import sys
  39 		sys.exit("%s: missing required system package %s" % (NAME, os_package))
  40 
  41 
  42 def _parse_arguments():
  43 	import argparse
  44 	arg_parser = argparse.ArgumentParser(prog=NAME.lower())
  45 	arg_parser.add_argument('-d', '--debug', action='count', default=0,
  46 							help='print logging messages, for debugging purposes (may be repeated for extra verbosity)')
  47 	arg_parser.add_argument('-D', '--hidraw', action='store', dest='hidraw_path', metavar='PATH',
  48 							help='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2')
  49 	arg_parser.add_argument('--restart-on-wake-up', action='store_true',
  50 							help='restart Solaar on sleep wake-up (experimental)')
  51 	arg_parser.add_argument('-w', '--window', choices=('show','hide','only'), help='start with window showing / hidden / only (no tray icon)')
  52 	arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__)
  53 	arg_parser.add_argument('--help-actions', action='store_true',
  54 							help='print help for the optional actions')
  55 	arg_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions,
  56 							help='optional actions to perform')
  57 
  58 	args = arg_parser.parse_args()
  59 
  60 	if args.help_actions:
  61 		_cli.print_help()
  62 		return
  63 
  64 	if args.window is None:
  65 		args.window = 'show' # default behaviour is to show main window
  66 
  67 	import logging
  68 	if args.debug > 0:
  69 		log_level = logging.WARNING - 10 * args.debug
  70 		log_format='%(asctime)s,%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s'
  71 		logging.basicConfig(level=max(log_level, logging.DEBUG), format=log_format, datefmt='%H:%M:%S')
  72 	else:
  73 		logging.root.addHandler(logging.NullHandler())
  74 		logging.root.setLevel(logging.ERROR)
  75 
  76 	if not args.action:
  77 		if logging.root.isEnabledFor(logging.INFO):
  78 			logging.info("language %s (%s), translations path %s", _i18n.language, _i18n.encoding, _i18n.path)
  79 
  80 	return args
  81 
  82 
  83 def main():
  84 	_require('pyudev', 'python3-pyudev')
  85 
  86 	# handle ^C in console
  87 	import signal
  88 	signal.signal(signal.SIGINT, signal.SIG_DFL)
  89 
  90 	args = _parse_arguments()
  91 	if not args: return
  92 	if args.action:
  93 		# if any argument, run comandline and exit
  94 		return _cli.run(args.action, args.hidraw_path)
  95 
  96 	gi = _require('gi', 'python3-gi or python3-gobject')
  97 	gi.require_version('Gtk', '3.0')
  98 	_require('gi.repository.Gtk', 'gir1.2-gtk-3.0')
  99 
  100 	try:
  101 		import solaar.ui as ui
  102 		import solaar.listener as listener
  103 		listener.setup_scanner(ui.status_changed, ui.error_dialog)
  104 
  105 		import solaar.upower as _upower
  106 		if args.restart_on_wake_up:
  107 			_upower.watch(listener.start_all, listener.stop_all)
  108 		else:
  109 			_upower.watch(lambda: listener.ping_all(True))
  110 
  111 		# main UI event loop
  112 		ui.run_loop(listener.start_all, listener.stop_all, args.window!='only', args.window!='hide')
  113 	except Exception as e:
  114 		import sys
  115 		sys.exit('%s: error: %s' % (NAME.lower(), e))
  116 
  117 
  118 if __name__ == '__main__':
  119 	main()
  120
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py
  --- a/lib/solaar/gtk.py
  +++ b/lib/solaar/gtk.py
  @@ -31,10 +31,12 @@
   #
   #
   
  -def _require(module, os_package):
  +def _require(module, os_package, gi=None, gi_package=None, gi_version=None):
   	try:
  +		if gi is not None:
  +			gi.require_version(gi_package,gi_version)
   		return importlib.import_module(module)
  -	except ImportError:
  +	except (ImportError, ValueError):
   		import sys
   		sys.exit("%s: missing required system package %s" % (NAME, os_package))
   
  @@ -94,8 +96,7 @@
   		return _cli.run(args.action, args.hidraw_path)
   
   	gi = _require('gi', 'python3-gi or python3-gobject')
  -	gi.require_version('Gtk', '3.0')
  -	_require('gi.repository.Gtk', 'gir1.2-gtk-3.0')
  +	_require('gi.repository.Gtk', 'gir1.2-gtk-3.0', gi, 'Gtk', '3.0')
   
   	try:
   		import solaar.ui as ui
  
verification_info: {"golden_diff": "diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py\n--- a/lib/solaar/gtk.py\n+++ b/lib/solaar/gtk.py\n@@ -31,10 +31,12 @@\n #\n #\n \n-def _require(module, os_package):\n+def _require(module, os_package, gi=None, gi_package=None, gi_version=None):\n \ttry:\n+\t\tif gi is not None:\n+\t\t\tgi.require_version(gi_package,gi_version)\n \t\treturn importlib.import_module(module)\n-\texcept ImportError:\n+\texcept (ImportError, ValueError):\n \t\timport sys\n \t\tsys.exit(\"%s: missing required system package %s\" % (NAME, os_package))\n \n@@ -94,8 +96,7 @@\n \t\treturn _cli.run(args.action, args.hidraw_path)\n \n \tgi = _require('gi', 'python3-gi or python3-gobject')\n-\tgi.require_version('Gtk', '3.0')\n-\t_require('gi.repository.Gtk', 'gir1.2-gtk-3.0')\n+\t_require('gi.repository.Gtk', 'gir1.2-gtk-3.0', gi, 'Gtk', '3.0')\n \n \ttry:\n \t\timport solaar.ui as ui\n", "issue": "Backtrace when run without gtk installed\nIf you try to bring up the graphical interface without gtk installed, you get a backtrace:\r\n```\r\nTraceback (most recent call last):\r\n  File \"/usr/bin/solaar\", line 57, in <module>\r\n    solaar.gtk.main()\r\n  File \"/usr/lib/python3.7/site-packages/solaar/gtk.py\", line 90, in main\r\n    gi.require_version('Gtk', '3.0')\r\n  File \"/usr/lib64/python3.7/site-packages/gi/__init__.py\", line 127, in require_version\r\n    raise ValueError('Namespace %s not available' % namespace)\r\nValueError: Namespace Gtk not available\r\n```\r\nIt does appear that there is code to handle missing modules, but gtk doesn't get imported directly so it wouldn't trigger.  Maybe something like this?\r\n```\r\ndiff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py\r\nindex f728420..20683af 100644\r\n--- a/lib/solaar/gtk.py\r\n+++ b/lib/solaar/gtk.py\r\n@@ -87,8 +87,11 @@ def main():\r\n        if not args: return\r\n        if args.action: return _cli.run(args.action, args.hidraw_path)\r\n\r\n-       gi = _require('gi', 'python-gi')\r\n-       gi.require_version('Gtk', '3.0')\r\n+       try:\r\n+               gi.require_version('Gtk', '3.0')\r\n+       except ValueError:\r\n+               import sys\r\n+               sys.exit(\"%s: Gtk (version 3) must be installed in order to run the graphical interface.\" % (NAME))\r\n        _require('gi.repository.Gtk', 'gir1.2-gtk-3.0')\r\n\r\n        try:\r\n```\r\nCan send a PR if desired, but I don't know if that's the right approach.\n", "before_files": [{"content": "#!/usr/bin/env python3\n# -*- python-mode -*-\n# -*- coding: UTF-8 -*-\n\n## Copyright (C) 2012-2013  Daniel Pavel\n##\n## This program is free software; you can redistribute it and/or modify\n## it under the terms of the GNU General Public License as published by\n## the Free Software Foundation; either version 2 of the License, or\n## (at your option) any later version.\n##\n## This program is distributed in the hope that it will be useful,\n## but WITHOUT ANY WARRANTY; without even the implied warranty of\n## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n## GNU General Public License for more details.\n##\n## You should have received a copy of the GNU General Public License along\n## with this program; if not, write to the Free Software Foundation, Inc.,\n## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport importlib\n\n\nfrom solaar import __version__, NAME\nimport solaar.i18n as _i18n\nimport solaar.cli as _cli\n\n#\n#\n#\n\ndef _require(module, os_package):\n\ttry:\n\t\treturn importlib.import_module(module)\n\texcept ImportError:\n\t\timport sys\n\t\tsys.exit(\"%s: missing required system package %s\" % (NAME, os_package))\n\n\ndef _parse_arguments():\n\timport argparse\n\targ_parser = argparse.ArgumentParser(prog=NAME.lower())\n\targ_parser.add_argument('-d', '--debug', action='count', default=0,\n\t\t\t\t\t\t\thelp='print logging messages, for debugging purposes (may be repeated for extra verbosity)')\n\targ_parser.add_argument('-D', '--hidraw', action='store', dest='hidraw_path', metavar='PATH',\n\t\t\t\t\t\t\thelp='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2')\n\targ_parser.add_argument('--restart-on-wake-up', action='store_true',\n\t\t\t\t\t\t\thelp='restart Solaar on sleep wake-up (experimental)')\n\targ_parser.add_argument('-w', '--window', choices=('show','hide','only'), help='start with window showing / hidden / only (no tray icon)')\n\targ_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__)\n\targ_parser.add_argument('--help-actions', action='store_true',\n\t\t\t\t\t\t\thelp='print help for the optional actions')\n\targ_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions,\n\t\t\t\t\t\t\thelp='optional actions to perform')\n\n\targs = arg_parser.parse_args()\n\n\tif args.help_actions:\n\t\t_cli.print_help()\n\t\treturn\n\n\tif args.window is None:\n\t\targs.window = 'show' # default behaviour is to show main window\n\n\timport logging\n\tif args.debug > 0:\n\t\tlog_level = logging.WARNING - 10 * args.debug\n\t\tlog_format='%(asctime)s,%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s'\n\t\tlogging.basicConfig(level=max(log_level, logging.DEBUG), format=log_format, datefmt='%H:%M:%S')\n\telse:\n\t\tlogging.root.addHandler(logging.NullHandler())\n\t\tlogging.root.setLevel(logging.ERROR)\n\n\tif not args.action:\n\t\tif logging.root.isEnabledFor(logging.INFO):\n\t\t\tlogging.info(\"language %s (%s), translations path %s\", _i18n.language, _i18n.encoding, _i18n.path)\n\n\treturn args\n\n\ndef main():\n\t_require('pyudev', 'python3-pyudev')\n\n\t# handle ^C in console\n\timport signal\n\tsignal.signal(signal.SIGINT, signal.SIG_DFL)\n\n\targs = _parse_arguments()\n\tif not args: return\n\tif args.action:\n\t\t# if any argument, run comandline and exit\n\t\treturn _cli.run(args.action, args.hidraw_path)\n\n\tgi = _require('gi', 'python3-gi or python3-gobject')\n\tgi.require_version('Gtk', '3.0')\n\t_require('gi.repository.Gtk', 'gir1.2-gtk-3.0')\n\n\ttry:\n\t\timport solaar.ui as ui\n\t\timport solaar.listener as listener\n\t\tlistener.setup_scanner(ui.status_changed, ui.error_dialog)\n\n\t\timport solaar.upower as _upower\n\t\tif args.restart_on_wake_up:\n\t\t\t_upower.watch(listener.start_all, listener.stop_all)\n\t\telse:\n\t\t\t_upower.watch(lambda: listener.ping_all(True))\n\n\t\t# main UI event loop\n\t\tui.run_loop(listener.start_all, listener.stop_all, args.window!='only', args.window!='hide')\n\texcept Exception as e:\n\t\timport sys\n\t\tsys.exit('%s: error: %s' % (NAME.lower(), e))\n\n\nif __name__ == '__main__':\n\tmain()\n", "path": "lib/solaar/gtk.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n# -*- python-mode -*-\n# -*- coding: UTF-8 -*-\n\n## Copyright (C) 2012-2013  Daniel Pavel\n##\n## This program is free software; you can redistribute it and/or modify\n## it under the terms of the GNU General Public License as published by\n## the Free Software Foundation; either version 2 of the License, or\n## (at your option) any later version.\n##\n## This program is distributed in the hope that it will be useful,\n## but WITHOUT ANY WARRANTY; without even the implied warranty of\n## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n## GNU General Public License for more details.\n##\n## You should have received a copy of the GNU General Public License along\n## with this program; if not, write to the Free Software Foundation, Inc.,\n## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport importlib\n\n\nfrom solaar import __version__, NAME\nimport solaar.i18n as _i18n\nimport solaar.cli as _cli\n\n#\n#\n#\n\ndef _require(module, os_package, gi=None, gi_package=None, gi_version=None):\n\ttry:\n\t\tif gi is not None:\n\t\t\tgi.require_version(gi_package,gi_version)\n\t\treturn importlib.import_module(module)\n\texcept (ImportError, ValueError):\n\t\timport sys\n\t\tsys.exit(\"%s: missing required system package %s\" % (NAME, os_package))\n\n\ndef _parse_arguments():\n\timport argparse\n\targ_parser = argparse.ArgumentParser(prog=NAME.lower())\n\targ_parser.add_argument('-d', '--debug', action='count', default=0,\n\t\t\t\t\t\t\thelp='print logging messages, for debugging purposes (may be repeated for extra verbosity)')\n\targ_parser.add_argument('-D', '--hidraw', action='store', dest='hidraw_path', metavar='PATH',\n\t\t\t\t\t\t\thelp='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2')\n\targ_parser.add_argument('--restart-on-wake-up', action='store_true',\n\t\t\t\t\t\t\thelp='restart Solaar on sleep wake-up (experimental)')\n\targ_parser.add_argument('-w', '--window', choices=('show','hide','only'), help='start with window showing / hidden / only (no tray icon)')\n\targ_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__)\n\targ_parser.add_argument('--help-actions', action='store_true',\n\t\t\t\t\t\t\thelp='print help for the optional actions')\n\targ_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions,\n\t\t\t\t\t\t\thelp='optional actions to perform')\n\n\targs = arg_parser.parse_args()\n\n\tif args.help_actions:\n\t\t_cli.print_help()\n\t\treturn\n\n\tif args.window is None:\n\t\targs.window = 'show' # default behaviour is to show main window\n\n\timport logging\n\tif args.debug > 0:\n\t\tlog_level = logging.WARNING - 10 * args.debug\n\t\tlog_format='%(asctime)s,%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s'\n\t\tlogging.basicConfig(level=max(log_level, logging.DEBUG), format=log_format, datefmt='%H:%M:%S')\n\telse:\n\t\tlogging.root.addHandler(logging.NullHandler())\n\t\tlogging.root.setLevel(logging.ERROR)\n\n\tif not args.action:\n\t\tif logging.root.isEnabledFor(logging.INFO):\n\t\t\tlogging.info(\"language %s (%s), translations path %s\", _i18n.language, _i18n.encoding, _i18n.path)\n\n\treturn args\n\n\ndef main():\n\t_require('pyudev', 'python3-pyudev')\n\n\t# handle ^C in console\n\timport signal\n\tsignal.signal(signal.SIGINT, signal.SIG_DFL)\n\n\targs = _parse_arguments()\n\tif not args: return\n\tif args.action:\n\t\t# if any argument, run comandline and exit\n\t\treturn _cli.run(args.action, args.hidraw_path)\n\n\tgi = _require('gi', 'python3-gi or python3-gobject')\n\t_require('gi.repository.Gtk', 'gir1.2-gtk-3.0', gi, 'Gtk', '3.0')\n\n\ttry:\n\t\timport solaar.ui as ui\n\t\timport solaar.listener as listener\n\t\tlistener.setup_scanner(ui.status_changed, ui.error_dialog)\n\n\t\timport solaar.upower as _upower\n\t\tif args.restart_on_wake_up:\n\t\t\t_upower.watch(listener.start_all, listener.stop_all)\n\t\telse:\n\t\t\t_upower.watch(lambda: listener.ping_all(True))\n\n\t\t# main UI event loop\n\t\tui.run_loop(listener.start_all, listener.stop_all, args.window!='only', args.window!='hide')\n\texcept Exception as e:\n\t\timport sys\n\t\tsys.exit('%s: error: %s' % (NAME.lower(), e))\n\n\nif __name__ == '__main__':\n\tmain()\n", "path": "lib/solaar/gtk.py"}]}