diff -Nru indicator-virtual-box-1.0.68/debian/changelog indicator-virtual-box-1.0.69/debian/changelog --- indicator-virtual-box-1.0.68/debian/changelog 2021-09-30 12:55:46.000000000 +0000 +++ indicator-virtual-box-1.0.69/debian/changelog 2021-11-05 22:55:02.000000000 +0000 @@ -1,3 +1,18 @@ +indicator-virtual-box (1.0.69-1) bionic; urgency=low + + * Bug fix: When bringing a running virtual machine window to front fails + (unable to uniquely identify the window), a notification informing the + user subsequently failed to display. + * Virtual machine name, UUID and group information now obtained directly + from VBoxManage rather than the XML configuration file which is somewhat + of a moving target. As the sort order cannot be obtained from VBoxManage, + group names are sorted alphabetically along with machine names. + * Removed the delay inserted on virtual machine autostart if the virtual + machine is already running and only needs its window brought to front. + + -- Bernard Giannetti Sat, 6 Nov 2021 9:54:02 +1000 + + indicator-virtual-box (1.0.68-1) bionic; urgency=low * Bug fix: VirtualBox 6 contains "n=GLOBAL" in the configuration file which diff -Nru indicator-virtual-box-1.0.68/po/indicator-virtual-box.pot indicator-virtual-box-1.0.69/po/indicator-virtual-box.pot --- indicator-virtual-box-1.0.68/po/indicator-virtual-box.pot 2021-09-30 12:57:48.000000000 +0000 +++ indicator-virtual-box-1.0.69/po/indicator-virtual-box.pot 2021-11-05 22:55:53.000000000 +0000 @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: indicator-virtual-box 1.0.68\n" +"Project-Id-Version: indicator-virtual-box 1.0.69\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-09-12 17:01+1000\n" +"POT-Creation-Date: 2021-11-01 12:54+1100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,116 +17,116 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: indicator-virtual-box.py:63 +#: indicator-virtual-box.py:64 msgid "Shows VirtualBox™ virtual machines and allows them to be started." msgstr "" -#: indicator-virtual-box.py:84 -msgid "(VirtualBox™ is not installed)" -msgstr "" - -#: indicator-virtual-box.py:100 +#: indicator-virtual-box.py:98 msgid "(no virtual machines exist)" msgstr "" -#: indicator-virtual-box.py:104 +#: indicator-virtual-box.py:102 msgid "Launch VirtualBox™ Manager" msgstr "" -#: indicator-virtual-box.py:161 +#: indicator-virtual-box.py:108 +msgid "(VirtualBox™ is not installed)" +msgstr "" + +#: indicator-virtual-box.py:182 msgid "" "The virtual machine could not be found - perhaps it has been renamed or " "deleted. The list of virtual machines has been refreshed - please try again." msgstr "" -#: indicator-virtual-box.py:162 +#: indicator-virtual-box.py:183 msgid "Error" msgstr "" -#: indicator-virtual-box.py:173 +#: indicator-virtual-box.py:192 #, python-brace-format msgid "" "Unable to find the window for the virtual machine '{0}' - perhaps it is " "running as headless." msgstr "" -#: indicator-virtual-box.py:174 indicator-virtual-box.py:186 +#: indicator-virtual-box.py:193 indicator-virtual-box.py:205 msgid "Warning" msgstr "" -#: indicator-virtual-box.py:185 +#: indicator-virtual-box.py:204 #, python-brace-format msgid "" "Unable to bring the virtual machine '{0}' to front as there is more than one " -"window of the same name." +"window with overlapping names." msgstr "" -#: indicator-virtual-box.py:367 +#: indicator-virtual-box.py:392 msgid "Virtual Machine" msgstr "" -#: indicator-virtual-box.py:368 indicator-virtual-box.py:505 +#: indicator-virtual-box.py:393 indicator-virtual-box.py:534 msgid "Autostart" msgstr "" -#: indicator-virtual-box.py:369 indicator-virtual-box.py:487 +#: indicator-virtual-box.py:394 indicator-virtual-box.py:516 msgid "Start Command" msgstr "" -#: indicator-virtual-box.py:370 +#: indicator-virtual-box.py:395 msgid "Double click to edit a virtual machine's properties." msgstr "" -#: indicator-virtual-box.py:378 +#: indicator-virtual-box.py:403 msgid "Virtual Machines" msgstr "" -#: indicator-virtual-box.py:386 +#: indicator-virtual-box.py:411 msgid "VirtualBox™ Manager" msgstr "" -#: indicator-virtual-box.py:392 +#: indicator-virtual-box.py:417 msgid "" "The window title of VirtualBox™ Manager.\n" "You may have to adjust for your local language." msgstr "" -#: indicator-virtual-box.py:399 +#: indicator-virtual-box.py:424 msgid "Show groups as submenus" msgstr "" -#: indicator-virtual-box.py:401 +#: indicator-virtual-box.py:426 msgid "" "If checked, groups are shown using submenus.\n" "\n" "Otherwise groups are shown as an indented list." msgstr "" -#: indicator-virtual-box.py:413 +#: indicator-virtual-box.py:438 msgid "Refresh interval (minutes)" msgstr "" -#: indicator-virtual-box.py:419 +#: indicator-virtual-box.py:446 msgid "" "How often the list of virtual machines\n" "and their running status are updated." msgstr "" -#: indicator-virtual-box.py:430 +#: indicator-virtual-box.py:457 msgid "Startup delay (seconds)" msgstr "" -#: indicator-virtual-box.py:436 +#: indicator-virtual-box.py:465 msgid "" "Amount of time to wait from automatically\n" "starting one virtual machine to the next." msgstr "" -#: indicator-virtual-box.py:444 +#: indicator-virtual-box.py:473 msgid "General" msgstr "" -#: indicator-virtual-box.py:498 +#: indicator-virtual-box.py:527 msgid "" "The terminal command to start the virtual machine such as\n" "\n" @@ -135,19 +135,19 @@ "\tVBoxHeadless --startvm %VM% --vrde off" msgstr "" -#: indicator-virtual-box.py:506 +#: indicator-virtual-box.py:535 msgid "Run the virtual machine when the indicator starts." msgstr "" -#: indicator-virtual-box.py:512 +#: indicator-virtual-box.py:541 msgid "Virtual Machine Properties" msgstr "" -#: indicator-virtual-box.py:520 +#: indicator-virtual-box.py:549 msgid "The start command cannot be empty." msgstr "" -#: indicator-virtual-box.py:525 +#: indicator-virtual-box.py:554 msgid "" "The start command must contain %VM% which is substituted for the virtual " "machine name/id." Binary files /tmp/tmps9bf0zl9/JHs2wOPJYg/indicator-virtual-box-1.0.68/po/ru/indicator-virtual-box.mo and /tmp/tmps9bf0zl9/vPqtiCtpHB/indicator-virtual-box-1.0.69/po/ru/indicator-virtual-box.mo differ diff -Nru indicator-virtual-box-1.0.68/po/ru/indicator-virtual-box.po indicator-virtual-box-1.0.69/po/ru/indicator-virtual-box.po --- indicator-virtual-box-1.0.68/po/ru/indicator-virtual-box.po 2021-09-30 12:57:48.000000000 +0000 +++ indicator-virtual-box-1.0.69/po/ru/indicator-virtual-box.po 2021-11-05 22:55:53.000000000 +0000 @@ -5,10 +5,10 @@ # msgid "" msgstr "" -"Project-Id-Version: indicator-virtual-box 1.0.68\n" +"Project-Id-Version: indicator-virtual-box 1.0.69\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-09-12 17:01+1000\n" -"PO-Revision-Date: 2021-09-27 15:02+0300\n" +"POT-Creation-Date: 2021-11-01 12:54+1100\n" +"PO-Revision-Date: 2021-11-03 15:53+0300\n" "Last-Translator: Oleg Moiseichuk \n" "Language-Team: Russian\n" "Language: ru\n" @@ -18,23 +18,23 @@ "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -#: indicator-virtual-box.py:63 +#: indicator-virtual-box.py:64 msgid "Shows VirtualBox™ virtual machines and allows them to be started." msgstr "Показывает виртуальные машины VirtualBox™ и позволяет запускать их." -#: indicator-virtual-box.py:84 -msgid "(VirtualBox™ is not installed)" -msgstr "(VirtualBox не установлен)" - -#: indicator-virtual-box.py:100 +#: indicator-virtual-box.py:98 msgid "(no virtual machines exist)" msgstr "(виртуальные машины не найдены)" -#: indicator-virtual-box.py:104 +#: indicator-virtual-box.py:102 msgid "Launch VirtualBox™ Manager" msgstr "Запустить Менеджер VirtualBox" -#: indicator-virtual-box.py:161 +#: indicator-virtual-box.py:108 +msgid "(VirtualBox™ is not installed)" +msgstr "(VirtualBox не установлен)" + +#: indicator-virtual-box.py:182 msgid "" "The virtual machine could not be found - perhaps it has been renamed or " "deleted. The list of virtual machines has been refreshed - please try again." @@ -42,11 +42,11 @@ "Виртуальная машина не найдена: возможно, она была переименована или удалена. " "Список виртуальных машин был обновлён - пожалуйста, повторите попытку." -#: indicator-virtual-box.py:162 +#: indicator-virtual-box.py:183 msgid "Error" msgstr "Ошибка" -#: indicator-virtual-box.py:173 +#: indicator-virtual-box.py:192 msgid "" "Unable to find the window for the virtual machine '{0}' - perhaps it is " "running as headless." @@ -54,43 +54,43 @@ "Не удалось найти окно виртуальной машины '{0}': вероятно, она запущена в " "автономном режиме." -#: indicator-virtual-box.py:174 indicator-virtual-box.py:186 +#: indicator-virtual-box.py:193 indicator-virtual-box.py:205 msgid "Warning" msgstr "Предупреждение" -#: indicator-virtual-box.py:185 +#: indicator-virtual-box.py:204 msgid "" "Unable to bring the virtual machine '{0}' to front as there is more than one " -"window of the same name." +"window with overlapping names." msgstr "" "Невозможно переместить виртуальную машину '{0}' на передний план, так как " -"существует несколько окон с одинаковым именем." +"существует несколько окон, чьи имена совпадают." -#: indicator-virtual-box.py:367 +#: indicator-virtual-box.py:392 msgid "Virtual Machine" msgstr "Виртуальная машина" -#: indicator-virtual-box.py:368 indicator-virtual-box.py:505 +#: indicator-virtual-box.py:393 indicator-virtual-box.py:534 msgid "Autostart" msgstr "Автозапуск" -#: indicator-virtual-box.py:369 indicator-virtual-box.py:487 +#: indicator-virtual-box.py:394 indicator-virtual-box.py:516 msgid "Start Command" msgstr "Команда запуска" -#: indicator-virtual-box.py:370 +#: indicator-virtual-box.py:395 msgid "Double click to edit a virtual machine's properties." msgstr "Щёлкните дважды, чтобы отредактировать свойства виртуальной машины." -#: indicator-virtual-box.py:378 +#: indicator-virtual-box.py:403 msgid "Virtual Machines" msgstr "Виртуальные машины" -#: indicator-virtual-box.py:386 +#: indicator-virtual-box.py:411 msgid "VirtualBox™ Manager" msgstr "Менеджер VirtualBox" -#: indicator-virtual-box.py:392 +#: indicator-virtual-box.py:417 msgid "" "The window title of VirtualBox™ Manager.\n" "You may have to adjust for your local language." @@ -98,11 +98,11 @@ "Заголовок окна Менеджера VirtualBox.\n" "Настройте название для вашего языка." -#: indicator-virtual-box.py:399 +#: indicator-virtual-box.py:424 msgid "Show groups as submenus" msgstr "Показывать подменю для групп" -#: indicator-virtual-box.py:401 +#: indicator-virtual-box.py:426 msgid "" "If checked, groups are shown using submenus.\n" "\n" @@ -113,11 +113,11 @@ "\n" "Иначе группы отображаются в виде списка с отступом." -#: indicator-virtual-box.py:413 +#: indicator-virtual-box.py:438 msgid "Refresh interval (minutes)" msgstr "Интервал обновления (в минутах)" -#: indicator-virtual-box.py:419 +#: indicator-virtual-box.py:446 msgid "" "How often the list of virtual machines\n" "and their running status are updated." @@ -125,11 +125,11 @@ "Насколько часто обновляется список\n" "виртуальных машин и их статус." -#: indicator-virtual-box.py:430 +#: indicator-virtual-box.py:457 msgid "Startup delay (seconds)" msgstr "Задержка запуска (в секундах)" -#: indicator-virtual-box.py:436 +#: indicator-virtual-box.py:465 msgid "" "Amount of time to wait from automatically\n" "starting one virtual machine to the next." @@ -137,11 +137,11 @@ "Интервал времени до автоматического запуска следующей\n" "виртуальной машины после запуска предыдущей." -#: indicator-virtual-box.py:444 +#: indicator-virtual-box.py:473 msgid "General" msgstr "Общие" -#: indicator-virtual-box.py:498 +#: indicator-virtual-box.py:527 msgid "" "The terminal command to start the virtual machine such as\n" "\n" @@ -155,19 +155,19 @@ "или\n" "\tVBoxHeadless --startvm %VM% --vrde off" -#: indicator-virtual-box.py:506 +#: indicator-virtual-box.py:535 msgid "Run the virtual machine when the indicator starts." msgstr "Запускать виртуальную машину при старте индикатора." -#: indicator-virtual-box.py:512 +#: indicator-virtual-box.py:541 msgid "Virtual Machine Properties" msgstr "Свойства виртуальной машины" -#: indicator-virtual-box.py:520 +#: indicator-virtual-box.py:549 msgid "The start command cannot be empty." msgstr "Команда запуска не может быть пустой." -#: indicator-virtual-box.py:525 +#: indicator-virtual-box.py:554 msgid "" "The start command must contain %VM% which is substituted for the virtual " "machine name/id." diff -Nru indicator-virtual-box-1.0.68/src/indicatorbase.py indicator-virtual-box-1.0.69/src/indicatorbase.py --- indicator-virtual-box-1.0.68/src/indicatorbase.py 2021-09-30 12:57:48.000000000 +0000 +++ indicator-virtual-box-1.0.69/src/indicatorbase.py 2021-11-05 22:55:52.000000000 +0000 @@ -226,7 +226,7 @@ label = Gtk.Label() label.set_markup( markup ) label.show() - aboutDialog.get_content_area().get_children()[ 0 ].get_children()[ 2 ].get_children()[ 0 ].add( label ) + aboutDialog.get_content_area().get_children()[ 0 ].get_children()[ 2 ].get_children()[ 0 ].pack_start( label, False, False, 0 ) def __onPreferences( self, widget ): @@ -810,11 +810,16 @@ # Executes the command and returns the result. + # + # logNonZeroErrorCode If True, will log any exception arising from a non-zero return code; otherwise will ignore. + # # On exception, logs to file. - def processGet( self, command ): + def processGet( self, command, logNonZeroErrorCode = False ): result = None try: - result = subprocess.check_output( command, shell = True, stderr = subprocess.PIPE, universal_newlines = True ) + result = subprocess.run( command, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True, check = logNonZeroErrorCode ).stdout.decode() + if not result: + result = None except subprocess.CalledProcessError as e: self.getLogging().error( e ) diff -Nru indicator-virtual-box-1.0.68/src/indicator-virtual-box.py indicator-virtual-box-1.0.69/src/indicator-virtual-box.py --- indicator-virtual-box-1.0.68/src/indicator-virtual-box.py 2021-09-30 12:57:48.000000000 +0000 +++ indicator-virtual-box-1.0.69/src/indicator-virtual-box.py 2021-11-05 22:55:52.000000000 +0000 @@ -29,11 +29,12 @@ gi.require_version( "Notify", "0.7" ) from gi.repository import Gdk, Gtk, Notify +from indicatorbase import IndicatorBase -import indicatorbase, datetime, os, time, virtualmachine +import datetime, os, time, virtualmachine -class IndicatorVirtualBox( indicatorbase.IndicatorBase ): +class IndicatorVirtualBox( IndicatorBase ): CONFIG_DELAY_BETWEEN_AUTO_START_IN_SECONDS = "delayBetweenAutoStartInSeconds" CONFIG_REFRESH_INTERVAL_IN_MINUTES = "refreshIntervalInMinutes" @@ -41,7 +42,6 @@ CONFIG_VIRTUAL_MACHINE_PREFERENCES = "virtualMachinePreferences" CONFIG_VIRTUALBOX_MANAGER_WINDOW_NAME = "virtualboxManagerWindowName" - VIRTUAL_BOX_CONFIGURATION = os.getenv( "HOME" ) + "/.config/VirtualBox/VirtualBox.xml" VIRTUAL_MACHINE_STARTUP_COMMAND_DEFAULT = "VBoxManage startvm %VM%" # Data model columns used in the Preferences dialog. @@ -58,7 +58,7 @@ def __init__( self ): super().__init__( indicatorName = INDICATOR_NAME, - version = "1.0.68", + version = "1.0.69", copyrightStartYear = "2012", comments = _( "Shows VirtualBox™ virtual machines and allows them to be started." ) ) @@ -71,40 +71,40 @@ def update( self, menu ): - virtualMachines = self.getVirtualMachines() - - if self.autoStartRequired and self.isVBoxManageInstalled(): + if self.autoStartRequired: # Start VMs here so that the indicator icon is displayed immediately. self.autoStartRequired = False - self.autoStartVirtualMachines( virtualMachines ) - - if self.isVBoxManageInstalled(): - self.buildMenu( menu, virtualMachines ) + if self.isVBoxManageInstalled(): + self.autoStartVirtualMachines() - else: - menu.append( Gtk.MenuItem.new_with_label( _( "(VirtualBox™ is not installed)" ) ) ) + self.buildMenu( menu ) return int( 60 * self.refreshIntervalInMinutes ) - def buildMenu( self, menu, virtualMachines ): - if virtualMachines: - runningNames, runningUUIDs = self.getRunningVirtualMachines() - for item in virtualMachines: - if type( item ) == virtualmachine.Group: - self.addMenuItemForGroup( menu, item, 0, runningUUIDs ) + def buildMenu( self, menu ): + if self.isVBoxManageInstalled(): + virtualMachines = self.getVirtualMachines() + if virtualMachines: + runningNames, runningUUIDs = self.getRunningVirtualMachines() + for item in sorted( virtualMachines, key = lambda item: item.getName().lower() ): + if type( item ) == virtualmachine.Group: + self.addMenuItemForGroup( menu, item, 0, runningUUIDs ) - else: - self.addMenuItemForVirtualMachine( menu, item, 0, item.getUUID() in runningUUIDs ) + else: + self.addMenuItemForVirtualMachine( menu, item, 0, item.getUUID() in runningUUIDs ) - else: - menu.append( Gtk.MenuItem.new_with_label( _( "(no virtual machines exist)" ) ) ) + else: + menu.append( Gtk.MenuItem.new_with_label( _( "(no virtual machines exist)" ) ) ) - menu.append( Gtk.SeparatorMenuItem() ) + menu.append( Gtk.SeparatorMenuItem() ) - menuItem = Gtk.MenuItem.new_with_label( _( "Launch VirtualBox™ Manager" ) ) - menuItem.connect( "activate", self.onLaunchVirtualBoxManager ) - menu.append( menuItem ) - self.secondaryActivateTarget = menuItem + menuItem = Gtk.MenuItem.new_with_label( _( "Launch VirtualBox™ Manager" ) ) + menuItem.connect( "activate", self.onLaunchVirtualBoxManager ) + menu.append( menuItem ) + self.secondaryActivateTarget = menuItem + + else: + menu.append( Gtk.MenuItem.new_with_label( _( "(VirtualBox™ is not installed)" ) ) ) def addMenuItemForGroup( self, menu, group, level, runningUUIDs ): @@ -116,7 +116,7 @@ menu = Gtk.Menu() menuItem.set_submenu( menu ) - for item in group.getItems(): + for item in sorted( group.getItems(), key = lambda item: item.getName().lower() ): if type( item ) == virtualmachine.Group: self.addMenuItemForGroup( menu, item, level + 1, runningUUIDs ) @@ -133,46 +133,64 @@ else: menuItem = Gtk.MenuItem.new_with_label( indent + virtualMachine.getName() ) - menuItem.connect( "activate", self.startVirtualMachine, virtualMachine.getUUID() ) + menuItem.connect( "activate", self._onVirtualMachine, virtualMachine ) menu.append( menuItem ) - def autoStartVirtualMachines( self, virtualMachines ): + def _onVirtualMachine( self, widget, virtualMachine ): + if self.isVirtualMachineRunning( virtualMachine.getUUID() ): + self.bringWindowToFront( virtualMachine.getName() ) + self.requestUpdate( 1 ) + + else: + self.startVirtualMachine( virtualMachine.getUUID() ) + self.requestUpdate( 10 ) # Delay the refresh as the VM will have been started in the background and VBoxManage will not have had time to update. + + + def autoStartVirtualMachines( self ): + virtualMachinesForAutoStart = [ ] + self.__getVirtualMachinesForAutoStart( self.getVirtualMachines(), virtualMachinesForAutoStart ) + previousVirtualMachineWasAlreadyRunning = True + while len( virtualMachinesForAutoStart ) > 0: # Start up each virtual machine and only insert the time delay if a machine was not already running. + virtualMachine = virtualMachinesForAutoStart.pop() + if self.isVirtualMachineRunning( virtualMachine.getUUID() ): + self.bringWindowToFront( virtualMachine.getName() ) + previousVirtualMachineWasAlreadyRunning = True + + else: + if not previousVirtualMachineWasAlreadyRunning: + time.sleep( self.delayBetweenAutoStartInSeconds ) + + self.startVirtualMachine( virtualMachine.getUUID() ) + previousVirtualMachineWasAlreadyRunning = False + + + def __getVirtualMachinesForAutoStart( self, virtualMachines, virtualMachinesForAutoStart ): for item in virtualMachines: if type( item ) == virtualmachine.Group: - self.autoStartVirtualMachines( item.getItems() ) + self.__getVirtualMachinesForAutoStart( item.getItems(), virtualMachinesForAutoStart ) else: if self.isAutostart( item.getUUID() ): - time.sleep( self.delayBetweenAutoStartInSeconds ) - self.startVirtualMachine( None, item.getUUID(), False ) + virtualMachinesForAutoStart.append( item ) - def startVirtualMachine( self, menuItem, uuid, requiresUpdate = True ): - runningVMNames, runningVMUUIDs = self.getRunningVirtualMachines() - if uuid in runningVMUUIDs: - self.bringWindowToFront( runningVMNames[ runningVMUUIDs.index( uuid ) ] ) - if requiresUpdate: - self.requestUpdate() + def startVirtualMachine( self, uuid ): + result = self.processGet( "VBoxManage list vms | grep " + uuid ) + if result is None or uuid not in result: + message = _( "The virtual machine could not be found - perhaps it has been renamed or deleted. The list of virtual machines has been refreshed - please try again." ) + Notify.Notification.new( _( "Error" ), message, self.icon ).show() else: - result = self.processGet( "VBoxManage list vms | awk \'/" + uuid + "/ {print}\'" ) # Using grep returns non zero error codes which results in unwanted log file. - if result is None or uuid not in result: - message = _( "The virtual machine could not be found - perhaps it has been renamed or deleted. The list of virtual machines has been refreshed - please try again." ) - Notify.Notification.new( _( "Error" ), message, self.icon ).show() - - else: - self.processCall( self.getStartCommand( uuid ).replace( "%VM%", uuid ) + " &" ) - if requiresUpdate: - self.requestUpdate( 10 ) # Delay the refresh as the VM will have been started in the background and VBoxManage will not have had time to update. + self.processCall( self.getStartCommand( uuid ).replace( "%VM%", uuid ) + " &" ) - def bringWindowToFront( self, virtualMachineName ): - numberOfWindowsWithTheSameName = self.processGet( "wmctrl -l | awk \'/" + virtualMachineName + "/ {print}\' | wc -l" ).strip() # Using grep returns non zero error codes which results in unwanted log file. + def bringWindowToFront( self, virtualMachineName, delayInSeconds = 0 ): + numberOfWindowsWithTheSameName = self.processGet( 'wmctrl -l | grep "' + virtualMachineName + '" | wc -l' ).strip() if numberOfWindowsWithTheSameName == "0": message = _( "Unable to find the window for the virtual machine '{0}' - perhaps it is running as headless." ).format( virtualMachineName ) summary = _( "Warning" ) - self.sendNotificationWithDelay( summary, message ) + self.sendNotificationWithDelay( summary, message, delayInSeconds ) elif numberOfWindowsWithTheSameName == "1": for line in self.processGet( "wmctrl -l" ).splitlines(): @@ -182,37 +200,37 @@ break else: - message = _( "Unable to bring the virtual machine '{0}' to front as there is more than one window of the same name." ).format( virtualMachineName ) + message = _( "Unable to bring the virtual machine '{0}' to front as there is more than one window with overlapping names." ).format( virtualMachineName ) summary = _( "Warning" ) - self.sendNotificationWithDelay( summary, message ) + self.sendNotificationWithDelay( summary, message, delayInSeconds ) # Zealous mouse wheel scrolling can cause too many notifications, subsequently popping the graphics stack! # Prevent notifications from appearing until a set time has elapsed since the previous notification. - def sendNotificationWithDelay( self, summary, message ): - if( self.dateTimeOfLastNotification + datetime.timedelta( seconds = 10 ) < datetime.datetime.now() ): + def sendNotificationWithDelay( self, summary, message, delayInSeconds = 0 ): + if( self.dateTimeOfLastNotification + datetime.timedelta( seconds = delayInSeconds ) < datetime.datetime.now() ): Notify.Notification.new( summary, message, self.icon ).show() self.dateTimeOfLastNotification = datetime.datetime.now() - # It is assumed that VirtualBox is installed! def onMouseWheelScroll( self, indicator, delta, scrollDirection ): - runningNames, runningUUIDs = self.getRunningVirtualMachines() - if runningUUIDs: - if self.scrollUUID is None or self.scrollUUID not in runningUUIDs: - self.scrollUUID = runningUUIDs[ 0 ] - - if scrollDirection == Gdk.ScrollDirection.UP: - index = ( runningUUIDs.index( self.scrollUUID ) + 1 ) % len( runningUUIDs ) - self.scrollUUID = runningUUIDs[ index ] - self.scrollDirectionIsUp = True + if self.isVBoxManageInstalled(): + runningNames, runningUUIDs = self.getRunningVirtualMachines() + if runningUUIDs: + if self.scrollUUID is None or self.scrollUUID not in runningUUIDs: + self.scrollUUID = runningUUIDs[ 0 ] + + if scrollDirection == Gdk.ScrollDirection.UP: + index = ( runningUUIDs.index( self.scrollUUID ) + 1 ) % len( runningUUIDs ) + self.scrollUUID = runningUUIDs[ index ] + self.scrollDirectionIsUp = True - else: - index = ( runningUUIDs.index( self.scrollUUID ) - 1 ) % len( runningUUIDs ) - self.scrollUUID = runningUUIDs[ index ] - self.scrollDirectionIsUp = False + else: + index = ( runningUUIDs.index( self.scrollUUID ) - 1 ) % len( runningUUIDs ) + self.scrollUUID = runningUUIDs[ index ] + self.scrollDirectionIsUp = False - self.bringWindowToFront( runningNames[ runningUUIDs.index( self.scrollUUID ) ] ) + self.bringWindowToFront( runningNames[ runningUUIDs.index( self.scrollUUID ) ], 10 ) def onLaunchVirtualBoxManager( self, menuItem ): @@ -224,7 +242,7 @@ # because the executable might be a script which calls another executable. # So using processes to find the window kept failing. # Instead, now have the user type in the title of the window into the preferences and find the window by that. - result = self.processGet( "wmctrl -l | awk \'/" + self.virtualboxManagerWindowName + "/ {print}\'" ) # Using grep returns non zero error codes which results in unwanted log file. + result = self.processGet( "wmctrl -l | grep \"" + self.virtualboxManagerWindowName + "\"" ) windowID = None if result: windowID = result.split()[ 0 ] @@ -254,68 +272,44 @@ return names, uuids - # Returns a list of virtualmachine and group objects reflecting VMs and groups as found via VBoxManage and the configuration file. - def getVirtualMachines( self ): - virtualMachines = [ ] - if self.isVBoxManageInstalled(): - virtualMachinesFromVBoxManage = self.getVirtualMachinesFromVBoxManage() - if os.path.isfile( IndicatorVirtualBox.VIRTUAL_BOX_CONFIGURATION ): - try: - with open( IndicatorVirtualBox.VIRTUAL_BOX_CONFIGURATION, 'r' ) as f: - lines = f.readlines() + def isVirtualMachineRunning( self, uuid ): return self.processGet( "VBoxManage list runningvms | grep " + uuid ) is not None - # The VirtualBox configuration file contains lines detailing the groups (if any) and virtual machines. - # This gives the sort of virtual machines and the group to which they belong (if applicable). - # Groups may contain groups and/or virtual machines. - topGroup = virtualmachine.Group( "" ) # Create a dummy group to make it easier to parse the first line of the configuration file. - for line in lines: - if "GUI/GroupDefinitions/" in line: - parts = line.split( "\"" ) - groupPath = parts[ 1 ].split( '/' )[ 2 : ] - groupItems = topGroup.getItems() - for groupName in groupPath: # Traverse the paths to find the final group... - group = next( ( x for x in groupItems if type( x ) == virtualmachine.Group and x.getName() == groupName ), topGroup ) - groupItems = group.getItems() - - groupNamesAndUUIDs = parts[ 3 ].split( ',' ) - if groupNamesAndUUIDs[ 0 ] == "n=GLOBAL": # VritualBox 6 has added this to the config...not needed by the indicator. - del groupNamesAndUUIDs[ 0 ] - - for item in groupNamesAndUUIDs: - if item.startswith( "go=" ): - group.addItem( virtualmachine.Group( item.replace( "go=", "" ) ) ) - - else: - uuid = item.replace( "m=", "" ) - name = next( x for x in virtualMachinesFromVBoxManage if x.getUUID() == uuid ).getName() - group.addItem( virtualmachine.VirtualMachine( name, uuid ) ) - - virtualMachines = topGroup.getItems() # Return the items (groups and/or virtual machines) of the dummy top group. - - except Exception as e: - self.getLogging().exception( e ) - virtualMachines = [ ] - else: - virtualMachines = virtualMachinesFromVBoxManage - - return virtualMachines - - - # Returns a list of virtualmachine objects from calling VBoxManage. - # Contains no group information, nor sort order which is set by the user in the GUI. - # Safe to call without checking if VBoxManage is installed. - def getVirtualMachinesFromVBoxManage( self ): + def getVirtualMachines( self ): virtualMachines = [ ] - result = self.processGet( "VBoxManage list vms" ) - if result: # If a VM is corrupt/missing, VBoxManage can give back a spurious (None) result. - for line in result.splitlines(): - try: - nameAndUUID = line[ 1 : -1 ].split( "\" {" ) - virtualMachines.append( virtualmachine.VirtualMachine( nameAndUUID[ 0 ], nameAndUUID[ 1 ] ) ) - - except Exception: - pass # Sometimes VBoxManage emits a warning message along with the VM information. + try: + def addVirtualMachine( group, name, uuid, groups ): + for groupName in groups: + theGroup = next( ( x for x in group.getItems() if type( x ) == virtualmachine.Group and x.getName() == groupName ), None ) + if theGroup is None: + theGroup = virtualmachine.Group( groupName ) + group.addItem( theGroup ) + + group = theGroup + + group.addItem( virtualmachine.VirtualMachine( name, uuid ) ) + + + topGroup = virtualmachine.Group( "" ) # Only needed whilst parsing results from VBoxManage... + listVirtualMachines = self.processGet( "VBoxManage list vms --long" ) + for line in listVirtualMachines.splitlines(): + if line.startswith( "Name:" ): + name = line.split( "Name:" )[ 1 ].strip() + + elif line.startswith( "Groups:" ): + groups = line.split( '/' )[ 1 : ] + if groups[ 0 ] == '': + del groups[ 0 ] + + elif line.startswith( "UUID:" ): + uuid = line.split( "UUID:" )[ 1 ].strip() + addVirtualMachine( topGroup, name, uuid, groups ) + + virtualMachines = topGroup.getItems() + + except Exception as e: + self.getLogging().exception( e ) + virtualMachines = [ ] return virtualMachines @@ -361,7 +355,7 @@ # List of groups and virtual machines. treeStore = Gtk.TreeStore( str, str, str, str ) # Group or virtual machine name, autostart, start command, UUID. - groupsExist = addItemsToStore( None, self.getVirtualMachines() ) + groupsExist = addItemsToStore( None, self.getVirtualMachines() if self.isVBoxManageInstalled() else [ ] ) treeView = Gtk.TreeView.new_with_model( treeStore ) treeView.expand_all() @@ -509,12 +503,12 @@ startCommand.set_hexpand( True ) # Only need to set this once and all objects will expand. grid.attach( startCommand, 1, 0, 1, 1 ) - autostartCheckbox = Gtk.CheckButton.new_with_label( _( "Autostart" ) ) - autostartCheckbox.set_tooltip_text( _( "Run the virtual machine when the indicator starts." ) ) - autostartCheckbox.set_active( + autostartCheckbutton = Gtk.CheckButton.new_with_label( _( "Autostart" ) ) + autostartCheckbutton.set_tooltip_text( _( "Run the virtual machine when the indicator starts." ) ) + autostartCheckbutton.set_active( model[ treeiter ][ IndicatorVirtualBox.COLUMN_AUTOSTART ] is not None and model[ treeiter ][ IndicatorVirtualBox.COLUMN_AUTOSTART ] == Gtk.STOCK_APPLY ) - grid.attach( autostartCheckbox, 0, 1, 2, 1 ) + grid.attach( autostartCheckbutton, 0, 1, 2, 1 ) dialog = self.createDialog( tree, _( "Virtual Machine Properties" ), grid ) while True: @@ -533,7 +527,7 @@ startCommand.grab_focus() continue - model[ treeiter ][ IndicatorVirtualBox.COLUMN_AUTOSTART ] = Gtk.STOCK_APPLY if autostartCheckbox.get_active() else None + model[ treeiter ][ IndicatorVirtualBox.COLUMN_AUTOSTART ] = Gtk.STOCK_APPLY if autostartCheckbutton.get_active() else None model[ treeiter ][ IndicatorVirtualBox.COLUMN_START_COMMAND ] = startCommand.get_text().strip() break