Back to index

unity  6.0.0
launcher.py
Go to the documentation of this file.
00001 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
00002 # Copyright 2012 Canonical
00003 # Author: Thomi Richards
00004 #
00005 # This program is free software: you can redistribute it and/or modify it
00006 # under the terms of the GNU General Public License version 3, as published
00007 # by the Free Software Foundation.
00008 #
00009 
00010 from __future__ import absolute_import
00011 
00012 from autopilot.emulators.dbus_handler import session_bus
00013 from autopilot.emulators.X11 import Mouse, ScreenGeometry
00014 from autopilot.keybindings import KeybindingsHelper
00015 from autopilot.utilities import get_compiz_option
00016 import dbus
00017 import logging
00018 from math import floor
00019 from testtools.matchers import NotEquals
00020 from time import sleep
00021 
00022 from unity.emulators import UnityIntrospectionObject
00023 from unity.emulators.icons import BFBLauncherIcon, BamfLauncherIcon, SimpleLauncherIcon
00024 
00025 logger = logging.getLogger(__name__)
00026 
00027 
00028 class IconDragType:
00029     """Define possible positions to drag an icon onto another"""
00030     INSIDE = 0
00031     OUTSIDE = 1
00032 
00033 
00034 class LauncherController(UnityIntrospectionObject):
00035     """The LauncherController class."""
00036 
00037     def get_launcher_for_monitor(self, monitor_num):
00038         """Return an instance of Launcher for the specified monitor, or None."""
00039         launchers = self.get_children_by_type(Launcher, monitor=monitor_num)
00040         return launchers[0] if launchers else None
00041 
00042     def get_launchers(self):
00043         """Return the available launchers, or None."""
00044         return self.get_children_by_type(Launcher)
00045 
00046     @property
00047     def model(self):
00048         """Return the launcher model."""
00049         models = LauncherModel.get_all_instances()
00050         assert(len(models) == 1)
00051         return models[0]
00052 
00053     def add_launcher_item_from_position(self,name,icon,icon_x,icon_y,icon_size,desktop_file,aptdaemon_task):
00054         """ Emulate a DBus call from Software Center to pin an icon to the launcher """
00055         launcher_object = session_bus.get_object('com.canonical.Unity.Launcher',
00056                                       '/com/canonical/Unity/Launcher')
00057         launcher_iface = dbus.Interface(launcher_object, 'com.canonical.Unity.Launcher')
00058         launcher_iface.AddLauncherItemFromPosition(name,
00059                                                    icon,
00060                                                    icon_x,
00061                                                    icon_y,
00062                                                    icon_size,
00063                                                    desktop_file,
00064                                                    aptdaemon_task)
00065 
00066 
00067 class Launcher(UnityIntrospectionObject, KeybindingsHelper):
00068     """An individual launcher for a monitor."""
00069 
00070     def __init__(self, *args, **kwargs):
00071         super(Launcher, self).__init__(*args, **kwargs)
00072 
00073         self.show_timeout = 1
00074         self.hide_timeout = 1
00075         self.in_keynav_mode = False
00076         self.in_switcher_mode = False
00077 
00078         self._mouse = Mouse()
00079         self._screen = ScreenGeometry()
00080 
00081     def _perform_key_nav_binding(self, keybinding):
00082         if not self.in_keynav_mode:
00083                 raise RuntimeError("Cannot perform key navigation when not in kaynav mode.")
00084         self.keybinding(keybinding)
00085 
00086     def _perform_key_nav_exit_binding(self, keybinding):
00087         self._perform_key_nav_binding(keybinding)
00088         self.in_keynav_mode = False
00089 
00090     def _perform_switcher_binding(self, keybinding):
00091         if not self.in_switcher_mode:
00092             raise RuntimeError("Cannot interact with launcher switcher when not in switcher mode.")
00093         self.keybinding(keybinding)
00094 
00095     def _perform_switcher_exit_binding(self, keybinding):
00096         # If we're doing a normal activation, all we need to do is release the
00097         # keybinding. Otherwise, perform the keybinding specified *then* release
00098         # the switcher keybinding.
00099         if keybinding != "launcher/switcher":
00100             self._perform_switcher_binding(keybinding)
00101         self.keybinding_release("launcher/switcher")
00102         self.in_switcher_mode = False
00103 
00104     def _get_controller(self):
00105         """Get the launcher controller."""
00106         [controller] = LauncherController.get_all_instances()
00107         return controller
00108 
00109     def move_mouse_to_right_of_launcher(self):
00110         """Places the mouse to the right of this launcher."""
00111         self._screen.move_mouse_to_monitor(self.monitor)
00112         (x, y, w, h) = self.geometry
00113         target_x = x + w + 10
00114         target_y = y + h / 2
00115 
00116         logger.debug("Moving mouse away from launcher.")
00117         self._mouse.move(target_x, target_y, False)
00118         sleep(self.show_timeout)
00119 
00120     def move_mouse_over_launcher(self):
00121         """Move the mouse over this launcher."""
00122         self._screen.move_mouse_to_monitor(self.monitor)
00123         (x, y, w, h) = self.geometry
00124         target_x = x + w / 2
00125         target_y = y + h / 2
00126 
00127         logger.debug("Moving mouse to center of launcher.")
00128         self._mouse.move(target_x, target_y)
00129 
00130     def move_mouse_to_icon(self, icon):
00131         # The icon may be off the bottom of screen, so we do this in a loop:
00132         while 1:
00133             target_x = icon.center_x + self.x
00134             target_y = icon.center_y
00135             if self._mouse.x == target_x and self._mouse.y == target_y:
00136                 break
00137             self._mouse.move(target_x, target_y)
00138             sleep(0.5)
00139 
00140     def mouse_reveal_launcher(self):
00141         """Reveal this launcher with the mouse.
00142 
00143         If the launcher is already visible calling this method does nothing.
00144         """
00145         if self.is_showing:
00146             return
00147         self._screen.move_mouse_to_monitor(self.monitor)
00148         (x, y, w, h) = self.geometry
00149 
00150         target_x = x - 920 # this is the pressure we need to reveal the launcher.
00151         target_y = y + h / 2
00152         logger.debug("Revealing launcher on monitor %d with mouse.", self.monitor)
00153         self._mouse.move(target_x, target_y, True, 5, .002)
00154         sleep(self.show_timeout)
00155 
00156     def keyboard_reveal_launcher(self):
00157         """Reveal this launcher using the keyboard."""
00158         self._screen.move_mouse_to_monitor(self.monitor)
00159         logger.debug("Revealing launcher with keyboard.")
00160         self.keybinding_hold("launcher/reveal")
00161         self.is_showing.wait_for(True)
00162 
00163     def keyboard_unreveal_launcher(self):
00164         """Un-reveal this launcher using the keyboard."""
00165         self._screen.move_mouse_to_monitor(self.monitor)
00166         logger.debug("Un-revealing launcher with keyboard.")
00167         self.keybinding_release("launcher/reveal")
00168         # only wait if the launcher is set to autohide
00169         if self.hidemode == 1:
00170             self.is_showing.wait_for(False)
00171 
00172     def keyboard_select_icon(self, **kwargs):
00173         """Using either keynav mode or the switcher, select an icon in the launcher.
00174 
00175         The desired mode (keynav or switcher) must be started already before
00176         calling this methods or a RuntimeError will be raised.
00177 
00178         This method won't activate the icon, it will only select it.
00179 
00180         Icons are selected by passing keyword argument filters to this method.
00181         For example:
00182 
00183         >>> launcher.keyboard_select_icon(tooltip_text="Calculator")
00184 
00185         ...will select the *first* icon that has a 'tooltip_text' attribute equal
00186         to 'Calculator'. If an icon is missing the attribute, it is treated as
00187         not matching.
00188 
00189         If no icon is found, this method will raise a ValueError.
00190 
00191         """
00192 
00193         if not self.in_keynav_mode and not self.in_switcher_mode:
00194             raise RuntimeError("Launcher must be in keynav or switcher mode")
00195 
00196         [launcher_model] = LauncherModel.get_all_instances()
00197         all_icons = launcher_model.get_launcher_icons()
00198         logger.debug("all_icons = %r", [i.tooltip_text for i in all_icons])
00199         for icon in all_icons:
00200             # can't iterate over the model icons directly since some are hidden
00201             # from the user.
00202             if not icon.visible:
00203                 continue
00204             logger.debug("Selected icon = %s", icon.tooltip_text)
00205             matches = True
00206             for arg,val in kwargs.iteritems():
00207                 if not hasattr(icon, arg) or getattr(icon, arg, None) != val:
00208                     matches = False
00209                     break
00210             if matches:
00211                 return
00212             if self.in_keynav_mode:
00213                 self.key_nav_next()
00214             elif self.in_switcher_mode:
00215                 self.switcher_next()
00216         raise ValueError("No icon found that matches: %r", kwargs)
00217 
00218     def key_nav_start(self):
00219         """Start keyboard navigation mode by pressing Alt+F1."""
00220         self._screen.move_mouse_to_monitor(self.monitor)
00221         logger.debug("Initiating launcher keyboard navigation with Alt+F1.")
00222         self.keybinding("launcher/keynav")
00223         self._get_controller().key_nav_is_active.wait_for(True)
00224         self.in_keynav_mode = True
00225 
00226     def key_nav_cancel(self):
00227         """End the key navigation."""
00228         logger.debug("Cancelling keyboard navigation mode.")
00229         self._perform_key_nav_exit_binding("launcher/keynav/exit")
00230         self._get_controller().key_nav_is_active.wait_for(False)
00231 
00232     def key_nav_activate(self):
00233         """Activates the selected launcher icon. In the current implementation
00234         this also exits key navigation"""
00235         logger.debug("Ending keyboard navigation mode, activating icon.")
00236         self._perform_key_nav_exit_binding("launcher/keynav/activate")
00237         self._get_controller().key_nav_is_active.wait_for(False)
00238 
00239     def key_nav_next(self):
00240         """Moves the launcher keynav focus to the next launcher icon"""
00241         logger.debug("Selecting next item in keyboard navigation mode.")
00242         old_selection = self._get_controller().key_nav_selection
00243         self._perform_key_nav_binding("launcher/keynav/next")
00244         self._get_controller().key_nav_selection.wait_for(NotEquals(old_selection))
00245 
00246     def key_nav_prev(self):
00247         """Moves the launcher keynav focus to the previous launcher icon"""
00248         logger.debug("Selecting previous item in keyboard navigation mode.")
00249         old_selection = self._get_controller().key_nav_selection
00250         self._perform_key_nav_binding("launcher/keynav/prev")
00251         self._get_controller().key_nav_selection.wait_for(NotEquals(old_selection))
00252 
00253     def key_nav_enter_quicklist(self):
00254         logger.debug("Opening quicklist for currently selected icon.")
00255         self._perform_key_nav_binding("launcher/keynav/open-quicklist")
00256         self.quicklist_open.wait_for(True)
00257 
00258     def key_nav_exit_quicklist(self):
00259         logger.debug("Closing quicklist for currently selected icon.")
00260         self._perform_key_nav_binding("launcher/keynav/close-quicklist")
00261         self.quicklist_open.wait_for(False)
00262 
00263     def switcher_start(self):
00264         """Start the super+Tab switcher on this launcher."""
00265         self._screen.move_mouse_to_monitor(self.monitor)
00266         logger.debug("Starting Super+Tab switcher.")
00267         self.keybinding_hold_part_then_tap("launcher/switcher")
00268         self._get_controller().key_nav_is_active.wait_for(True)
00269         self.in_switcher_mode = True
00270 
00271     def switcher_cancel(self):
00272         """End the super+tab swithcer."""
00273         logger.debug("Cancelling keyboard navigation mode.")
00274         self._perform_switcher_exit_binding("launcher/switcher/exit")
00275         self._get_controller().key_nav_is_active.wait_for(False)
00276 
00277     def switcher_activate(self):
00278         """Activates the selected launcher icon. In the current implementation
00279         this also exits the switcher"""
00280         logger.debug("Ending keyboard navigation mode.")
00281         self._perform_switcher_exit_binding("launcher/switcher")
00282         self._get_controller().key_nav_is_active.wait_for(False)
00283 
00284     def switcher_next(self):
00285         logger.debug("Selecting next item in keyboard navigation mode.")
00286         old_selection = self._get_controller().key_nav_selection
00287         self._perform_switcher_binding("launcher/switcher/next")
00288         self._get_controller().key_nav_selection.wait_for(NotEquals(old_selection))
00289 
00290     def switcher_prev(self):
00291         logger.debug("Selecting previous item in keyboard navigation mode.")
00292         old_selection = self._get_controller().key_nav_selection
00293         self._perform_switcher_binding("launcher/switcher/prev")
00294         self._get_controller().key_nav_selection.wait_for(NotEquals(old_selection))
00295 
00296     def switcher_up(self):
00297         logger.debug("Selecting next item in keyboard navigation mode.")
00298         old_selection = self._get_controller().key_nav_selection
00299         self._perform_switcher_binding("launcher/switcher/up")
00300         self._get_controller().key_nav_selection.wait_for(NotEquals(old_selection))
00301 
00302     def switcher_down(self):
00303         logger.debug("Selecting previous item in keyboard navigation mode.")
00304         old_selection = self._get_controller().key_nav_selection
00305         self._perform_switcher_binding("launcher/switcher/down")
00306         self._get_controller().key_nav_selection.wait_for(NotEquals(old_selection))
00307 
00308     def click_launcher_icon(self, icon, button=1):
00309         """Move the mouse over the launcher icon, and click it.
00310         `icon` must be an instance of SimpleLauncherIcon or it's descendants.
00311         """
00312         if not isinstance(icon, SimpleLauncherIcon):
00313             raise TypeError("icon must be a LauncherIcon, not %s" % type(icon))
00314 
00315         logger.debug("Clicking launcher icon %r on monitor %d with mouse button %d",
00316             icon, self.monitor, button)
00317         self.mouse_reveal_launcher()
00318 
00319         # The icon may be off the screen, so we do this in a loop:
00320         while 1:
00321             target_x = icon.center_x + self.x
00322             target_y = icon.center_y
00323             if self._mouse.x == target_x and self._mouse.y == target_y:
00324                 break
00325             self._mouse.move(target_x, target_y )
00326             sleep(1)
00327         self._mouse.click(button)
00328         self.move_mouse_to_right_of_launcher()
00329 
00330     def drag_icon_to_position(self, icon, pos, drag_type=IconDragType.INSIDE):
00331         """Place the supplied icon above the icon in the position pos.
00332 
00333         The icon is dragged inside or outside the launcher.
00334 
00335         >>> drag_icon_to_position(calc_icon, 0, IconDragType.INSIDE)
00336 
00337         This will drag the calculator icon above the bfb (but as you can't go
00338         above the bfb it will move below it (to position 1))
00339 
00340         """
00341         if not isinstance(icon, BamfLauncherIcon):
00342             raise TypeError("icon must be a LauncherIcon")
00343 
00344         [launcher_model] = LauncherModel.get_all_instances()
00345         all_icons = launcher_model.get_launcher_icons()
00346         all_icon_len = len(all_icons)
00347         if pos >= all_icon_len:
00348             raise ValueError("pos is outside valid range (0-%d)" % all_icon_len)
00349 
00350         logger.debug("Dragging launcher icon %r on monitor %d to position %s"
00351                      % (icon, self.monitor, pos))
00352         self.mouse_reveal_launcher()
00353 
00354         icon_height = get_compiz_option("unityshell", "icon_size")
00355 
00356         target_icon = all_icons[pos]
00357         if target_icon.id == icon.id:
00358             logger.warning("%s is already the icon in position %d. Nothing to do." % (icon, pos))
00359             return
00360 
00361         self.move_mouse_to_icon(icon)
00362         self._mouse.press()
00363         sleep(2)
00364 
00365         if drag_type == IconDragType.OUTSIDE:
00366             shift_over = self._mouse.x + (icon_height * 2)
00367             self._mouse.move(shift_over, self._mouse.y)
00368             sleep(0.5)
00369 
00370         # find the target drop position, between the center & top of the target icon
00371         target_y = target_icon.center_y - floor(icon_height / 4)
00372 
00373         # Need to move the icons top (if moving up) or bottom (if moving
00374         # downward) to the target position
00375         moving_up = True if icon.center_y > target_icon.center_y else False
00376         icon_half_height = floor(icon_height / 2)
00377         fudge_factor = 5
00378         if moving_up or drag_type == IconDragType.OUTSIDE:
00379             target_y += icon_half_height + fudge_factor
00380         else:
00381             target_y -= icon_half_height - fudge_factor
00382 
00383         self._mouse.move(self._mouse.x, target_y, rate=20,
00384                          time_between_events=0.05)
00385         sleep(1)
00386 
00387         self._mouse.release()
00388         self.move_mouse_to_right_of_launcher()
00389 
00390     def lock_to_launcher(self, icon):
00391         """lock 'icon' to the launcher, if it's not already.
00392         `icon` must be an instance of BamfLauncherIcon.
00393         """
00394         if not isinstance(icon, BamfLauncherIcon):
00395             raise TypeError("Can only lock instances of BamfLauncherIcon")
00396         if icon.sticky:
00397             # Nothing to do.
00398             return
00399 
00400         logger.debug("Locking icon %r to launcher.", icon)
00401         self.click_launcher_icon(icon, button=3)
00402         quicklist = icon.get_quicklist()
00403         pin_item = quicklist.get_quicklist_item_by_text('Lock to Launcher')
00404         quicklist.click_item(pin_item)
00405 
00406     def unlock_from_launcher(self, icon):
00407         """lock 'icon' to the launcher, if it's not already.
00408 
00409         `icon` must be an instance of BamfLauncherIcon.
00410 
00411         """
00412         if not isinstance(icon, BamfLauncherIcon):
00413             raise TypeError("Can only unlock instances of BamfLauncherIcon")
00414         if not icon.sticky:
00415             # nothing to do.
00416             return
00417 
00418         logger.debug("Unlocking icon %r from launcher.")
00419         self.click_launcher_icon(icon, button=3)
00420         quicklist = icon.get_quicklist()
00421         pin_item = quicklist.get_quicklist_item_by_text('Unlock from Launcher')
00422         quicklist.click_item(pin_item)
00423 
00424     @property
00425     def geometry(self):
00426         """Returns a tuple of (x,y,w,h) for the current launcher."""
00427         return (self.x, self.y, self.width, self.height)
00428 
00429 
00430 class LauncherModel(UnityIntrospectionObject):
00431     """THe launcher model. Contains all launcher icons as children."""
00432 
00433     def get_bfb_icon(self):
00434         icons = BFBLauncherIcon.get_all_instances()
00435         assert(len(icons) == 1)
00436         return icons[0]
00437 
00438     def get_launcher_icons(self, visible_only=True):
00439         """Get a list of launcher icons in this launcher."""
00440         if visible_only:
00441             return self.get_children_by_type(SimpleLauncherIcon, visible=True)
00442         else:
00443             return self.get_children_by_type(SimpleLauncherIcon)
00444 
00445     def get_bamf_launcher_icons(self, visible_only=True):
00446         """Get a list of bamf launcher icons in this launcher."""
00447         if visible_only:
00448             return self.get_children_by_type(BamfLauncherIcon, visible=True)
00449         else:
00450             return self.get_children_by_type(BamfLauncherIcon)
00451 
00452     def get_launcher_icons_for_monitor(self, monitor, visible_only=True):
00453         """Get a list of launcher icons for provided monitor."""
00454         icons = []
00455         for icon in self.get_launcher_icons(visible_only):
00456             if icon.is_on_monitor(monitor):
00457                 icons.append(icon)
00458 
00459         return icons
00460 
00461     def get_icon_by_tooltip_text(self, tooltip_text):
00462         """Get a launcher icon given it's tooltip text.
00463 
00464         Returns None if there is no icon with the specified text.
00465         """
00466         for icon in self.get_launcher_icons():
00467             if icon.tooltip_text == tooltip_text:
00468                 return icon
00469         return None
00470 
00471     def get_icon_by_desktop_id(self, desktop_id):
00472         """Gets a launcher icon with the specified desktop id.
00473 
00474         Returns None if there is no such launcher icon.
00475         """
00476         icons = self.get_children_by_type(SimpleLauncherIcon, desktop_id=desktop_id)
00477         if len(icons):
00478             return icons[0]
00479 
00480         return None
00481 
00482     def get_icon_by_window_xid(self, xid):
00483         """Gets a launcher icon that controls the specified window xid."""
00484         icons = [i for i in self.get_children_by_type(SimpleLauncherIcon) if i.xids.contains(xid)]
00485         if (len(icons)):
00486             return icons[0]
00487 
00488         return None
00489 
00490     def get_icons_by_filter(self, **kwargs):
00491         """Get a list of icons that satisfy the given filters.
00492 
00493         For example:
00494 
00495         >>> get_icons_by_filter(tooltip_text="My Application")
00496         ... [...]
00497 
00498         Returns an empty list if no icons matched the filter.
00499 
00500         """
00501         return self.get_children_by_type(SimpleLauncherIcon, **kwargs)
00502 
00503     def num_launcher_icons(self):
00504         """Get the number of icons in the launcher model."""
00505         return len(self.get_launcher_icons())
00506 
00507     def num_bamf_launcher_icons(self, visible_only=True):
00508         """Get the number of bamf icons in the launcher model."""
00509         return len(self.get_bamf_launcher_icons(visible_only))