Hello community, here is the log from the commit of package mate-applet-dock for openSUSE:Factory checked in at 2015-11-24 22:33:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mate-applet-dock (Old) and /work/SRC/openSUSE:Factory/.mate-applet-dock.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mate-applet-dock" Changes: -------- --- /work/SRC/openSUSE:Factory/mate-applet-dock/mate-applet-dock.changes 2015-10-24 10:30:20.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.mate-applet-dock.new/mate-applet-dock.changes 2015-11-24 22:34:26.000000000 +0100 @@ -1,0 +2,8 @@ +Mon Nov 23 20:34:07 UTC 2015 - [email protected] + +- Update to 0.65: + * Dock icons now blink when an application needs attention. + * Change to window activation code for applet to work with + MATE 1.12. + +------------------------------------------------------------------- Old: ---- dock-applet-0.64.tar.gz New: ---- dock-applet-0.65.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mate-applet-dock.spec ++++++ --- /var/tmp/diff_new_pack.1hkWpq/_old 2015-11-24 22:34:27.000000000 +0100 +++ /var/tmp/diff_new_pack.1hkWpq/_new 2015-11-24 22:34:27.000000000 +0100 @@ -18,7 +18,7 @@ %define _name dock-applet Name: mate-applet-dock -Version: 0.64 +Version: 0.65 Release: 0 Summary: Dock applet for the MATE panel License: GPL-2.0+ @@ -52,7 +52,6 @@ * Use either a light or dark indicator that it can always be seen no matter what colour the panel is. - %prep %setup -q -n %{_name}-%{version} if [ -L COPYING ]; then ++++++ dock-applet-0.64.tar.gz -> dock-applet-0.65.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dock-applet-0.64/ChangeLog new/dock-applet-0.65/ChangeLog --- old/dock-applet-0.64/ChangeLog 2015-10-15 11:32:08.000000000 +0200 +++ new/dock-applet-0.65/ChangeLog 2015-11-15 11:41:17.000000000 +0100 @@ -104,3 +104,6 @@ App icons can now be sourced from ~/.local/share/icons (e.g popcorn-time puts its icon here) +V0.65 Dock icons now blink when an app needs attention + Change to window activation code so that the applet works + with MATE 1.12 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dock-applet-0.64/configure.ac new/dock-applet-0.65/configure.ac --- old/dock-applet-0.64/configure.ac 2015-10-15 11:32:08.000000000 +0200 +++ new/dock-applet-0.65/configure.ac 2015-11-15 11:41:17.000000000 +0100 @@ -1,4 +1,4 @@ -AC_INIT([Dock Applet], [0.64]) +AC_INIT([Dock Applet], [0.65]) AM_INIT_AUTOMAKE AM_PATH_PYTHON([3.0]) Files old/dock-applet-0.64/dock-applet-0.64.tar.gz and new/dock-applet-0.65/dock-applet-0.64.tar.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dock-applet-0.64/src/dock.in new/dock-applet-0.65/src/dock.in --- old/dock-applet-0.64/src/dock.in 2015-10-15 11:32:08.000000000 +0200 +++ new/dock-applet-0.65/src/dock.in 2015-11-15 11:41:17.000000000 +0100 @@ -1529,6 +1529,14 @@ but first, hide any app window list that is being shown and stop the window list timer + Note: As of MATE 1.12 wnck_window.activate does not seem to work properly + if we specify our own event time. However, if an event time of + 0 (i.e. now) is specfied all works as expected. However, when the + applet is run from the command line, lots of these messages + 'Wnck-WARNING **: Received a timestamp of 0; window activation + may not function properly' appear, so another solution may need + to be found in the future + Args: app: the docked app whose windows are to be minimized or restored event : the mouse click event @@ -1559,7 +1567,7 @@ (win_type == Wnck.WindowType.DIALOG)) and \ (not window.is_skip_tasklist()) and \ (window != last_active_win): - window.activate(event.time) + window.activate(0) sleep(0.01) # allow the window manager time to activate # the window @@ -1571,13 +1579,13 @@ wnck_ws = last_active_win.get_workspace() if wnck_aws != wnck_ws: - wnck_ws.activate(event.time) + wnck_ws.activate(0) sleep(0.01) # rarely, the last active win does not end up as the active window if we # activate here, so instead a workaround which seems to do the trick # is use a timer as below - GObject.timeout_add(20, win_activation_timer, [last_active_win, event.time+20]) + GObject.timeout_add(20, win_activation_timer, [last_active_win, event.time]) else: #minimize all windows and do the last active window last of all @@ -1603,14 +1611,16 @@ will activate a specified wnck window Args: - args - a tuple containing two items + args - a tuple containing these items args[0] - the wnck window to activate - args[1] - the event time we want to specfiy + args[1] - the event time at which the timer was activated Returns: False - to cancel the timer """ - args[0].activate(args[1]) + # as of MATE 1.12 it seems we need to use an event time of 0 (i.e. now) to the window to + # activate properly + args[0].activate(0) sleep(0.01) return (False) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dock-applet-0.64/src/dock_applet.in new/dock-applet-0.65/src/dock_applet.in --- old/dock-applet-0.64/src/dock_applet.in 2015-10-15 11:32:08.000000000 +0200 +++ new/dock-applet-0.65/src/dock_applet.in 2015-11-15 11:41:17.000000000 +0100 @@ -330,7 +330,7 @@ | Gdk.EventMask.KEY_RELEASE_MASK \ | Gdk.EventMask.SCROLL_MASK \ | Gdk.EventMask.STRUCTURE_MASK) - + the_dock = dock.Dock(applet) the_dock.setup_dock() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dock-applet-0.64/src/dock_win_list.in new/dock-applet-0.65/src/dock_win_list.in --- old/dock-applet-0.64/src/dock_win_list.in 2015-10-15 11:32:08.000000000 +0200 +++ new/dock-applet-0.65/src/dock_win_list.in 2015-11-15 11:41:17.000000000 +0100 @@ -312,7 +312,7 @@ return True self.hide() - win.activate(event.time) + win.activate(0) return True def do_timer(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dock-applet-0.64/src/docked_app.in new/dock-applet-0.65/src/docked_app.in --- old/dock-applet-0.64/src/docked_app.in 2015-10-15 11:32:08.000000000 +0200 +++ new/dock-applet-0.65/src/docked_app.in 2015-11-15 11:41:17.000000000 +0100 @@ -45,7 +45,6 @@ import gi gi.require_version("Gtk", "2.0") -gi.require_version("MatePanelApplet", "4.0") from gi.repository import Gtk from gi.repository import MatePanelApplet from gi.repository import Gdk @@ -55,7 +54,7 @@ #from gi.repository import GdkPixbuf from gi.repository import Gio -from gi.repository import GLib +#from gi.repository import GLib from gi.repository import GObject import cairo import math @@ -63,9 +62,6 @@ import os import os.path import subprocess -import re -from urllib.parse import urljoin -from urllib.request import pathname2url from collections import namedtuple @@ -75,7 +71,7 @@ ColorTup = namedtuple('ColorTup', ['r', 'g', 'b']) AppInfoTup = namedtuple('AppInfoTup', ['app', 'pid', 'windows']) -ActionTup = namedtuple('ActionTup', ['disp_name', 'command', 'act_name', 'is_action']) +ActionTup = namedtuple('ActionTup', ['name', 'command']) def im_get_comp_color(filename): """Find the complimentary colour of the average colour of an image. @@ -225,6 +221,56 @@ return True +CONST_FLASH_DELAY = 330 + +class FlashTimer(object): + """Class to help provide visual feedback when an app requries user attention. + + Instantiates a timer which periodically causes the app's dock icon to flash + on and off until the app no longer needs attention + + Attributes: + app = the DockedApp object which we want to flashh + timer_id = the id of the timer that is instantiated + + """ + + def __init__(self, app): + """Init for the FlashTimer class. + + Sets everything up by creating the timer, setting a reference to the DockedApp and + setting the inital flash state to off + + Arguments: + app : the DockedApp object + """ + + self.app = app + self.app.is_flashing = True + self.app.flash_on = False + self.timer_id = GObject.timeout_add(CONST_FLASH_DELAY, self.do_timer) + + # make the app redraw itself + app.queue_draw() + + def do_timer(self): + """The timer function. + + If the app no longer needs attention, stop it flashing and delete + the timer. Otherwise, invert the flash. + + Finally,Redraw the app's icon + """ + + if self.app.is_flashing == True: + self.app.flash_on = not self.app.flash_on + else: + GObject.source_remove(self.timer_id) + + self.app.queue_draw() + + return True + class DockedApp(object): """Provide a docked app class @@ -244,7 +290,7 @@ applet_win : the Gdk.Window of the panel applet applet_orient : the applet orientation - drawing_area: Gtk.Label- provides a surface on which the app icon can bre drawn + drawing_area: Gtk.Label- provides a surface on which the app icon can be drawn drawing_area_size : the size in pixels (height AND width) that we have to draw in is_pulsing : boolean - True if the app is pulsing pulse_step : a count of how far we are through the pulse animation @@ -287,13 +333,15 @@ # this class and instead have to be done by the applet itself self.drawing_area = Gtk.Label() - self.drawing_area.set_app_paintable(True) self.drawing_area_size = 0 self.is_pulsing = False self.pulse_step = 0 + self.is_flashing = False + self.flash_on = False + self.app_pb = None self.highlight_color = ColorTup(r=0.0, g=0.0, b=0.0) @@ -345,6 +393,7 @@ # get the currently open windows for win in wnck_app.get_windows(): + win.connect("state-changed", self.win_state_changed) win_class = win.get_class_group() win_class_name = win_class.get_res_class() @@ -368,7 +417,6 @@ return False # now that we have the .desktop file, we can get the icon etc - return self.read_info_from_desktop_file() def set_app_name(self, app_name): @@ -502,14 +550,13 @@ In addition to <self.wm_class_name>.desktop looks for the following variations of name for the desktop flie: + lowercase<self.wm_class_name>.desktop + uppercase<self.wm_class_name>.desktop <self.app_name>.desktop lowercase<self.app_name>.desktop - camel cased<self.app_name>.desktop <self.app_name> with space characters replaced by '-'.desktop lowercase<self.app_name> with space characters replaced by "-",desktop <self.wm_class_name>.desktop - lowercase<self.wm_class_name>.desktop - uppercase<self.wm_class_name>.desktop If the destop file is found, self.desktop_file is set accordingly, otherwise self.desktop_file is set to None @@ -524,44 +571,37 @@ for the_dir, dir_list, file_list in os.walk(srch_dir): - dfname = the_dir + self.app_name + ".desktop" - if os.path.isfile(dfname): - self.desktop_file = dfname - return True - - dfname = the_dir + self.app_name.lower() + ".desktop" + dfname = the_dir + self.wm_class_name + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True - # camel case - dfname = the_dir + re.sub(r"\w+", lambda m:m.group(0).capitalize(), self.app_name) \ - + ".desktop" + dfname = the_dir + self.wm_class_name.lower() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True - dfname = the_dir + self.app_name.replace(" ", "-") + ".desktop" + dfname = the_dir + self.wm_class_name.upper() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True - dfname = the_dir + self.app_name.replace(" ", "-").lower() + ".desktop" + dfname = the_dir + self.app_name + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True - dfname = the_dir + self.wm_class_name + ".desktop" + dfname = the_dir + self.app_name.lower() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True - dfname = the_dir + self.wm_class_name.lower() + ".desktop" + dfname = the_dir + self.app_name.replace(" ", "-") + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True - dfname = the_dir + self.wm_class_name.upper() + ".desktop" + dfname = the_dir + self.app_name.replace(" ", "-").lower() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True @@ -582,10 +622,6 @@ the Exec field is found within self.cmd_line the StartupWMClass is the same as self.wm_class_name - Note: if StartupWMClass is present in a .desktop file and it - does not match the app's wm_class, the .desktop file is not - a match, no matter what the values of the name and exec fields are - If a match is found, self.desktop_file is set accordingly Note - the app must be running, and self.pid, self.cmd_line and @@ -654,26 +690,21 @@ # .desktop file the Exec field is '/usr/bin/software-center' whilst # it's command line while running is 'usr/bin/python /usr/bin/software-center' - # check the wm_classes - wm_class_mismatch = False + # check that the wm_classes + wm_class_found = False de_wm_class = the_de.getStartupWMClass() - if ((de_wm_class is not None) and (de_wm_class != "")): - if ((self.wm_class_name is not None) and (self.wm_class_name != "")) and \ - (de_wm_class.lower() == self.wm_class_name.lower()): - - # this is all we need to match, so.... - self.desktop_file = os.path.join(the_dir, the_file) - return True - else: - wm_class_mismatch = True - - # we now check the name and executable, as long as there was no - # mismatch between wm_classes - if wm_class_mismatch == False: - if (name_part_found == True) or \ - (exec_found == True): - self.desktop_file = os.path.join(the_dir, the_file) - return True + if ((de_wm_class is not None) and (de_wm_class != "")) and \ + ((self.wm_class_name is not None) and (self.wm_class_name != "")) and \ + (de_wm_class.lower() == self.wm_class_name.lower()): + wm_class_found = True + + + # Note: the app name is not a guaranteed match since wnck is + if (name_part_found == True) or \ + (exec_found == True) or \ + (wm_class_found == True): + self.desktop_file = os.path.join(the_dir, the_file) + return True self.desktop_file = None return False @@ -822,9 +853,7 @@ # now read the right click actions the app supports. These can be specified in # two ways - by a key named 'X-Ayatana-Desktop-Shortcuts' or by an Actions key - # - # Note: we differentiate between actions and shortcuts, because actions can - # be run via Gio.DesktopAppInfo whereas shortcuts we need to run ourselves + # xads = dfile.get('X-Ayatana-Desktop-Shortcuts') if (xads is not None) and (xads != ""): #the shortcut ids are in the the form of a semi-colon separated string @@ -832,17 +861,16 @@ rcname = dfile.get("Name", group = "%s Shortcut Group" %rca) rcexec = dfile.get("Exec", group = "%s Shortcut Group" %rca) if (rcname != "") and (rcexec !=""): - new_action = ActionTup(disp_name = rcname, command = rcexec, - act_name = rca, is_action = False) + new_action = ActionTup(name=rcname, command= rcexec) self.rc_actions.append(new_action) for action in dfile.getActions(): rcname = dfile.get("Name", group ="Desktop Action %s" %action) rcexec = dfile.get("Exec", group = "Desktop Action %s" %action) if (rcname != "") and (rcexec !=""): - new_action = ActionTup(disp_name = rcname, command = rcexec, - act_name = action, is_action = True) + new_action = ActionTup(name=rcname, command= rcexec) self.rc_actions.append(new_action) + return True @@ -860,6 +888,39 @@ cl_start = os.path.expanduser("~/.local/share/applications/mda_") return os.path.expanduser(self.desktop_file).beginswith(cl_start) + def win_state_changed(self, wnck_win, changed_mask, new_state): + """Handler for the wnck_window state-changed event + + If the app needs attention and we're not already flashing the icon + start it flashing. If the app icon is not visible, make it visible + + If the app doesn't need attention and its icon is flashing, stop + it flashing + + """ + + if ((new_state & Wnck.WindowState.DEMANDS_ATTENTION) != 0) or\ + ((new_state & Wnck.WindowState.URGENT) != 0): + + if not self.is_flashing: + self.is_flashing = True + self.flash_on = False # initial flash state = off + Flasher = FlashTimer(self) + + if not self.is_visible(): + self.show_icon() + self.set_icon_geometry + + else: + if self.is_flashing == True: + # we need to turn flashing off + self.is_flashing = False + self.queue_draw() + + # the timer will handle the rest .... + # hiding the icon (if necessary) will be taken care of next time the user + # changes workspace + def do_expose_event(self, drawing_area, event): """The main drawing event for the docked app. @@ -870,6 +931,8 @@ if the app is the foreground app, highlight the background with a gradient fill if the app is pulsing, draw the icon with a variable level of transparency according to the pulse count + if the app is flashing, draw the icon either fully opaque or completely transparent + according to its flash state Additionally, if necessary, calculate and set the app's on screen minimise location (this is done here as it can only be done after the drawing area has been created, set to @@ -942,9 +1005,13 @@ ctx.paint_with_alpha(alpha) + elif self.is_flashing: + if self.flash_on: + ctx.paint() # draw normally if in the flash on state else: ctx.paint() + if self.has_mouse: # lighten the icon ctx.set_operator(cairo.OPERATOR_ADD) @@ -1053,21 +1120,13 @@ if run_it == "/usr/bin/startcaja": run_it = "caja" - # remove any command line arguments - if "%" in run_it: - i = run_it.rfind("%") - run_it = run_it[0:i-1] + # remove any command line arguments + if "%" in run_it: + i = run_it.rfind("%") + run_it = run_it[0:i-1] - # start the app - self.run_cmd_line (run_it) - else: - - the_dae = Gio.DesktopAppInfo.new_from_filename(self.desktop_file) - alc = Gdk.AppLaunchContext() - alc.set_desktop(-1) # use default screen & desktop - the_dae.launch(None, alc) - # set the app icon pulsing - throbber = PulseTimer(self) + # start the app + self.run_cmd_line (run_it) def run_cmd_line(self, cmd_line): """Run a command line. To be used when starting an app for the first time or when running @@ -1098,19 +1157,7 @@ """ if len(self.rc_actions) >= act_no: - act = self.rc_actions[act_no-1] - if act.is_action == True: - # Gio.DesktopAppInfo give a more convenient and complete way to - # run actions - the_dae = Gio.DesktopAppInfo.new_from_filename(self.desktop_file) - alc = Gdk.AppLaunchContext() - alc.set_desktop(-1) # use default screen & desktop - the_dae.launch_action(act.act_name,alc) - else: - self.run_cmd_line(self.rc_actions[act_no-1].command) - - # set the app icon pulsing - throbber = PulseTimer(self) + self.run_cmd_line(self.rc_actions[act_no-1].command) def get_rc_action(self, act_no): """ return a specified right click action's details @@ -1125,9 +1172,8 @@ """ if len(self.rc_actions) >= act_no: - return (True, self.rc_actions[act_no-1].disp_name, + return (True, self.rc_actions[act_no-1].name, self.rc_actions[act_no-1].command) - else: return (False, "", "")
